Share This Article
The chain of responsibility pattern is used to process varied requests, each of which may be handled by a different handler.
A handler is responsible for completing a request, but they can delegate that responsibility to other handlers, making it a chain of responsibility.
Benefits and drawbacks
There are many benefits to using the chain of responsibility pattern:
- A request never gets ‘lost’ because at least one handler is always registered to process it.
- A request can be handled by more than one handler so that different types of processing can be applied.
- The order in which handlers are added to the chain determines the order in which they process the request.
- Handlers can be removed from the chain without affecting the other handlers or the request.
- High-level handlers can delegate to low-level handlers allowing different abstraction levels to handle the request.
There are also some drawbacks to this pattern:
- The handler that processes the request will not always be known in advance, so the requestor will not know where the request is being handled.
- Debugging can be problematic because the request will ‘bounce’ around the system until it is handled.
How it works
The chain of responsibility pattern is defined by two types of objects:-
- handler – this is an abstraction of an object that can handle a request.
- request – this is an object representing a request that needs to be processed.
Implementation
# Handler abstraction
class Handler
def successor
raise NotImplementedError
end
def handle_request
raise NotImplementedError
end
end
# ConcreteHandler1
class ConcreteHandler1 < Handler
# @successor - next handler in the chain
attr_accessor :successor
def handle_request
if request.class == Request1
# if we can handle the request...
# handle the request...
# ...then stop propagating
return true
end
# if we can't handle the request then pass the request to the next handler in the chain
@successor&.handle_request
# ...or stop propagating if there are no more handlers
false
end
end
# ConcreteHandler2
class ConcreteHandler2 < Handler
# @successor - next handler in the chain
attr_accessor :successor
def handle_request
if request.class == Request2
# if we can handle the request...
# handle the request...
# ...then stop propagating
return true
end
# if we can't handle the request then pass the request to the next handler in the chain
@successor&.handle_request
# ...or stop propagating if there are no more handlers
false
end
end
# Request
class Request
# some requests...
end
# Request1
class Request1 < Request
# some requests...
end
# Request2
class Request2 < Request
# some requests...
end
# client
def client
request1 = Request1.new
handler1 = ConcreteHandler1.new
handler2 = ConcreteHandler2.new
handler1.successor = handler2
# send the request to handler1
handler1.handle_request(request1)
end
Interpretation
The key feature of this example is the succession of handlers.
The first handler that can process the request does so, but if it can’t, it passes the request to the next handler in the chain.
This continues until either the request can be processed or there are no more handlers in the chain.
You can see that the chain of responsibility pattern only allows the request to be handled in a single way.
If you need to handle the same request in multiple ways, you should look at the command pattern, which can do this.
Summary
The chain of responsibility pattern is a useful design pattern for processing varied requests, but it has some drawbacks which you should keep in mind when using it.