Design Patterns · Behavioral Patterns
Chain of Responsibility
Pass a request through interchangeable handlers. Let each handler decide whether to handle, continue, or stop.
This lesson follows Proxy and starts the behavioral patterns. These patterns describe how objects divide work and communicate.
Chain of Responsibility sends a request along a chain of handlers. Each handler decides what to do and whether to pass it onward. The sender does not select the final receiver. The cost is making order and unhandled requests explicit.
Route without naming the final receiver
A support inbox receives password questions, invoices, and bug reports. FAQ handles passwords. Billing handles invoices. Engineering handles bugs. The inbox sends each ticket to the chain's entry point.
type Ticket = { kind: string };
interface Handler { handle(ticket: Ticket): string }
class SupportHandler implements Handler {
constructor(
private accepts: string,
private answer: string,
private next?: Handler,
) {}
handle(ticket: Ticket): string {
if (ticket.kind === this.accepts) return this.answer;
return this.next?.handle(ticket) ?? "No handler available";
}
}
const engineering = new SupportHandler("bug", "Engineering");
const billing = new SupportHandler("invoice", "Billing", engineering);
const inbox: Handler = new SupportHandler("password", "FAQ", billing);
inbox.handle({ kind: "invoice" }); // Billing
Try it
Send a ticket through the chain
FAQ handles the ticket
Billing and Engineering are not called
FAQ passes
Billing handles
Engineering is not called
FAQ passes; Billing passes
Engineering handles
all handlers pass
No handler available
Choose the stopping rule
The classic form stops at the first handler that accepts a request. Another form runs a sequence of checks: authenticate, validate, and execute. A check continues on success and stops on rejection. Both use a handler-controlled continuation. State which form the application needs.
Step through
Follow a validation pipeline
Authenticate
credentials invalid → reject
credentials valid → next handler
A rejection stops later work. A success does not yet mean the request completed.
Validate
payload invalid → reject
payload valid → next handler
This handler depends on authenticated context if the preceding stage supplies it. Reordering changes the contract.
Execute
business operation runs once after required checks
A terminal handler gives the chain a defined completion result.
Order is part of the behavior
A cache hit before an authorization check can return a response without applying the required access rule. A handler that forgets to delegate can silently suppress all later work. Validate the configured chain and prevent cycles.
Map the roles
| Role | In this example | Job |
|---|---|---|
| Handler | Handler | Declares the interface common to all handlers. Usually one method for handling requests, sometimes also one for setting the next handler. |
| Base handler (optional) | SupportHandler, which stores next | Holds the reference to the next handler and the shared code, such as forwarding the request when a next handler exists |
| Concrete handlers | the FAQ, Billing, and Engineering instances | Contain the real handling code. Each decides whether to process a request and whether to pass it along. Usually immutable, with all data supplied through the constructor. |
| Client | the support inbox | Builds the chain once or dynamically. Can send a request to any handler in the chain, not only the first one. |
Check yourself
Must a request always enter the chain at the first handler?
Correct. Starting mid-chain is allowed. For example, a GUI help request starts at the clicked element.
Not quite. The chain still works from any entry point. Earlier handlers are simply skipped.
Reach for it when who handles a request is decided at runtime
- your program must process different kinds of requests in different ways, but the exact request types and their order are unknown in advance. The chain asks each handler in turn whether it can process the request, so every handler gets a chance.
- several handlers must run in a particular order. However you link them, every request passes through them in exactly that order.
- the set of handlers and their order must change at runtime. With a setter for the next-handler field, you can insert, remove, or reorder handlers dynamically.
Check yourself
An API needs authentication, then rate limiting, then validation, always in that order. Which Chain of Responsibility use is this?
Not quite. Nothing here changes at runtime. The point is the guaranteed order.
Correct. Every request passes through the linked checks in exactly that order.
Implement it in six steps
Refactor existing code toward the pattern in this order:
- Declare the handler interface and the signature of its request-handling method.
- Decide how the client passes request data into the method. The most flexible way is to turn the request into an object and pass it as the only argument.
- To remove duplicated boilerplate, create an abstract base handler with a field for the next handler. Make it immutable, or add a setter if the chain must change at runtime. Its default handling can forward the request to the next handler when one exists.
- Create the concrete handlers one by one. Each must make two decisions when it receives a request: whether to process it, and whether to pass it along the chain.
- Let the client assemble the chain itself, or receive a pre-built chain. In the second case, use factory classes that build chains from configuration or environment settings.
- Prepare the client for the chain's dynamic nature: the chain may have a single link, some requests may never reach the end, and others may reach the end unhandled.
Check yourself
Step 4 says each handler makes two decisions. What are they?
Not quite. The chain's links decide order. Handlers decide processing and forwarding.
Correct. The two decisions are independent. A validation handler processes the request and also passes it on.
Name what it costs
| You gain | You pay |
|---|---|
| You control the order in which requests are handled | Some requests may end up unhandled |
| Classes that send requests are decoupled from classes that perform them (single responsibility) | Order creates hidden dependencies between stages |
| New handlers without breaking existing code (open/closed) | Debugging needs a trace of which handlers ran |
Check yourself
A cache handler sits before the authorization handler. What can go wrong?
Correct. Order is behavior. Put access checks before anything that can answer early.
Not quite. Order matters whenever a handler can stop the chain.
Do not confuse it with its neighbors
| Pattern | How it relates |
|---|---|
| Command, Mediator, Observer | All four connect senders and receivers differently. Chain passes a request along a dynamic chain of possible receivers until one handles it. Command creates a one-way connection from sender to receiver. Mediator removes direct connections and routes everything through a mediator. Observer lets receivers subscribe and unsubscribe dynamically. |
| Composite | Often used together. A leaf component can pass a request up through its parent containers to the root of the tree. |
| Command | Handlers can be implemented as commands, running different operations on the same request context. Alternatively, the request itself can be a command, run against a chain of different contexts. |
| Decorator | Very similar class structures, both built on recursive composition. Chain handlers act independently and can stop the request at any point. Decorators extend an object's behavior and cannot break the flow. |
Check yourself
Receivers sign up and drop out at runtime, and every current receiver gets each event. Which pattern is that, rather than a chain?
Correct. Dynamic subscription to every event is Observer. A chain stops at the handler that takes the request.
Not quite. A chain usually ends when a handler deals with the request. It does not broadcast.
Retrieve and apply
Check yourself
A ticket matches no handler. What must the chain contract define?
Correct. The pattern does not guarantee a receiver exists. Unhandled requests need a deliberate product behavior.
Not quite. All handlers may decline. The sender still needs a meaningful result.
Sketch a help request climbing from a button to its panel and window. Mark where it can stop. Continue to Command to give a request its own object.
Source: Alexander Shvets, Dive Into Design Patterns (深入设计模式), Chinese edition v2021-1.25. Chain of Responsibility, printed pages 235–252. Explanations, examples, and exercises are adapted for this course.