Design Patterns

Behavioral Patterns I: Strategy, Observer, Command

Three of the most-used behavioral patterns: swapping an algorithm at runtime (Strategy), broadcasting state changes to interested listeners (Observer), and turning a request into a first-class object (Command).
  • Strategy: defines a family of interchangeable algorithms behind one interface, letting the algorithm vary independently of the code that uses it
  • Observer: defines a one-to-many dependency so that when one object (the subject) changes state, all its dependents (observers) are notified automatically
  • Command: encapsulates a request (an action plus its arguments) as an object, enabling queuing, logging, and undo/redo
  • In modern Java, Strategy and Command are frequently just a Comparator, Runnable, or custom functional interface passed as a lambda — the pattern still applies, the ceremony shrinks
  • Observer underlies GUI event listeners, reactive streams, and the classic publish-subscribe architecture — Behavioral Patterns Ii continues with Iterator, Template Method, and State
Three patterns, three distinct jobs
PatternProblemJava shape
Strategyswap an algorithm without an if/else per variantinterface + implementations, or a lambda
Observernotify many dependents of a state changePropertyChangeListener, GUI listeners, reactive streams
Commandtreat "do this action" as data you can queue/log/undoRunnable, a Command object with execute()/undo()
Strategy — classic form vs. modern lambda form
// Classic: interface + one class per strategy
interface DiscountStrategy { double apply(double price); }
class NoDiscount implements DiscountStrategy { public double apply(double p) { return p; } }
class TenPercentOff implements DiscountStrategy { public double apply(double p) { return p * 0.9; } }

class Order {
    private DiscountStrategy discount;
    void setDiscount(DiscountStrategy d) { this.discount = d; }   // swap algorithm at runtime
    double total(double price) { return discount.apply(price); }
}

// Modern: the interface is already there (Function<Double, Double>), skip the classes
Order order = new Order();
order.setDiscount(price -> price * 0.9);   // the strategy itself, no named class needed
Command — enabling undo
interface Command { void execute(); void undo(); }

class InsertTextCommand implements Command {
    private final Document doc; private final String text; private final int pos;
    InsertTextCommand(Document doc, String text, int pos) { this.doc = doc; this.text = text; this.pos = pos; }
    public void execute() { doc.insert(pos, text); }
    public void undo() { doc.delete(pos, pos + text.length()); }
}

Deque<Command> history = new ArrayDeque<>();
void perform(Command c) { c.execute(); history.push(c); }
void undoLast() { if (!history.isEmpty()) history.pop().undo(); }
Because the request is an object, not just a method call, it can be stored, queued, and reversed — the essence of Command
Sources
  • Head First Design Patterns (2nd ed.)Ch. 1, 2, 6 — Strategy; Observer; Command