Java Design Patterns: Strategy vs. State – Key Differences Explained
The Strategy pattern encapsulates interchangeable algorithms selected explicitly by the client, while the State pattern manages internal behavioral transitions where the object appears to change its class based on lifecycle conditions.
Understanding the distinction between Strategy and State is essential for applying behavioral patterns correctly in Java. According to the source code in the CyC2018/CS-Notes repository, both patterns use delegation but solve fundamentally different architectural problems—Strategy handles algorithmic variation, while State manages complex object lifecycle transitions. This article breaks down the structural differences, control flow mechanisms, and practical implementations found in notes/设计模式 - 策略.md and notes/设计模式 - 状态.md.
Core Conceptual Differences
Purpose and Intent
Strategy addresses the need to encapsulate a family of interchangeable algorithms and let the client pick or switch one at runtime. The pattern defines a Strategy interface and multiple concrete implementations (such as different sorting or validation algorithms) that the context can use interchangeably.
State allows an object to change its behavior when its internal state changes, making the object appear to change its class. Rather than selecting algorithms externally, the pattern models an object that passes through distinct phases (like NoQuarterState to HasQuarterState in a vending machine), with each phase defining its own behavior and transition rules.
Control Flow and Transition Triggers
In the Strategy pattern, the client explicitly controls which algorithm executes. The client code calls context.setStrategy(new ConcreteStrategy()), and the context simply forwards requests to the current strategy instance. This represents a static composition where the set of algorithms is defined once, and external code decides which to use.
In the State pattern, state transitions happen internally. Concrete state classes (like NoQuarterState) hold a reference back to the context (e.g., private GumballMachine gumballMachine) and trigger transitions by calling machine.setState(machine.getHasQuarterState()) within their own methods. This creates a dynamic composition driven by the object's internal state machine rather than external client decisions.
Coupling and Object Relationships
Strategy maintains loose coupling: the context knows only the Strategy interface, and concrete strategies remain independent of the context. They receive data through method parameters rather than holding references to the context.
State introduces tighter coupling between concrete states and the context. Because states must trigger transitions (e.g., moving from HasQuarterState to SoldState when the crank turns), concrete state classes typically maintain a back-reference to the context object, allowing them to invoke setState() and modify the context's internal variables directly.
Implementation Examples from CS-Notes
Strategy Pattern: Duck Behavior
The CS-Notes repository demonstrates Strategy using a duck simulation where quacking behavior can be swapped at runtime. In notes/设计模式 - 策略.md, the Duck class acts as the context holding a QuackBehavior reference, with concrete strategies Quack and Squeak implementing the algorithm interface.
// Strategy interface defined in notes/设计模式 - 策略.md
public interface QuackBehavior {
void quack();
}
// Concrete strategies
public class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("quack!");
}
}
public class Squeak implements QuackBehavior {
@Override
public void quack() {
System.out.println("squeak!");
}
}
// Context class
public class Duck {
private QuackBehavior quackBehavior;
public void performQuack() {
if (quackBehavior != null)
quackBehavior.quack();
}
public void setQuackBehavior(QuackBehavior qb) {
this.quackBehavior = qb;
}
}
// Client explicitly selects strategy
public class Client {
public static void main(String[] args) {
Duck duck = new Duck();
duck.setQuackBehavior(new Squeak());
duck.performQuack(); // Output: squeak!
duck.setQuackBehavior(new Quack());
duck.performQuack(); // Output: quack!
}
}
State Pattern: Gumball Machine
The State pattern implementation in notes/设计模式 - 状态.md models a gumball machine that behaves differently based on whether it has a quarter, is sold out, or is dispensing. The GumballMachine context delegates all actions (insertQuarter(), turnCrank(), dispense()) to its current State object, while concrete states like NoQuarterState manage the transitions.
// State interface from notes/设计模式 - 状态.md
public interface State {
void insertQuarter();
void ejectQuarter();
void turnCrank();
void dispense();
}
// Concrete state with back-reference to context
public class NoQuarterState implements State {
private GumballMachine machine;
public NoQuarterState(GumballMachine m) {
this.machine = m;
}
@Override
public void insertQuarter() {
System.out.println("You insert a quarter");
machine.setState(machine.getHasQuarterState());
}
// Other methods handle invalid actions for this state
}
// Context class managing state instances
public class GumballMachine {
private State noQuarterState, hasQuarterState, soldState, soldOutState;
private State state;
private int count;
public GumballMachine(int n) {
count = n;
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
soldOutState = new SoldOutState(this);
state = (n > 0) ? noQuarterState : soldOutState;
}
// Delegates to current state
public void insertQuarter() { state.insertQuarter(); }
public void turnCrank() { state.turnCrank(); state.dispense(); }
public void setState(State s) { this.state = s; }
public State getHasQuarterState() { return hasQuarterState; }
}
// Client interaction triggers internal state changes
public class Client {
public static void main(String[] args) {
GumballMachine gm = new GumballMachine(5);
gm.insertQuarter(); // Transitions to HasQuarterState internally
gm.turnCrank(); // Transitions to SoldState, then NoQuarterState
}
}
When to Use Each Pattern
Choose Strategy when you need multiple interchangeable algorithms (sorting, formatting, validation) that can be selected at runtime by client code. Use State when an object must exhibit different behavior depending on its lifecycle phase or conditions, and those phases have complex transition logic that should be encapsulated separately from the context.
Summary
- Strategy encapsulates algorithms; State encapsulates lifecycle phases and transitions.
- Strategy transitions are triggered externally by the client calling
setStrategy(); State transitions are triggered internally by concrete state classes callingsetState()on the context. - Strategy maintains loose coupling with no back-references; State requires concrete states to hold references to the context to facilitate transitions.
- Both patterns rely on delegation, but Strategy represents static algorithm selection while State represents dynamic behavioral composition.
Frequently Asked Questions
Can the Strategy and State patterns be used together in the same application?
Yes, these patterns are complementary. You might use State to manage high-level object phases (like connection states in a network client) while using Strategy within specific states to vary algorithmic behavior (like different compression strategies during an active connection). The CS-Notes repository documents both patterns independently in notes/设计模式 - 目录.md for this reason.
How does client code interaction differ between Strategy and State implementations?
With Strategy, the client explicitly instantiates and assigns strategies using methods like setQuackBehavior(), maintaining full control over which algorithm executes. With State, the client simply invokes actions like insertQuarter() or turnCrank() on the context, remaining unaware of the current state or the transition logic that determines the next behavioral phase.
Which pattern is better for implementing a finite state machine?
The State pattern is the canonical choice for finite state machines because it models states as discrete objects with explicit transition logic. Each state class (e.g., HasQuarterState) encapsulates both the behavior for that state and the rules for moving to subsequent states, preventing the complex conditional logic that would clutter a monolithic context class.
Why do State pattern implementations typically show higher coupling than Strategy pattern implementations?
Concrete state classes in the State pattern require back-references to the context (e.g., private GumballMachine machine) so they can trigger transitions by calling machine.setState(). This necessity creates bidirectional dependencies between states and context. In contrast, Strategy concrete classes are purely algorithmic and receive all necessary data through method parameters, maintaining unidirectional dependency from context to strategy only.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →