Chain of Responsibility
Intent
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.
Problem
A support system receives tickets of varying severity. Simple issues should be resolved by Level 1 support, more complex issues by Level 2, and critical escalations by a Manager. Hard-wiring the routing logic into the client creates tight coupling and makes it difficult to add or reorder handlers.
Real-World Analogy
Think of calling customer support. First you get a chatbot. If it can’t help, you’re transferred to a frontline agent. If they can’t help, you go to a specialist. If still unresolved, you reach a manager. Each level either handles your request or passes it up the chain. You don’t choose who handles it — the chain figures it out.
When You Need It
- You’re building a request validation pipeline where input must pass through authentication, authorization, rate limiting, and data validation — each step either handles the request or passes it along
- You’re creating a help desk system where tickets are routed through tiers of support based on severity and expertise
- You’re implementing middleware in a web framework where each middleware can process the request, modify it, or pass it to the next one
UML Class Diagram
classDiagram
class Handler {
<<abstract>>
-Handler _next
+SetNext(Handler) Handler
+Handle(TicketRequest) string
}
class Level1Support {
+Handle(TicketRequest) string
}
class Level2Support {
+Handle(TicketRequest) string
}
class ManagerSupport {
+Handle(TicketRequest) string
}
class TicketRequest {
+Severity int
+Description string
}
Handler <|-- Level1Support
Handler <|-- Level2Support
Handler <|-- ManagerSupport
Handler o-- Handler : _next
Handler ..> TicketRequest
Sequence Diagram
sequenceDiagram
participant Client
participant Handler1
participant Handler2
participant Handler3
Client->>Handler1: handle(request)
Handler1->>Handler2: handle(request)
Handler2->>Handler3: handle(request)
Handler3-->>Client: handled
Participants
| Participant | Role |
|---|---|
| Handler | Declares the interface for handling requests and holds a reference to the next handler in the chain. |
| Level1Support | Handles low-severity tickets; forwards the rest. |
| Level2Support | Handles medium-severity tickets; forwards the rest. |
| ManagerSupport | Handles high-severity or unhandled tickets as the final link. |
| TicketRequest | Carries the data describing the support ticket. |
How It Works
- The client creates a chain of handlers (Level1 -> Level2 -> Manager).
- A
TicketRequestis passed to the first handler. - Each handler inspects the request. If it can handle the severity, it processes the ticket and returns a result. Otherwise it delegates to the next handler.
- If no handler processes the request, a default response is returned.
Applicability
- More than one object may handle a request, and the handler is not known in advance.
- You want to issue a request to one of several objects without specifying the receiver explicitly.
- The set of handlers or their order should be configurable at runtime.
Trade-offs
Pros:
- Decouples the sender from the receiver — the sender doesn’t know which handler processes the request
- Handlers can be added, removed, or reordered without changing client code
- Each handler has a single responsibility — process or pass along
Cons:
- No guarantee that any handler will process the request — it may fall off the end of the chain
- Can be hard to debug — tracing which handler handled (or dropped) a request requires logging
- Performance can degrade with long chains if most handlers just pass the request through
Example Code
C#
public class TicketRequest
{
public int Severity { get; }
public string Description { get; }
public TicketRequest(int severity, string description)
{
Severity = severity;
Description = description;
}
}
public abstract class Handler
{
private Handler _next;
public Handler SetNext(Handler next)
{
_next = next;
return next;
}
public virtual string Handle(TicketRequest request)
{
if (_next != null)
return _next.Handle(request);
return "No handler available.";
}
}
public class Level1Support : Handler
{
public override string Handle(TicketRequest request)
{
if (request.Severity <= 1)
return $"Level1 handled: {request.Description}";
return base.Handle(request);
}
}
public class Level2Support : Handler
{
public override string Handle(TicketRequest request)
{
if (request.Severity <= 2)
return $"Level2 handled: {request.Description}";
return base.Handle(request);
}
}
public class ManagerSupport : Handler
{
public override string Handle(TicketRequest request)
{
return $"Manager handled: {request.Description}";
}
}
// Usage
var level1 = new Level1Support();
var level2 = new Level2Support();
var manager = new ManagerSupport();
level1.SetNext(level2).SetNext(manager);
Console.WriteLine(level1.Handle(new TicketRequest(1, "Password reset")));
Console.WriteLine(level1.Handle(new TicketRequest(3, "Server outage")));
Delphi
type
TTicketRequest = record
Severity: Integer;
Description: string;
end;
THandler = class
private
FNext: THandler;
public
function SetNext(ANext: THandler): THandler;
function Handle(const ARequest: TTicketRequest): string; virtual;
end;
TLevel1Support = class(THandler)
public
function Handle(const ARequest: TTicketRequest): string; override;
end;
TLevel2Support = class(THandler)
public
function Handle(const ARequest: TTicketRequest): string; override;
end;
TManagerSupport = class(THandler)
public
function Handle(const ARequest: TTicketRequest): string; override;
end;
function THandler.SetNext(ANext: THandler): THandler;
begin
FNext := ANext;
Result := ANext;
end;
function THandler.Handle(const ARequest: TTicketRequest): string;
begin
if Assigned(FNext) then
Result := FNext.Handle(ARequest)
else
Result := 'No handler available.';
end;
function TLevel1Support.Handle(const ARequest: TTicketRequest): string;
begin
if ARequest.Severity <= 1 then
Result := 'Level1 handled: ' + ARequest.Description
else
Result := inherited Handle(ARequest);
end;
function TLevel2Support.Handle(const ARequest: TTicketRequest): string;
begin
if ARequest.Severity <= 2 then
Result := 'Level2 handled: ' + ARequest.Description
else
Result := inherited Handle(ARequest);
end;
function TManagerSupport.Handle(const ARequest: TTicketRequest): string;
begin
Result := 'Manager handled: ' + ARequest.Description;
end;
// Usage
var
Level1, Level2, Manager: THandler;
Ticket: TTicketRequest;
begin
Level1 := TLevel1Support.Create;
Level2 := TLevel2Support.Create;
Manager := TManagerSupport.Create;
Level1.SetNext(Level2).SetNext(Manager);
Ticket.Severity := 1;
Ticket.Description := 'Password reset';
WriteLn(Level1.Handle(Ticket));
Ticket.Severity := 3;
Ticket.Description := 'Server outage';
WriteLn(Level1.Handle(Ticket));
end;
C++
#include <iostream>
#include <memory>
#include <string>
struct TicketRequest {
int severity;
std::string description;
};
class Handler {
protected:
std::shared_ptr<Handler> next_;
public:
virtual ~Handler() = default;
std::shared_ptr<Handler> SetNext(std::shared_ptr<Handler> next) {
next_ = next;
return next;
}
virtual std::string Handle(const TicketRequest& req) {
if (next_)
return next_->Handle(req);
return "No handler available.";
}
};
class Level1Support : public Handler {
public:
std::string Handle(const TicketRequest& req) override {
if (req.severity <= 1)
return "Level1 handled: " + req.description;
return Handler::Handle(req);
}
};
class Level2Support : public Handler {
public:
std::string Handle(const TicketRequest& req) override {
if (req.severity <= 2)
return "Level2 handled: " + req.description;
return Handler::Handle(req);
}
};
class ManagerSupport : public Handler {
public:
std::string Handle(const TicketRequest& req) override {
return "Manager handled: " + req.description;
}
};
int main() {
auto level1 = std::make_shared<Level1Support>();
auto level2 = std::make_shared<Level2Support>();
auto manager = std::make_shared<ManagerSupport>();
level1->SetNext(level2)->SetNext(manager);
TicketRequest t1{1, "Password reset"};
TicketRequest t2{3, "Server outage"};
std::cout << level1->Handle(t1) << "\n";
std::cout << level1->Handle(t2) << "\n";
return 0;
}
Runnable Examples
| Language | Source |
|---|---|
| C# | ChainOfResponsibility.cs |
| C++ | chain-of-responsibility.cpp |
| Delphi | chain_of_responsibility.pas |