Skip to content

Chain of Responsibility Pattern

Intent

The Chain of Responsibility pattern avoids 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.

Problem

When you need to process requests in a flexible way, but you don't want the sender to be coupled to specific receivers:

  • Multiple objects may be able to handle a request
  • The handler isn't known in advance
  • The set of handlers should be specified dynamically
  • You want to issue a request to one of several objects without specifying the receiver explicitly

For example, in a help system, context-sensitive help depends on which part of the application has focus. The help request should be handled by the appropriate component, but the requester shouldn't need to know which component will handle it.

Solution

The Chain of Responsibility pattern suggests creating a chain of handler objects. Each handler:

  • Has a reference to the next handler in the chain
  • Decides whether to process the request or pass it to the next handler
  • Can process the request and/or pass it along the chain
  • Is independent and doesn't know about other handlers (except the next one)

The request travels through the chain until a handler processes it or the chain ends.

Structure

Chain Of Responsibility Pattern Diagram 0

Participants

  • Handler - Defines an interface for handling requests - Optionally implements the successor link - In our example: Handler abstract class
  • ConcreteHandler - Handles requests it is responsible for - Can access its successor - If it can handle the request, does so; otherwise forwards it - In our example: ConcreteHandlerA, ConcreteHandlerB, ConcreteHandlerC
  • Client - Initiates the request to a ConcreteHandler in the chain - Doesn't know which handler will process the request

Implementation

Handler Base Class

class Handler {
protected:
    std::shared_ptr<Handler> nextHandler_;

public:
    virtual ~Handler() = default;

    Handler* setNext(std::shared_ptr<Handler> handler) {
        this->nextHandler_ = handler;
        return handler.get();
    }

    virtual std::string handle(const std::string& request) {
        if (this->nextHandler_) {
            return this->nextHandler_->handle(request);
        }
        return "";
    }
};

Concrete Handlers

class ConcreteHandlerA : public Handler {
public:
    std::string handle(const std::string& request) override {
        if (request == "A") {
            return "ConcreteHandlerA: I'll handle the request 'A'.";
        } else {
            return Handler::handle(request);
        }
    }
};

class ConcreteHandlerB : public Handler {
public:
    std::string handle(const std::string& request) override {
        if (request == "B") {
            return "ConcreteHandlerB: I'll handle the request 'B'.";
        } else {
            return Handler::handle(request);
        }
    }
};

Usage Example

int main() {
    // Create handlers
    auto handlerA = std::make_shared<ConcreteHandlerA>();
    auto handlerB = std::make_shared<ConcreteHandlerB>();
    auto handlerC = std::make_shared<ConcreteHandlerC>();

    // Build the chain: A -> B -> C
    handlerA->setNext(handlerB);
    handlerB->setNext(handlerC);

    // Send requests
    std::cout << handlerA->handle("A") << std::endl;  // A handles
    std::cout << handlerA->handle("B") << std::endl;  // B handles
    std::cout << handlerA->handle("C") << std::endl;  // C handles
    std::cout << handlerA->handle("D") << std::endl;  // Not handled

    return 0;
}

Applicability

Use the Chain of Responsibility pattern when:

  • More than one object may handle a request - The handler isn't known a priori - Example: Event handling in GUI where parent widgets can handle events
  • You want to issue a request to one of several objects without specifying the receiver explicitly - Sender doesn't need to know which object handles the request - Example: Help system where context determines handler
  • The set of objects that can handle a request should be specified dynamically - Can build/modify the chain at runtime - Example: Middleware pipeline in web frameworks
  • You want to decouple sender and receiver - Sender doesn't need to know about receivers - Example: Logging with different severity levels

Consequences

Benefits

  1. Reduced coupling - Sender doesn't need to know which object handles the request - Objects only know about their successor - Easy to add/remove responsibilities

  2. Added flexibility in assigning responsibilities - Can change chain at runtime - Can add new handlers without modifying existing code - Can reorder handlers dynamically

  3. Single Responsibility Principle - Each handler focuses on one type of request - Separation of concerns

  4. Open/Closed Principle - Can introduce new handlers without changing existing ones - Extend functionality without modification

Drawbacks

  1. Receipt isn't guaranteed - Request may reach end of chain without being handled - Need to handle this case (default handler or error)

  2. Hard to observe runtime characteristics - Difficult to debug which handler processes which request - Chain traversal can be hard to trace

  3. Performance concerns - Request may traverse many handlers - Each handler in chain adds overhead - Long chains can impact performance

  4. Chain configuration - Setting up chain correctly can be complex - Need to ensure chain is properly configured

Chain Variations

1. Pure Chain

Each handler either: - Processes the request (and stops) - Passes it to the next handler

1
2
3
4
5
if (canHandle(request)) {
    return process(request);
} else if (nextHandler_) {
    return nextHandler_->handle(request);
}

2. Impure Chain

Handlers can: - Process and pass to next - Partially process and pass remainder - Process, modify, and pass

1
2
3
4
5
std::string result = preProcess(request);
if (nextHandler_) {
    result += nextHandler_->handle(request);
}
return postProcess(result);

3. Conditional Chain

