Learn five essential behavioural design patterns in Java, including Strategy, Observer, Command, Template Method, and State, with practical examples and use cases.
Updated August 2026 — Behavioural Patterns (Part 3) restored for this URL.
TL;DR
-
Behavioural patterns define how objects communicate, share responsibilities, and manage workflows.
-
Strategy lets you swap algorithms at runtime without relying on long
if/elseorswitchchains. -
Observer and Command support event-driven communication, queued actions, logging, and undoable operations.
-
Template Method and State organize fixed workflows and state-specific behavior without complex conditional logic.
-
Choose patterns based on the problem, not the pattern name. Use the smallest pattern that reduces coupling or removes unnecessary complexity.
This is Part 3 of Software Design Patterns. We already covered:
Behavioural patterns describe how objects assign responsibilities and talk to each other — algorithms, notifications, undo stacks, and workflows.
We will cover:
1. Strategy Pattern
Strategy lets you swap algorithms at runtime without rewriting the caller. Shipping cost, payment method, and sort comparators are common strategies.
interface ShippingStrategy {
int quoteCents(int weightGrams);
}
class FlatRateShipping implements ShippingStrategy {
public int quoteCents(int weightGrams) { return 500; }
}
class WeightShipping implements ShippingStrategy {
public int quoteCents(int weightGrams) { return weightGrams; } // 1 cent/gram
}
class Checkout {
private ShippingStrategy shipping;
Checkout(ShippingStrategy shipping) { this.shipping = shipping; }
void setShipping(ShippingStrategy s) { this.shipping = s; }
int total(int itemCents, int weightGrams) {
return itemCents + shipping.quoteCents(weightGrams);
}
}
Checkout cart = new Checkout(new FlatRateShipping());
System.out.println(cart.total(2000, 800));
cart.setShipping(new WeightShipping());
System.out.println(cart.total(2000, 800));
Prefer Strategy over long if/else or switch chains that pick an algorithm.
2. Observer Pattern
Observer (publish/subscribe): when a subject changes, all registered observers are notified. UI listeners, event buses, and stock tickers use this idea.
interface Observer {
void update(String event);
}
class OrderSubject {
private final List<Observer> observers = new ArrayList<>();
void subscribe(Observer o) { observers.add(o); }
void unsubscribe(Observer o) { observers.remove(o); }
void publish(String event) {
for (Observer o : observers) o.update(event);
}
}
class EmailObserver implements Observer {
public void update(String event) {
System.out.println("Email got: " + event);
}
}
class AnalyticsObserver implements Observer {
public void update(String event) {
System.out.println("Analytics got: " + event);
}
}
OrderSubject orders = new OrderSubject();
orders.subscribe(new EmailObserver());
orders.subscribe(new AnalyticsObserver());
orders.publish("ORDER_PAID");
Keep observers lightweight. Heavy work should go to a queue so one slow listener does not block others.
3. Command Pattern
Command wraps a request as an object. You can queue it, log it, undo it, or send it across a network.
interface Command {
void execute();
void undo();
}
class Light {
void on() { System.out.println("Light ON"); }
void off() { System.out.println("Light OFF"); }
}
class LightOnCommand implements Command {
private final Light light;
LightOnCommand(Light light) { this.light = light; }
public void execute() { light.on(); }
public void undo() { light.off(); }
}
class Remote {
private final Deque<Command> history = new ArrayDeque<>();
void press(Command cmd) {
cmd.execute();
history.push(cmd);
}
void undo() {
if (!history.isEmpty()) history.pop().undo();
}
}
Light lamp = new Light();
Remote remote = new Remote();
remote.press(new LightOnCommand(lamp));
remote.undo();
Editors (undo/redo) and job queues are textbook Command uses.
4. Template Method Pattern
Template Method defines the skeleton of an algorithm in a base class; subclasses fill in steps without changing the overall order.
abstract class DataImporter {
public final void run(String path) {
byte[] raw = read(path);
List<String> rows = parse(raw);
persist(rows);
afterImport();
}
protected abstract byte[] read(String path);
protected abstract List<String> parse(byte[] raw);
protected abstract void persist(List<String> rows);
protected void afterImport() { /* optional hook */ }
}
class CsvImporter extends DataImporter {
protected byte[] read(String path) { return path.getBytes(); }
protected List<String> parse(byte[] raw) {
return Arrays.asList(new String(raw).split("\n"));
}
protected void persist(List<String> rows) {
System.out.println("Saving " + rows.size() + " CSV rows");
}
}
The final run method locks the workflow; subclasses cannot reorder steps accidentally.
5. State Pattern
State lets an object alter behavior when its internal state changes — as if the class changed. Order lifecycles (NEW → PAID → SHIPPED) map well to State.
interface OrderState {
void pay(OrderContext ctx);
void ship(OrderContext ctx);
}
class OrderContext {
private OrderState state = new NewState();
void setState(OrderState s) { this.state = s; }
void pay() { state.pay(this); }
void ship() { state.ship(this); }
}
class NewState implements OrderState {
public void pay(OrderContext ctx) {
System.out.println("Payment accepted");
ctx.setState(new PaidState());
}
public void ship(OrderContext ctx) {
throw new IllegalStateException("Pay first");
}
}
class PaidState implements OrderState {
public void pay(OrderContext ctx) {
throw new IllegalStateException("Already paid");
}
public void ship(OrderContext ctx) {
System.out.println("Shipped");
ctx.setState(new ShippedState());
}
}
class ShippedState implements OrderState {
public void pay(OrderContext ctx) { /* no-op or error */ }
public void ship(OrderContext ctx) { /* already shipped */ }
}
Compared with a giant switch (status), State keeps each status’s rules in its own class.
Wrap-up
- Creational – how objects are created (Part 1)
- Structural – how objects are composed (Part 2)
- Behavioural – how objects collaborate (this part)
You do not need every pattern on every project. Learn to recognize the smell (long switches, fragile inheritance, tight coupling) and pick the smallest pattern that removes it.
Start again from Part 1 if you want the full series in order.
Final Thoughts
Behavioural design patterns are about making interactions between objects clearer and easier to manage. Instead of allowing algorithms, events, workflows, or state rules to become tangled inside large classes, these patterns give each responsibility a more deliberate structure.
The five patterns in this part address different problems: Strategy handles interchangeable algorithms, Observer manages notifications, Command turns actions into objects, Template Method controls a consistent workflow, and State separates behavior based on an object’s current state.
You do not need to use every pattern in every project. The real skill is recognizing when code has become difficult to extend or maintain, then choosing the simplest pattern that solves the problem.