Encapsulation
1 Encapsulation

Encapsulation

CS 3500, Spring 2024

Back to Connect $N$

Our model’s interface

static enum Status { Playing, Stalemate, Won, }

Status getStatus();
boolean isGameOver();
int getNextPlayer();
int getWinner();

Integer getPlayerAt(int x, int y);
boolean isColumnFull(int which);

int move(int who, int where);

int getWidth(); int getHeight();
int getGoal(); int getPlayers();

What fields do we need?

int width;
int height;
int goal;
int players;
Status status;
int turn;

List<List<Integer>> columns;

How about some more flexibility‽

Why commit to players being ints?

int width;
int height;
int goal;
int players;

Status status;
int turn;

List<List<Integer>> columns;

More flexibility!

int width;
int height;
int goal;
int players;

Status status;
Object turn;

List<List<Object>> columns;

What if we want 3-D Connect $N$?

int width;
int height;
int goal;
int players;

Status status;
Object turn;

List<List<Object>> columns;

Or $k$-D Connect $N$?

int width;
int height;
int goal;
int players;

Status status;
Object turn;

List<List<Object>> columns;

That’s better

int width;
int height;
int goal;
int players;

Status status;
Object turn;

Object hypercolumns;

Width and height are only 2-D

int width;
int height;
int goal;
int players;

Status status;
Object turn;

Object hypercolumns;

Ah, this is more general

Map<String, Integer> dimensions;

int goal;
int players;

Status status;
Object turn;

Object hypercolumns;

But what about goal & players?

Map<String, Integer> dimensions;

int goal;
int players;

Status status;
Object turn;

Object hypercolumns;

Into the Map with them!

Map<String, Integer> configuration;




Status status;
Object turn;

Object hypercolumns;

In $k$-D, status might get interesting

Map<String, Integer> configuration;




Status status;
Object turn;

Object hypercolumns;

Better not limit it

Map<String, Object> properties;





Object turn;

Object hypercolumns;

Hmm, I dunno about turn

Map<String, Object> properties;





Object turn;

Object hypercolumns;

The Map means we decide later!

Map<String, Object> properties;







Object hypercolumns;

Go big or go home

Map<String, Object> properties;

Our model representation is now as flexible as can be

Do we like it?

ಠ_ಠ

What’s the trade-off here?

We gained flexibility

We lost meaning and intent

Specifically...

  • might not have width, height, goal, columns, etc.

  • those properties might have the wrong types

Let’s try the other direction

Does this representation allow too much freedom?

int width;
int height;
int goal;
int players;

Status status;
int turn;

List<List<Integer>> columns;

Bad freedoms

  • width or height or goal or players might change mid-game

  • status or columns might be null

  • width or height or goal or players might be zero or negative

  • the shape of the list-of-lists in columns might not match the dimensions in width and height

  • or it might contain Integers that don’t stand for actual players

  • and the client can look at or change whatever it pleases

The language lets us rule some of these possibilities out

Use final wherever you can

If it shouldn't change, let the compiler check for you that it doesn't

final int width;
final int height;
final int goal;
final int players;

Status status;
int turn;

List<List<Integer>> columns;

Now variables cannot change after you initialize them in the constructor

Use final wherever you can

If it shouldn't change, let the compiler check for you that it doesn't

final int width;
final int height;
final int goal;
final int players;

Status status;
int turn;

final List<List<Integer>> columns;

Bad freedoms

  • width or height or goal or players might change mid-game

  • width or height or goal or players might be zero or negative

  • status or columns might be null

  • the shape of the list-of-lists in columns might not match the dimensions in width and height

  • or it might contain Integers that don’t stand for actual players

  • and the client can look at or change whatever it pleases

Ensure correct initialization using a constructor

public ConnectNModelImpl(int width, int height,
                         int goal, int players)
{
  if (width < 1 || height < 1 || goal < 2 || players < 1) {
    throw new IllegalArgumentException(…);
  }

  columns = new ArrayList<>(width);
  for (int i = 0; i < width; ++i) {
    columns.add(new ArrayList<>(height));
  }

  ...
}

(If the constructor is private, checking might happen in a factory.)

Bad possibilities

  • width or height or goal or players might change mid-game

  • width or height or goal or players might be zero or negative

  • status or columns might be null

  • the shape of the list-of-lists in columns might not match the dimensions in width and height

  • or it might contain Integers that don’t stand for actual players

  • and the client can look at or change whatever it pleases

The last point is key!

Use private whenever you can

Prevent directly accessing from outside the class

private final int width;
private final int height;
private final int goal;
private final int players;

private Status status;
private int turn;

private List<List<Integer>> columns;

Principle of least privilege

