State and Strategy Patterns
The State Pattern
Intent: Let an object change its behaviour cleanly when its internal state changes, without a sprawling if/switch on a status field. The object appears to change class.
The problem
An academic’s duties change as they progress through career stages. Without the State pattern, you would write:
class Academic {
private String rank;
String duties() {
if (rank.equals("Lecturer")) {
return "Teaching and research";
} else if (rank.equals("SeniorLecturer")) {
return "Teaching, research, and administration";
} else if (rank.equals("Professor")) {
return "Research leadership and mentoring";
}
return "Unknown";
}
}
This is brittle: adding a new rank means modifying the duties() method (violating OCP). The logic for each rank is scattered across every method that depends on rank.
The solution
Encapsulate state-dependent behaviour in separate state classes:
interface AcademicState {
String duties();
}
class LecturerState implements AcademicState {
public String duties() {
return "Teaching and research";
}
}
class SeniorLecturerState implements AcademicState {
public String duties() {
return "Teaching, research, and administration";
}
}
class ProfessorState implements AcademicState {
public String duties() {
return "Research leadership and mentoring";
}
}
class Academic {
private AcademicState state;
Academic() {
this.state = new LecturerState();
}
void promote(AcademicState next) {
this.state = next;
}
String duties() {
return state.duties();
}
}
The Academic object delegates duties() to its current state object. When the academic is promoted, the state reference is swapped to a different implementation — the object’s behaviour changes without changing the object’s class.
Key characteristics of State
- The object itself changes its state (or has it changed by an internal trigger).
- The state transitions are part of the object’s lifecycle.
- The client does not typically choose the state — it is a consequence of the object’s history.
The Strategy Pattern
Intent: Define a family of interchangeable algorithms, encapsulate each one, and make them interchangeable at runtime. The strategy is chosen externally by the client.
The problem
A till needs to make change using different algorithms: a greedy approach or a dynamic programming approach for optimal coin usage. Without Strategy, you would embed the algorithm choice in the Till class with conditional logic.
The solution
interface ChangeStrategy {
List<Integer> makeChange(int amount);
}
class GreedyChange implements ChangeStrategy {
public List<Integer> makeChange(int amount) {
List<Integer> coins = new ArrayList<>();
int[] denominations = {50, 20, 10, 5, 2, 1};
for (int d : denominations) {
while (amount >= d) {
coins.add(d);
amount -= d;
}
}
return coins;
}
}
class DPChange implements ChangeStrategy {
public List<Integer> makeChange(int amount) {
// dynamic programming implementation for optimal change
// ...
}
}
class Till {
private ChangeStrategy strategy;
Till(ChangeStrategy s) {
this.strategy = s;
}
void setStrategy(ChangeStrategy s) {
this.strategy = s;
}
List<Integer> giveChange(int amount) {
return strategy.makeChange(amount);
}
}
The client decides which strategy to use:
Till till = new Till(new GreedyChange());
till.giveChange(67); // uses greedy
till.setStrategy(new DPChange());
till.giveChange(67); // uses DP for optimal change
Key characteristics of Strategy
- The strategy is chosen by the client, not by the object itself.
- The object usually holds one strategy for its lifetime (or until the client explicitly changes it).
- Strategies are typically about how to compute something, not about the object’s own lifecycle.
The critical Tripos distinction
State and Strategy are structurally near-identical: both delegate to an interchangeable interface, both have a context class holding a reference to a behaviour object, both allow swapping the behaviour at runtime. The difference is entirely in intent:
| Aspect | State | Strategy |
|---|---|---|
| Who decides the change? | The object itself (internally, in response to its lifecycle) | The client (externally, chooses the algorithm) |
| How many changes? | Multiple transitions through a lifecycle | Typically set once and used for the lifetime |
| What drives the change? | The object’s internal state | External requirements |
| Example | TCP connection: LISTEN → ESTABLISHED → CLOSED | Sorting: quicksort vs mergesort chosen by caller |
| Analogy | A light switch toggling between on/off | Choosing which screwdriver to use for the job |
Tripos tip: If a question asks you to justify which pattern fits a scenario, argue from intent, not from structure. Write something like: “Although both patterns could structurally model this, the Strategy pattern is more appropriate because the algorithm choice is made externally by the client, not driven by the object’s own lifecycle transitions.”
Patterns and design principles
- Both patterns satisfy OCP: you can add new states or new strategies without modifying the context class.
- Both promote SRP: each state/strategy class has a single reason to change.
- Both demonstrate composition over inheritance: the context has-a state/strategy rather than subclassing for each behaviour variant.
Tripos traps
- Naming the wrong pattern — A question describes an object whose behaviour depends on its internal state (e.g., a vending machine that behaves differently when idle, selecting, or dispensing). This is State, but students sometimes call it Strategy because both involve delegation. Check who changes the behaviour.
- Missing the lifecycle — State makes sense when there is a finite state machine with transitions. If there are no transitions and the client just picks a behaviour, it is Strategy.
- Over-engineering — Both patterns add indirection. If the behaviour is simple and unlikely to change, a basic conditional may be more readable. The Tripos wants you to know when a pattern is justified.