Skip to content
aipatterns.dev

Category

Behavioral Patterns

Patterns that assign responsibilities and route requests between objects, so behavior can change without rewriting the code that triggers it.

What these patterns solve

Behavioral patterns are about who does what and who tells whom. They turn requests into objects, let behavior vary by situation, decouple senders from receivers and walk structures without exposing them. Many of them exist to replace a growing conditional or a tangle of direct calls.

Several of these patterns are a function value in disguise. Check that first-class functions, closures, iterators or your platform's events don't already cover the need.

Members

  • Chain of Responsibility: passes a request along a list of handlers until one handles it, as in HTTP middleware.
  • Command: wraps an action and its arguments in an object so it can be queued, logged, retried or undone.
  • Iterator: walks a collection without exposing how it is stored.
  • Mediator: routes communication between many components through one coordinator instead of letting them call each other.
  • Memento: captures an object's state as an opaque snapshot so it can be restored later without breaking encapsulation.
  • Observer: lets objects subscribe to events from another object and get notified when they occur.
  • State: moves state-specific behavior into separate objects so an object changes behavior when its state changes.
  • Strategy: puts interchangeable algorithms behind one interface and lets the caller pick one at runtime.
  • Template Method: fixes an algorithm's skeleton in a base class and lets subclasses fill in specific steps.
  • Visitor: adds operations over a fixed set of node types without editing those types.

How to choose within this category

  • A switch picks between algorithms: Strategy. A function parameter is often enough.
  • A switch on a status field appears in many methods, and the status changes over time: State. For a handful of states, a transition table or a discriminated union may be clearer.
  • The steps are fixed and only a few vary: Template Method. Passing the varying steps as functions avoids inheritance.
  • An action must be queued, retried, logged or undone: Command. Undo also needs Memento or an inverse operation.
  • Several handlers might process a request, in order: Chain of Responsibility.
  • One change must notify an unknown number of listeners: Observer. Use your platform's event emitter, channels or signals before writing your own.
  • Many components call each other directly: Mediator.
  • You need many operations over a stable set of types: Visitor. Exhaustive pattern matching over a sealed or union type replaces it in TypeScript, Rust, Kotlin and Java 21+. See Visitor and double dispatch.
  • You need to traverse a custom structure: implement your language's iteration protocol (Symbol.iterator, __iter__, Iterable, range-over-func in Go) rather than a separate Iterator class.

Prefer no pattern when

  • There are two branches and no third in sight. Keep the if.
  • The behavior is one stateless function. Pass the function.
  • Only one listener or handler will ever exist. Call it directly.

See also

Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.