Every program and every privileged user of the system should operate using the least amount of privilege necessary to complete the job.

—Jerome Saltzer

Encapsulation

Any pair of integers looks like any other

Methods give meaning to the data

Hiding the fields preserves meaning and adds flexibility

Access level modifiers

ModifierScope
privatesame class only
default...and everything else in the package
protected...and subclasses
public...and the rest of the world

Where can it be seen from?

Modifierclasspackagesubclassworld
private
default
protected
public

Who can see members of Base?

package left;                       package right;

public class Base { ... }           public class Derived
                                    extends Base { ... }

public class LeftHelper { ... }     public class RightHelper { ... }

ModifierBaseLeftHelperDerivedRightHelper
private
default
protected
public

Bad possibilities

  • width or height or goal or players might change mid-game

  • width or height or goal or players might be zero or negative

  • status or columns might be null

  • the shape of the list-of-lists in columns might not match the dimensions in width and height

  • or it might contain Integers that don’t stand for actual players

  • and the client can look at or change whatever it pleases

The principle of invariance

Preserving the “validity” of an object

  • Don’t let outsiders access it

  • If possible make it final so that once valid, always valid

  • What about mutable objects?

An example

final class Even {
  public Even(int value) {
    if (value % 2 != 0) {
      throw IllegalArgumentException("value must be even");
    }

    this.value = value;
  }

  public int getValue() {
    return value;
  }
  private final int value;
}

value represents the state of this object.

How do we know that value is always even?

How do we know that value is always even?

  • The constructor ensures it

  • It can't change

Another example

final class EvenCounter {
  public EvenCounter(int value) {
    if (value % 2 != 0) {
      throw IllegalArgumentException("value must be even");
    }

    this.value = value;
  }

  public int nextValue() {
    return value += 2;
  }
  private int value;
}

How do we know that value is always even?

  • The constructor ensures it

  • Only methods can change it

  • Method nextValue() preserves evenness

  • There aren't any other (public) methods

Ruling out nonsense

Which methods are okay?

public void reset() {
  value = 0;
}
public int scale(int factor) {
  return value *= factor;
}
public int halve() {
  return value /= 2;
}
public int half() {
  return value / 2;
}

What’s going on here?

A class invariant is a logical statement about the instantaneous state of an object that is ensured by the constructors and preserved by the methods.

public int nextValue() {
  return value += 2;
}
private int value;
// INVARIANT: value is even

Definitions

A class invariant is a logical statement about the instantaneous state of an object that is ensured by the constructors and preserved by the methods.

  • logical statement: a claim that is true or false

  • the instantaneous state of an object: meaning the values in its fields at a point in time

  • ensured by the constructors: when a constructor finishes normally then the claim is true

  • preserved by the methods: if the claim is true before a method is called then it is true afterward

Non-examples

A class invariant is a logical statement about the instantaneous state of an object that is ensured by the constructors and preserved by the methods.

// INVARIANT: value is small

Not a logical statement—too vague

// INVARIANT: value never decreases

Not instantaneous—not even wrong!

// INVARIANT: value is non-negative

Just not true

// INVARIANT: value is an int

True, but vacuous because Java guarantees it

“Examples” from Connect $N$ — which actually hold?

private final int width;
// INVARIANT (1): width is not null
// INVARIANT (2): width > 0
// INVARIANT (3): width > height
// INVARIANT (4): width never changes
private int turn;
// INVARIANT (5): turn only increases
// INVARIANT (6): turn > 0
// INVARIANT (7): turn < players

(For those reading along at home, only 2 and 7 are invariants.)

More examples from Connect $N$

private List<List<Integer>> columns;
// INVARIANT (1): columns != null
// INVARIANT (2): columns.size() == width
// INVARIANT (3): columns.get(col) != null if 0 <= col < width
// INVARIANT (4): every column in columns has size <= height
// INVARIANT (5): every Integer in columns is a valid player
//                in (0, players]
// NOT AN INVARIANT: columns
// NOT AN INVARIANT: columns agrees with width
// NOT AN INVARIANT: columns always refers to the same list
// INVARIANT BUT VACUOUS: columns is a list

Class invariants enable rely-guarantee reasoning

The class-invariant reasoning principle:

  • If the constructor ensures it,

  • and the methods preserve it,

  • then the methods can rely on it.

Rule out representations you don't want (whether invalid or merely inconvenient)

Example: rational numbers

“Merely inconvenient” representation:

public Rational(long numerator, long denominator) {
  if (denominator == 0) {
    throw new IllegalArgumentException(DEN_ZERO_MSG);
  }

  num = numerator;
  den = denominator;
}

private final long num;
private final long den;
// INVARIANT: den != 0

Continue with the code.

Example: Heaps