Skip to main content

Chain of Responsibility

complexitypopularity

Pass a request along a line of handler objects; each one decides whether to handle it, pass it on, or stop the chain dead.

The problem

You are building an ordering system, and orders should only come from authenticated users. Then admins need full access, so that is a permissions check. Then a colleague points out that raw request data should be sanitized. Then someone notices the login endpoint is a brute-force buffet, so you add IP throttling. Then someone wants caching, which is another check in front of everything.

The checks have to run in order, and each one has veto power - failed authentication makes every later check pointless. So they pile up in one method.

Now protect a second endpoint that needs three of those five checks in a different order. You cannot. You copy them, and from that moment the two copies drift.

The solution

Turn each behavior into a standalone object - a handler - with a single method that takes the request. Then link the handlers: every handler holds a reference to the next one. A request enters the chain and travels along it, and at each link the handler makes two independent decisions: do I process this, and do I pass it on.

That second decision is the interesting one. A handler is allowed to stop the chain, which is what makes CoR more than a fancy loop. In the canonical GUI variant, the first element capable of handling the event consumes it and nothing downstream ever hears about it.

The one hard requirement: all handlers implement the same interface, and each knows nothing about the next link beyond that interface. That is what lets you compose chains at runtime out of whatever handlers the situation calls for.

ApplicationButton (Cancel)PanelDialoggetComponentAtMouseCoords()1showHelp()2tooltipText == null?3container.showHelp()4modalHelpText != null5show modal6
  1. 1The user hits F1. The app finds whatever sits under the cursor - here, the Cancel button - and sends the help request there.
  2. 2The request enters the chain at the deepest element. The app has no idea how far it will travel, and does not need to.
  3. 3Decision one: can I handle this? Cancel was never given tooltip text, so the answer is no.
  4. 4Decision two: do I forward? Yes - the button hands off to its container and drops out of the story entirely.
  5. 5The panel does have modal help text, so it handles the request its own way: a modal window instead of a tooltip.
  6. 6And here it stops. The dialog never hears about the request - a handler that processes a request is free to end the chain.

Structure

The GUI example is a nice reminder that a chain can be lifted straight out of an object tree: a component's chain is just its containment path to the root. Note the optional base handler - the class that owns the next-link field and the default "forward it" behavior so concrete handlers do not each reinvent it.

«interface»ComponentWithContextualHelpshowHelp()HANDLER«abstract»ComponenttooltipText: stringcontainer: ContainershowHelp()BASE«abstract»Containerchildren: Component[]add(child)BASEButton(inherits showHelp)CONCRETEPanelmodalHelpText: stringshowHelp()CONCRETEDialogwikiPageURL: stringshowHelp()CONCRETE
implementsextends

Code

Same example three ways: contextual help bubbling from a widget up through its containers.

// One method that knows every check, in a fixed order, forever.
class OrderSystem is
method handle(request) is
if (!authenticate(request.user, request.password))
return "bad credentials"
if (!hasPermission(request.user))
return "forbidden"
if (!sanitize(request.data))
return "malformed request"
if (bruteForceDetected(request.ip))
return "too many attempts"
if (cache.has(request))
return cache.get(request)
 
// ...and the real work, buried under the checks.
return createOrder(request)
 
// Want the same checks on the reporting endpoint - but only three of
// them, in a different order? Copy, paste, and pray they stay in sync.
// Each check is a standalone handler with one job.
class AuthCheck extends Handler is
method handle(request) is
if (!authenticate(request.user, request.password))
return "bad credentials" // stop the chain here
return next(request) // otherwise, keep going
 
// The chain is data, assembled per endpoint, reordered at will.
orders = AuthCheck().setNext(Permissions()).setNext(Throttle()).setNext(Cache())
reports = AuthCheck().setNext(Cache())
 
orders.handle(request)
// New check next month? A new class and one setNext() call.
// The handler interface: one method, which is the entire contract
// each link needs from the next one.
interface ComponentWithContextualHelp is
method showHelp()
 
 
// The base class carries the boilerplate every handler would
// otherwise repeat.
abstract class Component implements ComponentWithContextualHelp is
field tooltipText: string
 
// The next link in the chain is just the enclosing container.
protected field container: Container
 
// Default behavior: handle it if we can, otherwise forward.
method showHelp() is
if (tooltipText != null)
// Show tooltip.
else
container.showHelp()
 
 
// Containers hold children and, in doing so, build the chain.
abstract class Container extends Component is
protected field children: array of Component
 
method add(child) is
children.add(child)
child.container = this
 
 
// A leaf that is perfectly happy with the inherited behavior.
class Button extends Component is
// ...
 
// A handler with its own idea of what "help" means. Calling super
// keeps the request moving; not calling it would stop the chain.
class Panel extends Container is
field modalHelpText: string
 
method showHelp() is
if (modalHelpText != null)
// Show a modal window with the help text.
else
super.showHelp()
 
// The last link. Whatever reaches here is the chain's final chance.
class Dialog extends Container is
field wikiPageURL: string
 
method showHelp() is
if (wikiPageURL != null)
// Open the wiki help page.
else
super.showHelp()
 
 
// The client assembles the chain and then forgets about it.
class Application is
method createUI() is
dialog = new Dialog("Budget Reports")
dialog.wikiPageURL = "http://..."
panel = new Panel(0, 0, 400, 800)
panel.modalHelpText = "This panel does..."
ok = new Button(250, 760, 50, 20, "OK")
ok.tooltipText = "This is an OK button that..."
cancel = new Button(320, 760, 50, 20, "Cancel")
panel.add(ok)
panel.add(cancel)
dialog.add(panel)
 
// The request enters wherever the mouse happens to be.
method onF1KeyPress() is
component = this.getComponentAtMouseCoords()
component.showHelp()

When to use it

  • Your program handles several kinds of request in several ways, and you do not know the types or their order up front.
  • Order of execution matters and should be explicit. A chain makes the order literal data rather than the shape of an if-else ladder.
  • The set of handlers, or their order, must change at runtime. Give the next-link field a setter and you can insert, remove, and reshuffle handlers on the fly.

Pitfalls

  • Requests can vanish. Nothing guarantees a taker, so a request may fall off the end of the chain with no answer. Decide up front whether that is acceptable or whether the last link should be a catch-all.
  • Debugging by flashlight. A bug becomes "which of these eleven handlers ate my request", and the answer is only visible at runtime. Logging at each hop is cheap insurance.
  • Chains of one. The client should survive a single-link chain, a request handled at the first hop, and a request handled at none of them. All three are normal.

Don't confuse it with

  • Decorator. The class diagrams are nearly twins - both use recursive composition to push execution through a series of objects. The difference is what each is permitted to do. A decorator must keep the request flowing and stay behaviorally compatible with the interface it wraps; a CoR handler may run something entirely unrelated and stop the chain whenever it likes.
  • Command. A CoR handler can be implemented as a Command, and the request travelling the chain can itself be a Command - the patterns compose happily. Their intents differ: CoR is about finding a receiver, Command is about packaging a request.
  • Composite. They pair naturally. A leaf that receives a request can pass it up through its parents to the root, which is exactly the contextual-help example.
  • Middleware stacks. Express, Rack and friends are CoR with a job title: each middleware handles the request, calls next(), or short-circuits with a response.

Check yourself

Question 1 of 5

When a handler receives a request, how many decisions does it make?