Handler decides based on condition:

1
2
3
4
if (shouldHandle(request)) {
    return process(request);
}
return nextHandler_ ? nextHandler_->handle(request) : "";

Real-world Examples

1. Support Ticket System

  • Handlers: Basic Support → Technical Support → Manager Support
  • Request: Support tickets with different severity levels
  • Escalation: Ticket moves up chain based on complexity

2. GUI Event Handling

  • Handlers: Button → Panel → Window → Application
  • Request: Mouse clicks, key presses
  • Bubbling: Event propagates from child to parent until handled

3. Web Middleware

  • Handlers: Authentication → Logging → Compression → Routing
  • Request: HTTP requests
  • Processing: Each middleware processes or passes request

4. Logging Framework

  • Handlers: Debug Logger → Info Logger → Error Logger
  • Request: Log messages with severity levels
  • Filtering: Each logger handles messages at its level

5. Exception Handling

  • Handlers: Try-catch blocks at different levels
  • Request: Exceptions
  • Propagation: Exception moves up stack until caught

6. ATM Cash Dispensing

  • Handlers: $100 Dispenser → $50 Dispenser → $20 Dispenser
  • Request: Withdrawal amount
  • Distribution: Each dispenser handles what it can, passes remainder

7. Email Spam Filter

  • Handlers: Blacklist → Bayesian Filter → Content Filter
  • Request: Incoming emails
  • Filtering: Each filter checks and may block or pass

Implementation Considerations

1. Successor Chain

Options for implementing the chain:

1
2
3
4
5
6
7
8
9
// Option 1: Each handler maintains successor
class Handler {
    std::shared_ptr<Handler> nextHandler_;
};

// Option 2: Separate chain manager
class Chain {
    std::vector<std::shared_ptr<Handler>> handlers_;
};

2. Representing Requests

Options:

1
2
3
4
5
6
7
8
9
// Simple: String or enum
std::string handle(const std::string& request);

// Complex: Request object
class Request {
    RequestType type;
    std::map<std::string, std::string> data;
};
std::string handle(const Request& request);

3. Default Handler

Provide a default handler at chain end:

1
2
3
4
5
class DefaultHandler : public Handler {
    std::string handle(const std::string& request) override {
        return "DefaultHandler: No one could handle '" + request + "'.";
    }
};

4. Connecting Successors

Manual connection:

handlerA->setNext(handlerB);
handlerB->setNext(handlerC);

Chain builder:

1
2
3
4
Chain chain;
chain.addHandler(handlerA)
     .addHandler(handlerB)
     .addHandler(handlerC);

5. Breaking the Chain

Handlers can break the chain:

1
2
3
if (shouldStopChain(request)) {
    return process(request);  // Don't call next
}

Chain of Responsibility vs Similar Patterns

Chain of Responsibility vs Decorator

Aspect Chain of Responsibility Decorator
Intent Handle request in chain Add responsibilities
Processing One handler processes All decorators process
Order Stops when handled All layers execute
Success May or may not be handled Always processes

Chain of Responsibility vs Composite

Aspect Chain of Responsibility Composite
Structure Linear chain Tree structure
Propagation Stops when handled Propagates to all children
Purpose Find handler Treat individuals and groups uniformly

Chain of Responsibility vs Command

Aspect Chain of Responsibility Command
Focus Who handles request Encapsulate request
Connection Handlers linked Commands independent
Receiver Dynamic (traverses chain) Fixed (bound to command)

Sample Output

=== Chain of Responsibility Pattern Demo ===

--- Creating a chain of handlers ---
Chain: HandlerA -> HandlerB -> HandlerC

--- Sending requests through the chain ---
Client: Sending request 'A'
  Result: ConcreteHandlerA: I'll handle the request 'A'.
Client: Sending request 'B'
  Result: ConcreteHandlerB: I'll handle the request 'B'.
Client: Sending request 'C'
  Result: ConcreteHandlerC: I'll handle the request 'C'.
Client: Sending request 'D'
  Result: Request 'D' was not handled.

=== Real-world Scenario: Support Ticket System ===

Support Chain: Basic -> Technical -> Manager

Processing ticket: Password reset
  -> Basic Support: Handling ticket - Password reset

Processing ticket: Software installation issue
Basic Support: Escalating ticket...
  -> Technical Support: Handling ticket - Software installation issue

Processing ticket: System-wide outage
Basic Support: Escalating ticket...
Technical Support: Escalating ticket...
  -> Manager Support: Handling CRITICAL ticket - System-wide outage

Key Takeaways

  1. Chain of Responsibility decouples sender from receiver of a request
  2. Multiple objects can handle a request; handler determined at runtime
  3. Request travels through chain until handled or chain ends
  4. Each handler decides to process or pass request to next handler
  5. Easy to add/remove handlers without affecting others (OCP)
  6. Each handler has single responsibility (SRP)
  7. Request may not be handled if no suitable handler exists
  8. Commonly used in GUI event handling, middleware, logging, and exception handling
  9. Can be pure (one handler processes) or impure (multiple handlers process)
  10. Trade-off: Flexibility and loose coupling vs guaranteed handling and debuggability