Chain of Responsibility
The expense code is one long if/elif: if amount <= 1000 lead approves, elif amount <= 10000 manager, elif ... The rules for who approves are hard-coded in one place, and changing the order or adding a level means editing it.
Give every handler a link to the next one.
More than one object may handle a request and you do not know which
The picture from life
A customer-care call. First the front-line agent tries; if the issue is too big it goes to the senior agent, and if still too big, to the manager. The caller does not choose who answers. The request simply travels up the chain until someone can handle it.
The problem
What goes wrong when you do not use it.
The expense code is one long if/elif: if amount <= 1000 lead approves, elif amount <= 10000 manager, elif ... The rules for who approves are hard-coded in one place, and changing the order or adding a level means editing it.
- calls / uses
- More than one object may handle a request and you do not know which
- The set or order of handlers changes
- You want to add or remove steps like middleware
The idea
How the pattern fixes it.
Give every handler a link to the next one. A handler either deals with the request or passes it on. The sender only knows the first handler.
- calls / uses
Who is who
- Approver (handler)
- Has a limit and a link to the next approver.
- approve(amount)
- Handle it if within limit, otherwise forward.
- Client
- Builds the chain and calls the first handler.
Build it in steps
- 1Define a handler with a reference to the next.
- 2In handle(): decide, else call next.handle().
- 3Link the handlers in the order you want.
- 4Send requests only to the first one.
Watch it run
Step through the calls. Tap an arrow to jump to it.
The client sends the request to the first approver only.
approve(5000)The code
Python and Java. Try writing the classes yourself from the diagram first.
class Approver:
def __init__(self, name, limit, next_approver=None):
self.name = name
self.limit = limit
self.next_approver = next_approver
def approve(self, amount):
if amount <= self.limit:
return f"{self.name} approves {amount}"
if self.next_approver is None:
return f"nobody can approve {amount}"
return self.next_approver.approve(amount) # pass it on
def demo():
chain = Approver("lead", 1000, Approver("manager", 10000, Approver("director", 100000)))
return [chain.approve(500), chain.approve(5000), chain.approve(50000), chain.approve(500000)]What you gain
- Sender and handler are decoupled
- Reorder or extend the chain freely
What it costs
- A request may fall off the end unhandled
- Hard to see at a glance who handled it
You have already met it in
- Servlet filters and web middleware (auth, logging, compression)
- Java's try / catch blocks passing exceptions up
- DOM event bubbling
Often mixed up with
Make it stick
Build a support-ticket chain: Bot (FAQ), Agent, Manager. Each handles only some severities and passes the rest.