Design Patterns

Sefism.

Chapter 05

Behavioural patterns: varying behaviour

Patterns for swapping algorithms, changing behaviour with state, fixing a skeleton of steps, and adding operations to object structures.

Strategy

Also called policy, pluggable algorithm

What
Intent: define a family of algorithms behind one interface and choose one at run time.
Why
When: you have if-else chains choosing how to do something, such as pricing rules, sorting or shipping cost.
TypeScript
type Pricing = (base: number) => number;

const regular: Pricing = (b) => b;
const student: Pricing = (b) => b * 0.8;
const sale: Pricing = (b) => Math.max(b - 500, 0);

function total(base: number, pricing: Pricing) {
  return pricing(base);
}

State

Also called state machine, finite state machine

What
Intent: an object changes its behaviour when its internal state changes, as if it changed class. Each state is its own object.
Why
When: many methods switch on the same status field, such as an order that is pending, paid, shipped or cancelled.
TypeScript
interface OrderState { ship(o: Order): void; }

class Paid implements OrderState {
  ship(o: Order) { o.state = new Shipped(); }
}
class Pending implements OrderState {
  ship() { throw new Error('Pay first'); }
}

Watch out

Mixing it up with Strategy: the client picks a strategy, while states switch themselves.

Template Method

Also called hook methods, skeleton algorithm

What
Intent: a base class fixes the steps of an algorithm and lets subclasses fill in some of them.
Why
When: several variants share the same outline, such as importers that all read, validate and save but parse differently.
TypeScript
abstract class Importer {
  run(path: string) {
    const rows = this.parse(read(path));   // varies
    rows.forEach(validate);                // fixed
    save(rows);                            // fixed
  }
  protected abstract parse(raw: string): Row[];
}

Visitor

Also called double dispatch

What
Intent: add new operations to a fixed set of classes without changing them, by moving each operation into a visitor object.
Why
When: the class hierarchy is stable but operations keep growing, such as compilers walking syntax trees to type-check, print or optimise.
TypeScript
interface Visitor<R> { num(n: Num): R; add(a: Add): R; }

class Num { constructor(public v: number) {}
  accept<R>(v: Visitor<R>) { return v.num(this); } }
class Add { constructor(public l: Expr, public r: Expr) {}
  accept<R>(v: Visitor<R>) { return v.add(this); } }

Watch out

Using it when new classes are added often: every new class forces a change to every visitor.

Interpreter

Also called grammar, expression tree

What
Intent: represent the grammar of a small language as classes and evaluate sentences by walking the tree.
Why
When: you have a simple, stable mini-language such as filter rules, feature flags or formulas.
Example
A rule like country = "PK" and plan = "pro" becomes an And node holding two Equals nodes, each with an evaluate(user) method.

Watch out

Growing it into a full language by hand; use a parser generator once the grammar gets serious.

05 · Behavioural patterns: varying behaviour5 / 9