Bird
0
0
LLDsystem_design~7 mins

Chain of Responsibility pattern in LLD - System Design Guide

Choose your learning style9 modes available
Problem Statement
When a request needs to be handled by one of many possible handlers, hardcoding the logic to decide which handler processes it leads to rigid, hard-to-maintain code. Adding new handlers or changing the order requires modifying existing code, increasing the risk of bugs and reducing flexibility.
Solution
This pattern passes the request along a chain of handlers. Each handler decides if it can process the request; if not, it forwards the request to the next handler in the chain. This way, handlers are loosely coupled, and new handlers can be added without changing existing code.
Architecture
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ Handler 1   │ →→→ │ Handler 2   │ →→→ │ Handler 3   │
└─────────────┘     └─────────────┘     └─────────────┘
       ↓                  ↓                  ↓
    Handles?           Handles?           Handles?
       ↓                  ↓                  ↓
    Process            Process            Process
       ↓                  ↓                  ↓
    Or pass            Or pass            Or pass

The diagram shows a sequence of handlers linked in a chain. Each handler receives the request, decides whether to process it or pass it to the next handler.

Trade-offs
✓ Pros
Decouples sender and receiver, allowing flexible assignment of responsibilities.
Easily extendable by adding new handlers without modifying existing ones.
Supports dynamic changes in the chain order or composition at runtime.
✗ Cons
Request may pass through many handlers, causing performance overhead.
No guarantee a request will be handled if no handler matches.
Debugging can be harder due to indirect flow of control.
Use when multiple handlers can process a request and the handler is not known in advance. Suitable for systems with dynamic or extensible processing rules, especially when the number of handlers is moderate (less than a few dozen).
Avoid when performance is critical and the chain length is very long, or when the request must be handled by a specific handler deterministically.
Real World Examples
Java Standard Library
The java.util.logging framework uses Chain of Responsibility to pass log messages through a chain of handlers that decide whether to log or filter messages.
Microsoft ASP.NET
ASP.NET uses this pattern in its HTTP pipeline where multiple middleware components process or pass on HTTP requests.
Apache Tomcat
Tomcat uses a chain of Valve components to process HTTP requests, each Valve deciding whether to handle or forward the request.
Code Example
The before code has client logic deciding which handler to call, causing tight coupling and duplication. The after code creates a chain where each handler tries to process the request and passes it on if it cannot. This decouples client from handler logic and allows easy extension.
LLD
### Before: tightly coupled handlers
class HandlerA:
    def handle(self, request):
        if request == 'A':
            return 'Handled by A'
        else:
            return None

class HandlerB:
    def handle(self, request):
        if request == 'B':
            return 'Handled by B'
        else:
            return None

# Client code
request = 'B'
handler_a = HandlerA()
handler_b = HandlerB()
result = handler_a.handle(request)
if result is None:
    result = handler_b.handle(request)
print(result)

### After: Chain of Responsibility pattern
class Handler:
    def __init__(self, successor=None):
        self.successor = successor

    def handle(self, request):
        handled = self.process_request(request)
        if handled is None and self.successor:
            return self.successor.handle(request)
        return handled

    def process_request(self, request):
        raise NotImplementedError('Must override')

class HandlerA(Handler):
    def process_request(self, request):
        if request == 'A':
            return 'Handled by A'
        return None

class HandlerB(Handler):
    def process_request(self, request):
        if request == 'B':
            return 'Handled by B'
        return None

# Client code
handler_chain = HandlerA(HandlerB())
result = handler_chain.handle('B')
print(result)
OutputSuccess
Alternatives
Observer pattern
Observer notifies multiple listeners simultaneously, while Chain of Responsibility passes a request along a single chain until handled.
Use when: Choose Observer when multiple components need to react to the same event independently.
Strategy pattern
Strategy selects one algorithm at runtime, Chain of Responsibility passes a request through multiple handlers until one processes it.
Use when: Choose Strategy when you want to swap entire algorithms dynamically.
Summary
Chain of Responsibility passes a request through a chain of handlers until one processes it.
It decouples sender and receiver, allowing flexible and extensible request handling.
This pattern is useful when multiple handlers can process a request but the handler is not known in advance.