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 int
s?
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
orheight
orgoal
orplayers
might change mid-gamestatus
orcolumns
might benull
width
orheight
orgoal
orplayers
might be zero or negativethe shape of the list-of-lists in
columns
might not match the dimensions inwidth
andheight
or it might contain
Integer
s that don’t stand for actual playersand 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
orheight
orgoal
orplayers
might change mid-gamewidth
orheight
orgoal
orplayers
might be zero or negativestatus
orcolumns
might benull
the shape of the list-of-lists in
columns
might not match the dimensions inwidth
andheight
or it might contain
Integer
s that don’t stand for actual playersand 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
orheight
orgoal
orplayers
might change mid-gamewidth
orheight
orgoal
orplayers
might be zero or negativestatus
orcolumns
might benull
the shape of the list-of-lists in
columns
might not match the dimensions inwidth
andheight
or it might contain
Integer
s that don’t stand for actual playersand 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
Modifier | Scope |
---|---|
private | same 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?
Modifier | class | package | subclass | world |
---|---|---|---|---|
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 { ... }
Modifier | Base | LeftHelper | Derived | RightHelper |
---|---|---|---|---|
private | ✓ | ✗ | ✗ | ✗ |
default | ✓ | ✓ | ✗ | ✗ |
protected | ✓ | ✓ | ✓ | ✗ |
public | ✓ | ✓ | ✓ | ✓ |
Bad possibilities
width
orheight
orgoal
orplayers
might change mid-gamewidth
orheight
orgoal
orplayers
might be zero or negativestatus
orcolumns
might benull
the shape of the list-of-lists in
columns
might not match the dimensions inwidth
andheight
or it might contain
Integer
s that don’t stand for actual playersand 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 validWhat 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 evennessThere 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.