Unit 09 · lesson
Constructors and Encapsulation Defend Invariants
A class with public fields allows outside code to create impossible states whenever it wants.
class Attempt {
int completed;
int total;
}
Nothing prevents:
attempt.completed = 20;
attempt.total = 10;
If the rule says completed cannot exceed total, the object is now invalid.
Construction is the first boundary
A constructor can require valid initial state.
class Attempt {
private int completed;
private int total;
Attempt(int completed, int total) {
if (total <= 0 || completed < 0 || completed > total) {
throw new IllegalArgumentException("invalid attempt counts");
}
this.completed = completed;
this.total = total;
}
double percent() {
return (double) completed / total * 100;
}
}
The private fields prevent arbitrary direct mutation from unrelated outside code.
The constructor establishes the invariant.
this distinguishes object state
Inside:
this.completed = completed;
this.completedis the field belonging to the current object;completedis the constructor parameter.
The repeated name is common because both represent the same concept at different boundaries.
Encapsulation is not "make everything private"
Encapsulation means controlling access so the type can defend its rules and present a coherent interface.
If a field can safely be immutable, consider final:
private final int total;
If state may change, expose a method representing the valid state transition instead of a generic setter when possible.
Weak:
setCompleted(-100)
Stronger domain operation:
recordCompletion()
when that is what the system actually does.
Invariants survive every public operation
A constructor can create a valid object, then a badly designed method can break it later.
List every operation that can change state and ask:
Can this operation leave the object invalid?
The invariant must hold after construction and after every valid public state transition.
Build an invariant-defending type
Choose one domain:
- percentage 0..100;
- inventory count never below zero;
- battery percentage 0..100;
- match score never negative;
- booking with end time after start time.
Implement either a class or record that prevents invalid construction.
If the object can change, provide at least one method that changes state while preserving the invariant.
Fault test
Attempt one invalid construction or transition. Preserve the failure evidence.
Then show at least two valid cases, including a boundary value.
Evidence
Your model dossier should contain:
domain concept:
state:
invariant:
constructor responsibility:
public behavior:
invalid state blocked:
boundary test:
why class or record:
The object earns its place by making the domain easier to reason about, not by increasing the class count.