Handbooks / OOP & Design Patterns / Chapter 5
Behavioral Patterns
7 pages · ~5 min✓ Reviewed
Builds on Structural Patterns.
Behavioral Patterns: The Mental Model
1. What They Are
- Govern how objects communicate and share responsibility
- Move variation (algorithm, reaction, request) out of the caller, behind an interface
| Pattern | Intent |
|---|---|
| Strategy | Swap an algorithm |
| Observer | Notify dependents of a change |
| Command | Turn a request into an object |
| Iterator | Traverse without exposing structure |
| State | Behavior changes with internal state |
| Template Method | Fixed skeleton, subclass fills steps |
| Chain of Responsibility | Pass request along handlers until one handles it |
2. Inheritance or Composition
| Template Method | The other six | |
|---|---|---|
| Reuse via | Inheritance | Composition |
| Bound | Compile-time, subclass | Runtime, swappable object |
| Patterns | Template Method | Strategy, State, Command, Observer, Chain |
3. The Smell
- Long if/else or
switchon type - A flag or state that picks behavior
- Caller hard-wired to one concrete receiver
Remember
Spot the variation, then hide it behind an interface.
Strategy, State and Template Method
1. Strategy and State
- Strategy: client picks one algorithm object behind one interface
- Context delegates; strategies never know each other
- e.g. sort comparator, payment method, compression
- State: context holds a current state and delegates
- States replace the context's state themselves
- e.g. TCP Open/Closed, Order Pending/Shipped
| Strategy | State | |
|---|---|---|
| Class diagram | identical | identical |
| Chosen by | client | the object itself |
| Over time | stays fixed | changes automatically |
| Know each other | no | yes |
2. Template Method
- Base class: a final method is the skeleton
- It calls abstract steps and optional hooks
- Subclasses override steps only
- Hollywood principle: the base calls you, you don't call it
python
class Parser(ABC): def parse(self): # skeleton, not overridden self.open() data = self.extract() self.close() return data
open and extract are abstract steps
3. Template Method vs Strategy
| Template Method | Strategy | |
|---|---|---|
| Varies | parts of one algorithm | the whole algorithm |
| Mechanism | inheritance | composition |
| Bound | compile time | runtime |
Template Method trap
A rigid, deep inheritance hierarchy. Need runtime swapping? Use Strategy.
Observer, Command and Mediator-Free Messaging
1. Observer
- Subject keeps a list of observers
subscribe/unsubscribe/notify()on change- One-to-many, loosely coupled
- DOM
addEventListener, Reduxsubscribe
javascript
const off = store.subscribe(render) // later off()
subscribe returns the unsubscribe function
- Forgotten unsubscribe: lapsed listener, memory leak
- Notification order is not guaranteed
- Cascading updates can loop
2. Command
- Request as an object with
execute()andundo() - Holds the receiver and its arguments
- Enables queues, logging, macros, undo/redo, retry
- Examples: editor actions, job queue
3. Telling Them Apart
| Observer | Command | Strategy | |
|---|---|---|---|
| Says | something happened | do this | how to do the job |
| Goes to | many unknown listeners | one receiver | its context |
| Receiver | no | yes | no |
| Stored, replayed | no | yes, often undoable | no |
Remember
Observer = announce. Command = order. Strategy = method.
Iterator and Chain of Responsibility
1. Iterator
- Iterator = sequential access, structure stays hidden
next/hasNext, Python__iter__/__next__, JSSymbol.iterator, generators- One cursor per iterator, so independent traversals
- Modify while iterating: Java
ConcurrentModificationException, or skipped items - Exhausted iterator cannot be reused: ask for a new one
2. Chain of Responsibility
- Each handler processes the request or passes it on
- Sender does not know which handler acts
- Examples: middleware, logging levels, support escalation
| Classic | Middleware | |
|---|---|---|
| Who acts | First handler that handles it, then stop | Every handler acts, then next() |
Falls off the end
A request can go unhandled. Add a default terminal handler
3. Chain vs Command
| Chain | Command | |
|---|---|---|
| Intent | Routes one request to whichever handler can take it | Packages a request for a known receiver |
- They combine well: commands passed through a handler chain
Choosing the Right Pattern
1. Decision Rules
| Question | Pattern |
|---|---|
| Swappable algorithm? | Strategy |
| Behavior follows lifecycle status? | State |
| Fixed steps, variable parts? | Template Method |
| Many parties react to a change? | Observer |
| Undo, queue or log of actions? | Command |
| Unknown handler picks it up? | Chain of Responsibility |
| Hide traversal? | Iterator |
2. Look-Alikes
| Pair | Difference |
|---|---|
| Strategy / State | Client picks Strategy; State switches itself |
| Strategy / Template | Composition (runtime) vs inheritance (compile time) |
| Command / Strategy | Command has a receiver, often undo; Strategy has neither |
| Observer / Chain | Observer: all notified; Chain: first handler wins |
3. Mistakes and Trade-off
- State for two states: a boolean suffices
- Strategy for an algorithm that never changes
- Observer where a direct call is clearer
- Template Method with a deep inheritance chain
Variation must be real
Every pattern adds classes and indirection. Apply it at the second or third variant.
Behavioral Patterns Cheat Sheet
1. The Seven at a Glance
| Pattern | Core idea | Example |
|---|---|---|
| Strategy | swap algorithm at runtime; client-chosen; composition | sort comparator |
| State | behavior follows internal state; objects drive transitions | order lifecycle |
| Template Method | base-class skeleton; subclasses fill steps; inheritance | framework lifecycle hooks |
| Observer | one-to-many notify | event listeners |
| Command | request as object; execute/undo; queue, replay | editor undo stack |
| Iterator | traverse without exposing structure; one cursor per traversal | for-each loop |
| Chain | handlers pass along until one handles | middleware |
2. Telling Look-alikes Apart
| Pair | Difference |
|---|---|
| Strategy / State | client switches vs state switches itself |
| Strategy / Template | composition vs inheritance |
| Command / Strategy | Command has a receiver and undo |
| Observer / Chain | all notified vs first handler wins |
3. Traps
- Observer: forgotten unsubscribe leaks memory
- Chain: add a default terminal handler
- Template Method: deep hierarchies get rigid
Rule of thumb
Add a pattern at the second or third real variant, not before.
Check yourself
Locked.press() sets door.state = Unlocked() once the code is right. Strategy or State?
- State: the state object swaps itself on the context.
- In Strategy the client picks the object and it stays fixed.
A widget subscribes to a store when it opens and never unsubscribes. It is then closed. What goes wrong?
- The store still holds the widget, so it is never freed and still gets notified.
- This is the lapsed listener leak; unsubscribe on close.
What does this print?
[1, 2, 3]then[].- The first call exhausts the iterator; call
iter()again for a fresh cursor.
it = iter([1, 2, 3]) print(list(it)) print(list(it))