Development Blog
Collection of development articles I've put together...
Chain Of Responsibility Pattern
To build a chain of objects to handle a call in sequential order. If one object cannot handle a call, it delegates the call to the next in the chain and so forth.
1
Likes
Stuart Todd
•
2 years ago
•
Design Patterns: Behavioural
Purpose
The purpose of the design pattern
To build a chain of objects to handle a call in sequential order. If one object cannot handle a call, it delegates the call to the next in the chain and so forth.
Examples
Examples of how the design pattern can be used
- Logging framework, where each chain element decides autonomously what to do with a log message
- A Spam filter
- Caching: first object is an instance of e.g. a Memcached Interface, if that "misses" it delegates the call to the database interface
UML
UML design pattern diagram
Code
Code snippets
Handler
Handler interface defines the method signatures for this design pattern.
/**
* The Handler interface declares a method for building the chain of handlers.
* It also declares a method for executing a request.
*/
interface Handler
{
public function setNext(Handler $handler): Handler;
public function handle(string $request): ?string;
}
Abstract Handler
AbstractHandler handles boilerplate (possible code duplication throughout the concrete subclasses) - using an abstract class is also known as the template method pattern.
setNext accepts an instance of Hander, updates a class property called nextHandler. If the nextHandler class property isn’t empty, then we move down the chain to the next instance of Handler.
setNext accepts an instance of Hander, updates a class property called nextHandler. If the nextHandler class property isn’t empty, then we move down the chain to the next instance of Handler.
/**
* The default chaining behavior can be implemented inside a base handler class.
*/
abstract class AbstractHandler implements Handler
{
/**
* @var Handler
*/
private $nextHandler;
public function setNext(Handler $handler): Handler
{
$this->nextHandler = $handler;
// Returning a handler from here will let us link handlers in a
// convenient way like this:
// $stu->setNext($cheryl)->setNext($millie)->setNext($harry);
return $handler;
}
public function handle(string $request): ?string
{
if ($this->nextHandler) {
return $this->nextHandler->handle($request);
}
return null;
}
}
Concrete Sub Classes
Example below defines a set of concrete sub classes which all extend AbstractHandler. You’ll notice that each handle method contains slightly different behaviour, i.e StuHandler wants a Mixed Grill, otherwise we’d be moving onto the next concrete sub class (if the chain has been set up - see Examples). HarryHandler wants a McDonalds.
/**
* The client code is usually suited to work with a single handler. In most
* cases, it is not even aware that the handler is part of a chain.
*/
function clientCode(Handler $handler)
{
foreach (["Nandos", "Mixed Grill", "Cup of coffee", "Burger King", "McDonalds", "Prawns"] as $food) {
echo "Client: Who wants a " . $food . "?\n";
$result = $handler->handle($food);
if ($result) {
echo " " . $result;
} else {
echo " " . $food . " was left alone.\n";
}
}
}
/**
* Build the chain first
*/
$stu = new StuHandler();
$cheryl = new CherylHandler();
$millie = new MillieHandler();
$harry = new HarryHandler();
$stu->setNext($cheryl)->setNext($millie)->setNext($harry);
/**
* The client should be able to send a request to any handler, not just the
* first one in the chain.
*/
echo "Chain: Stu > Cheryl > Millie > Harry\n\n";
clientCode($stu);
echo "\n";
echo "Subchain: Millie > Harry\n\n";
clientCode($millie);
Test output
You’ll notice the output differs depending on how the chain has been set up at runtime. This flexibility allows the adjustment of behaviour at run time. Additionally, this design pattern provides you with mechanism to extend code, rather than leaving code open for modification (which leads to code rot).
Chain: Stu > Cheryl > Millie > Harry
Client: Who wants a Nandos?
Millie: I'll eat the Nandos.
Client: Who wants a Mixed Grill?
Stu: I'll eat the Mixed Grill.
Client: Who wants a Cup of coffee?
Cup of coffee was left alone.
Client: Who wants a Burger King?
Burger King was left alone.
Client: Who wants a McDonalds?
Harry: I'll eat the McDonalds.
Client: Who wants a Prawns?
Cheryl: I'll eat the Prawns.
Subchain: Millie > Harry
Client: Who wants a Nandos?
Millie: I'll eat the Nandos.
Client: Who wants a Mixed Grill?
Mixed Grill was left alone.
Client: Who wants a Cup of coffee?
Cup of coffee was left alone.
Client: Who wants a Burger King?
Burger King was left alone.
Client: Who wants a McDonalds?
Harry: I'll eat the McDonalds.
Client: Who wants a Prawns?
Prawns was left alone.