Chain of Responsibility

Link handlers in a row and pass each request down the row until one takes it, so the sender never has to know which one that is.

Overview

The problem: the sender has to pick the receiver

An employee files an expense. Small amounts need a team lead's approval, larger ones a manager, the largest a director. The first version of the code puts that knowledge in the place that files the expense: an if on the amount that calls the right approver. Every new rule (a second currency, a travel category, a holiday freeze) is another branch in the same method, and every caller that submits expenses must carry the same branches.

  • up to 1,000 — Team lead — in the submit method
  • up to 10,000 — Manager — in the submit method
  • up to 100,000 — Director — in the submit method
  • above that — nobody; refuse — in the submit method

The sender knows every receiver and every rule. The Chain of Responsibility pattern moves each rule into the receiver it belongs to, and lines the receivers up.

The pattern

Chain of Responsibility is one of the 23 patterns in the 1994 Gang of Four book (Gamma, Helm, Johnson and Vlissides). Each handler holds a reference to the next handler. When a request arrives, the handler either deals with it or passes it on. The client builds the chain once and sends every request to the first link. It never learns which link answered.

  • Handler — Declares handle, keeps the link to the next handler, and passes on what it does not take — Approver
  • Concrete handler — Decides for one kind of request — TeamLead, Manager, Director
  • Client — Assembles the chain and sends requests to its head — the expense form

A runnable example

The base class does the passing, so a concrete handler only answers one question: can I take this? The set_next method returns the handler it was given, which lets the client write the chain as one line.

Adding a vice president for larger amounts is one new class and one more set_next. The client's call, head.handle(amount), does not change.

Two ways to run a chain

The Gang of Four version stops at the first handler that takes the request. A second form, common in web servers, lets every handler run and pass the request on. Both are chains; they answer different needs.

  • Who handles a request — Exactly one handler, or none — All of them, in order
  • A handler's choices — Take it and stop, or pass it on — Do its part, then pass it on; or stop early (a rejection)
  • Typical use — Approval levels, exception handlers, help lookups — Logging, authentication, compression, rate limits
  • Order matters because — The first handler that matches decides — Each step sees what earlier steps did

Where you have already used it

  • Express (Node.js) — Middleware functions registered with app.use — A middleware that does not end the request-response cycle must call next() to pass control on, or the request is left hanging
  • Jakarta Servlet — Filter objects joined by a FilterChain — doFilter invokes the next filter in the chain, or the resource at the end of it when the caller is the last filter
  • Python try statement — The except clauses — When an exception occurs the clauses are inspected in turn until one matches; if none does, the search continues in the surrounding code and up the call stack

The last row is the pattern in a language feature: the raise site does not know which except will catch, the clauses are tried in order, and an unmatched exception is passed to the enclosing chain.

Chain of Responsibility compared with its neighbors

  • Shape — A row of handlers, each linked to the next — Wrappers around one core object — One object chosen from a set
  • Who runs — The first match, or each link in turn — Every wrapper, always, inside out — Exactly the one chosen
  • Can a link stop the request — Yes — Not by design; a wrapper adds to the call — Not applicable
  • The question it answers — Who should deal with this — What extra behavior should surround this — Which algorithm should run

Middleware sits on the border between Chain and Decorator: it is linked like a chain and wraps like a decorator. Call it a chain when links can stop the request, which is the property that matters for authentication and rate limiting.

What you will be able to do

  • Spot a sender that knows too much about its receivers, and move each rule into a handler.
  • Draw the chain and write the base class that does the passing.
  • Say what happens when no handler takes a request, and defend the choice.
  • Tell the first-match chain from the pipeline, and name a real system for each.
  • Explain why order matters and how to build the chain so order is easy to read and test.

Interview questions

  • Show the smell Chain of Responsibility fixes, and the fix.
  • What happens when no handler takes the request?
  • How does a middleware pipeline pass the response back?
  • Order matters. How do you build the chain so it is easy to get right?
  • When is Chain of Responsibility the wrong choice?

Sign up free to read the full topic

More Low-Level Design topics

Start free on Cariara · Coding problems · System design studio