Chain of Responsibility Pattern

Chain of Responsibility Pattern

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.

Intent

The Chain of Responsibility decouples request senders from receivers by threading the request through a sequence of handler objects. Each handler in the chain either handles the request or passes it to the next handler. The sender never knows which object will ultimately respond — or whether any will. This allows the handler set to be assembled and altered at runtime without touching the sender.

When NOT to Use

  • Every request must be guaranteed to be handled — a chain can silently swallow requests that reach the end without a handler
  • The chain length is unbounded and performance is critical — requests traverse every preceding handler before reaching the right one
  • Simple if/else or switch dispatch is clearer — a chain adds indirection without benefit when the handler selection logic is trivial

When to Use

  • The request sender should not need to know which handler will process it
  • More than one handler may be a candidate and the appropriate handler should be determined at runtime
  • The set of handlers changes at runtime (middleware pipelines, approval workflows, event filters)

How It Works

Participants:

  • Handler — abstract base with an optional successor reference and a handle(request) method
  • ConcreteHandler — either processes the request (when its condition is met) or calls this.successor.handle(request) to forward it
  • Client — builds the chain by wiring handlers, then submits requests to the first handler

The chain terminates when a ConcreteHandler handles the request or the end of the chain is reached. Handlers are unaware of the full chain; each only knows its immediate successor.

Sequence Diagram

sequenceDiagram
    participant Client
    participant HandlerA
    participant HandlerB
    participant HandlerC

    Client->>HandlerA: handle(request)
    Note right of HandlerA: Can I handle this?
    HandlerA->>HandlerA: check condition
    HandlerA-->>HandlerB: pass to next

    Note right of HandlerB: Can I handle this?
    HandlerB->>HandlerB: check condition
    HandlerB-->>HandlerC: pass to next

    Note right of HandlerC: Yes -- handle it
    HandlerC->>HandlerC: process request
    HandlerC-->>Client: response

    Note over HandlerA,HandlerC: Each handler either processes the request or forwards it along the chain

TypeScript Example

// Chain of Responsibility — TypeScript
// Source: GoF Design Patterns, p.223
 
abstract class Handler {
  protected successor: Handler | null = null;
  setNext(h: Handler): Handler { this.successor = h; return h; }
  abstract handle(request: number): void;
}
 
class LowPriorityHandler extends Handler {
  handle(request: number) {
    if (request < 10) { console.log(`Low handled: ${request}`); }
    else { this.successor?.handle(request); }
  }
}
 
class HighPriorityHandler extends Handler {
  handle(request: number) {
    if (request >= 10) { console.log(`High handled: ${request}`); }
    else { this.successor?.handle(request); }
  }
}
 
// Chain: low → high
const low = new LowPriorityHandler();
low.setNext(new HighPriorityHandler());
low.handle(5);   // Low handled: 5
low.handle(15);  // High handled: 15

Java Example

// Chain of Responsibility — Java
// Source: GoF Design Patterns, p.223
 
abstract class Handler {
    protected Handler successor;
    public void setSuccessor(Handler s) { this.successor = s; }
    public abstract void handleRequest(int request);
}
 
class LowPriorityHandler extends Handler {
    public void handleRequest(int request) {
        if (request < 10) { System.out.println("Low handled: " + request); }
        else if (successor != null) { successor.handleRequest(request); }
    }
}
 
class HighPriorityHandler extends Handler {
    public void handleRequest(int request) {
        if (request >= 10) { System.out.println("High handled: " + request); }
        else if (successor != null) { successor.handleRequest(request); }
    }
}
 
// Client: low.setSuccessor(new HighPriorityHandler()); low.handleRequest(5);
PatternRelationship
Decorator-PatternDecorator passes through all wrappers unconditionally; CoR stops at the first handler that claims the request
Command-PatternCommand encapsulates a request as an object; CoR dispatches that request through a handler chain
Composite-PatternComposite forms a tree; CoR forms a linear chain — both propagate calls through a structure

Sources

  • Gamma, Helm, Johnson, Vlissides — Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1994, p.223

Notes that link here: GoF-Patterns-MOC