Home Design Patterns Chain of Responsibility
Chain of Responsibility
Behavioral Gang of Four“Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.”
Gamma, Helm, Johnson & Vlissides, p. 223Purpose
A Chain of Responsibility lets a request be offered to several candidate handlers in turn, without the sender knowing how many there are or which one will take it. Each handler looks at the request, decides whether it is theirs, and either deals with it or hands it on.
The thing being decoupled is the choice of receiver. Normally the sender has to know who can do the work, which means the sender contains the knowledge of every possible handler. A chain moves that knowledge out of the sender and into the arrangement of the chain itself, which is usually built at startup and can be different on every installation.
Design
Every handler holds a reference to the next handler and implements the same request-handling operation. The typical implementation is two-part: a test for whether this handler applies, and the work it does if it does apply. If the test fails, the handler forwards to its successor. If there is no successor, the request goes unanswered, and deciding what that means is part of designing the chain.
successor reference points at the abstract
Handler, not at a concrete class. That is what lets the chain be reordered, cut
short or extended by configuration without any handler knowing.Two variations matter. In the strict version exactly one handler acts and the request stops there. In the loose version a handler may act and forward, so several handlers contribute. The second is more useful in practice and less honest about its name, because at that point you have a pipeline rather than a search for a responsible party.
Example
Autobot Robotics arm controllers raise numbered faults: a drive over-current, a lost encoder count, a safety scanner trip, a tool that failed to latch. The first version of the firmware had a single fault routine with a switch statement covering every code, and that routine ran inside the real-time control loop. Every new customer requirement added another case to it.
The requirements were not the same from site to site, which is what actually broke the design. One customer wanted a recoverable drive fault retried twice before anyone was told. A customer in a regulated plant wanted every fault written to an audit log before anything else happened. A third wanted the line paused but not stopped, because stopping the oven cost them an hour. All three behaviors were in the same switch statement behind configuration flags, and the flags interacted.
Replacing it with a chain let each of those be a small handler class:
AuditHandler, DriveRetryHandler,
PauseLineHandler, and a final HaltHandler that takes
anything still unclaimed. The order and the membership come from the site
configuration file that is read at boot. The control loop calls one method on the
head of the chain and knows nothing else.
class Client
{
public void start() {
FaultHandler chain = new AuditHandler();
chain.setNext(new DriveRetryHandler())
.setNext(new PauseLineHandler())
.setNext(new HaltHandler());
chain.handle(new Fault(1204));
}
}
abstract class FaultHandler
{
protected FaultHandler next;
public FaultHandler setNext(FaultHandler h) { next = h; return h; }
public void handle(Fault f) {
if (canHandle(f)) {
act(f);
} else if (next != null) {
next.handle(f);
}
}
protected abstract bool canHandle(Fault f);
protected abstract void act(Fault f);
}
class DriveRetryHandler : FaultHandler
{
protected bool canHandle(Fault f) { return f.isRecoverable(); }
protected void act(Fault f) {
// clear the drive fault and re-issue the move
}
}
class PauseLineHandler : FaultHandler { } // same shape
class HaltHandler : FaultHandler { } // last link, canHandle is always true
handle(): test, act,
otherwise forward. Everything else is bookkeeping. Note that
HaltHandler exists purely so the chain cannot run off the
end.Seen in the Wild
Shared interrupt lines in the Linux kernel are a real chain: several drivers
register a handler for the same IRQ, the kernel calls each in turn, and a handler
returns IRQ_NONE to say the interrupt was not its device. Event
propagation in the browser DOM is the same idea in the other direction, with an
event bubbling from the target up through its ancestors until a listener calls
stopPropagation. In Windows desktop programming, MFC routes command
messages along a fixed chain of view, document, frame and application through
CCmdTarget::OnCmdMsg. Servlet filter chains and ASP.NET Core
middleware have exactly this shape too, although in those the usual case is that
every link contributes rather than one link claiming the request.
Comparisons
- Decorator is the same structure with the opposite expectation. Every decorator in a stack contributes; in a chain, one handler is supposed to take the request and the rest never see it.
- Command pairs with it naturally. Make the request an object and the chain becomes a general dispatcher rather than something hard-wired to one kind of message.
- Composite often supplies the chain for free: if the objects already form a tree, the parent link is the successor link, and requests travel up the structure you already have.
- Mediator also stops senders knowing about receivers, but does it with one hub instead of a line. A chain distributes the routing decision; a Mediator centralizes it.
- Observer broadcasts to everyone who registered. A chain stops at the first taker. If you catch yourself wanting both, you want Observer.
Why It Exists
My take
This pattern exists because dispatch tables want to be editable at runtime. The alternative to a chain is a conditional in the sender, and a conditional is a decision compiled into the program. That is fine when the set of handlers is fixed and known. It stops being fine the moment the right handler depends on which customer bought the machine, which is a fact you learn after shipping.
What it really buys you is the ability to insert a handler in the middle without touching either neighbor. In the fault example, adding the audit requirement was one new class and one line of configuration, and none of the existing handlers were recompiled or retested. On an embedded controller where a firmware release is a customer site visit, that difference is the whole argument.
I would also say the name oversells it. There is no responsibility being transferred and nothing enforces that anyone is responsible at all. What you have is a linked list of predicates with an early exit. Knowing that is useful, because it tells you when not to reach for it: if the chain always runs to the end, or if the same handler always takes the request, you have paid for indirection you are not using and a switch statement would have been clearer and faster.
Criticisms
Nothing guarantees the request is handled. A chain assembled from configuration can be missing the one handler that mattered, and the symptom is silence: no error, no log line, just a request that quietly evaporated. The usual fix is a catch-all at the tail, which works, but notice that you have now reintroduced a default case and the thing you were avoiding was a switch statement with a default case.
Debugging is unpleasant for a specific reason: the chain does not exist in the source. Reading every handler class tells you nothing about the order they run in, because the order was decided somewhere else, probably in a config file, at startup. A stack trace shows you where the request ended up but not why it got past the four handlers before it. The chain is a runtime structure and you need a debugger or a log to see it.
There is also a subtler ordering hazard. When two handlers could both accept a request, the first one wins, and that is decided by chain order rather than by anything anyone wrote down. Handlers that were correct in isolation become wrong when someone reorders them, and that mistake is invisible in code review because the review sees one file and the bug lives in the arrangement.