Chain of Responsibility
Learning Objectives
Pass a request along a chain of handlers until one handles it (or all process it).
Build middleware and validation pipelines with clear ordering and termination.
Prerequisites: Modules 36, 40.
Intuition and Definition
Intent: avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle it; chain the receivers and pass the request along until handled.
Two common variants:
Classic: the first handler that *can* handle the request stops the chain (approval levels).
Pipeline / middleware: every handler processes the request and decides whether to continue (authentication → logging → rate limit → handler).
Why It Matters
Request processing often consists of independent checks whose order and composition change. A chain makes each check a small, testable unit.
Syntax and Language Notes
// Express/Koa-style middleware: (req, next) => ...
type Middleware = (req: Req, next: () => Res) => Res;Complete Example
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
struct Request { std::string user; std::string service; int securityLevel; bool sessionActive; };
using Next = std::function<std::string(const Request&)>;
using Middleware = std::function<std::string(const Request&, const Next&)>;
class Pipeline {
std::vector<Middleware> mws_;
std::function<std::string(const Request&)> final_;
public:
explicit Pipeline(std::function<std::string(const Request&)> handler) : final_(std::move(handler)) {}
Pipeline& use(Middleware m) { mws_.push_back(std::move(m)); return *this; }
std::string handle(const Request& r) const { return step(0, r); }
private:
std::string step(size_t i, const Request& r) const {
if (i == mws_.size()) return final_(r);
return mws_[i](r, [this, i](const Request& rr) { return step(i + 1, rr); });
}
};
int main() {
Pipeline p([](const Request& r) { return "OK: executed " + r.service; });
p.use([](const Request& r, const Next& next) { // logging: always continues
std::cout << " log: " << r.user << " -> " << r.service << "\n";
return next(r);
…log: tester -> read-dtc
OK: executed read-dtc
log: tester -> write-vin
DENIED: security level 2 required
log: tester -> write-vin
OK: executed write-vin
log: anonymous -> read-dtc
DENIED: no active sessionStep-by-Step Walkthrough
Each middleware receives the request and a next function; it can stop by returning a response or continue by calling next.
Logging always continues; the session and security checks can short-circuit.
The order is explicit in use(...) calls: logging runs even for denied requests because it comes first.
Common Mistakes
Unclear termination — requests that fall off the end of the chain unhandled without an error.
Order-dependent bugs — e.g. caching before authorization.
Handlers that mutate shared request state in surprising ways.
Long chains that are hard to debug — log which handler stopped the request.
Trade-offs and Alternatives
Chain of Responsibility decouples senders from handlers and makes processing steps composable, at the cost of less obvious control flow. Decorator is structurally similar (wrapping) but each decorator always delegates; a chain handler may stop.
Real-World Use
Web middleware (ASP.NET Core, Express, Django), exception handling (catch blocks up the stack), event bubbling in UI toolkits, approval workflows (manager → director → CFO), diagnostic request routing in gateways.
Practice
Q1. (Easy) What two choices does each middleware have?
Answer: Handle/stop the request (return a response) or pass it on by calling next.
Q2. (Medium) Why put logging first in the chain?
Answer: So every request is logged, including those rejected by later checks.
Q3. (Advanced) How would you test a single middleware in isolation?
Answer: Call it directly with a request and a fake next that records whether it was called (and returns a sentinel), then assert on the returned response and whether next was invoked.
Interview Question
Q1. "Chain of Responsibility vs Decorator?" Answer: Both form a chain of objects with the same interface. A decorator always forwards to the wrapped object and adds behaviour around it; the chain is about enrichment. A chain-of-responsibility handler *decides* whether to handle the request and may stop propagation; the chain is about finding the right handler or applying gatekeeping steps. Middleware frameworks blend the two.
Quick Quiz
Q1. In the classic variant, how many handlers handle a request?
Answer: One — the first that can.
Q2. Name a language feature that behaves like a chain.
Answer: Exception propagation through enclosing catch handlers (or DOM event bubbling).
Q3. What must every chain define?
Answer: What happens when no handler handles the request (termination behaviour).
Summary
Chain of Responsibility passes requests through handlers that may handle, stop or continue.
Make order and termination explicit; keep handlers small.
Related: Modules 36, 42.