Introduction to Chain of Responsibility

05:29:00 0 Comments



In this article, we’ll explain and demonstrate the Chain of Responsibility pattern.

Definition

The Chain of Responsibility is a behavioral design pattern that processes a request through a series of processor (handlers/receivers) objects. The request is sent from one handler object to another and processed by one (pure implementation) or all of the handlers. All the handlers are part of the chain.
Two simple examples containing a chain logic are:
  • a person using an ATM to get cash, (enter pin, amount, receipt);
  • a help desk call (options list, press dial buttons, follow the steps).

Participants

The pattern in the short version includes:
  • The Handler: defines an interface for handling requests. It can be an abstract class, which optionally implements default methods and the way to set a successor in the chain.
  • Many concrete handler objects: process the request and optionally provide access to successors;
CoR can also include: 
– a Client object to perform the request and set up the chain; 
– a Request object; 
– a Response object; 
– other design patterns.
The CoR design pattern is not one that is used often, but its core logic makes it useful in several cases.
CoR is useful when:
  • the handler must be determined automatically (e.g., a logging system);
  • the handler cannot be known in advance (e.g., handling exceptions);
  • the request must pass through a specific chain’s priority (e.g., event propagation, command propagation) or the order of the chain must be dynamic.

Basic usage

CoR is often applied in conjunction with Composite pattern, so it’s easy to treat all the handlers in the same way and dispatch the request to the chain’s successors. 
Below is a basic PHP example:
<?php
abstract class BasicHandler
{
    /**
     * @var Handler
     */
    private $successor = null;

    /**
     * Sets a successor handler.
     * 
     * @param Handler $handler
     */
    public function setSuccessor(Handler $handler)
    {
        $this->successor = $handler;
    }

    /**
     * Handles the request and/or redirect the request
     * to the successor.
     *
     * @param mixed $request
     *
     * @return mixed
     */
    abstract public function handle($request);

}

Below is an example (not fully implemented) continuing with the code above:
class FirstHandler extends BasicHandler
{
    public function handle($request)
    {
        //provide a response, call the next successor
    }
}

// .. code for SecondHandler and ThirdHandler classes ..

$firstHandler = new FirstHandler();
$secondHandler = new SecondHandler();
$thirdHandler = new ThirdHandler();

$firstHandler->setSuccessor($secondHandler);
$secondHandler->setSuccessor($thirdHandler);

$result = $firstHandler->handle($request);

Advanced usage

The power behind this pattern is the flexibility and the open logic in the chain organization. 
This flexibility becomes clearer with some examples.
From the BasicHandler code, it’s possible to automate the handle method (with some constraints) moving its scope from the children to the parent abstract class.
While the example below doesn’t solve all the CoR issues, it shows how the pattern can be easily restructured and adapted to different logic.
<?php
abstract class AdvancedHandler
{
    /**
     * @var Handler
     */
    private $successor = null;

    /**
     * Sets a successor handler,
     * in case the class is not able to satisfy the request.
     *
     * @param Handler $handler
     */
    final public function setSuccessor(Handler $handler)
    {
        if ($this->successor === null) {
            $this->successor = $handler;
        } else {
            $this->successor->setSuccessor($handler);
        }
    }

    /**
     * Handles the request or redirect the request
     * to the successor, if the process response is null.
     *
     * @param string|array $data
     *
     * @return string
     */
    final public function handle($request)
    {
        $response = $this->process($request);
        if (($response === null) && ($this->successor !== null)) {
            $response = $this->successor->handle($request);
        }

        return $response;
    }

    /**
     * Processes the request.
     * This is the only method a child can implements, 
     * with the constraint to return null to dispatch the request to next successor.
     * 
     * @param $request
     *
     * @return null|mixed
     */
    abstract protected function process($request);
}

class FirstHandler extends AdvancedHandler
{
    public function process($request)
    {
        //do something
    }
}

// .. code for SecondHandler and ThirdHandler classes ..
$firstHandler = new FirstHandler();
$secondHandler = new SecondHandler();
$thirdHandler = new ThirdHandler();

//the code below sets all successors through the first handler
$firstHandler->setSuccessor($secondHandler);
$firstHandler->setSuccessor($thirdHandler);

$result = $firstHandler->handle($request);
The example above minimizes the methods inside the Concrete Handler (increasing the cohesion) despite the constraint on a not null response.
If it’s necessary to go to the end of the chain for each request, it’s easy to refactor the handle method returning the response when the successor is null, because the last chain’s handler doesn’t have a successor. It is also possible to use a more structured Response object.
There are several modifications to the CoR – for example, combining this pattern with others, like the Factory or Decorator pattern to obtain an incremental construction/change of the Response.
Real world examples include the making of a car (chassis, interior, exterior, painting, wheels, etc.), or the creation of a web page (header, body, content, and footer).
I encourage you to explore other pattern modifications.
Next, we’ll look at some suggestions on common CoR issues like configuring the priority and speeding up the chain.

Configuring the chain

Extracting the logic for configuring the chain’s handlers helps keep the code clean and allows for easy changes.
Using a configuration object with Dependency Injection we can make the configuration global (e.g. via a yaml file).
class Client
{
    private $firstHandler;
    
    public function setChainOrder($handlersOrder) 
    {
        //code for setup the chain
    }
    public function process($request)
    {
        $response = $this->firstHandler->handle($request);
        
        return $response; 
    }
}
$client = new Client();
$client->setChainOrder($myHanldersOrder);
$client->process($request);
Dependency Injection can allow for more flexibility. For example, the chain can be configured by each Request object, as in the example below, with a little code modification.
$client = new Client();
$handlersOrder = $request->getHandlersOrder();
$client->setChainOrder($handlersOrder);
$client->process($request);

Speeding up the chain

If the CoR is stressed by several requests and the handlers are also complex – maybe they create other classes to process the request – a Service Container could be useful.
A Service Container (or dependency injection container) that manages the instantiation of objects without creating them each time helps improve memory and performance.
If CoR receives the same type of request often, a caching system is useful for improving speed. 
One possible solution is to use an implementation of the Flyweight Pattern to store responses and provide the stored response (if it exists) on identical requests, without having to process the chain again.
In this example, I have added a simple cache to the AdvancedHandler class.
abstract class AdvancedHandler
{
//code ...
    final public function handle($request)
    {
        $response = $this->cache->getResponse($request);
        if ($response === null) {
            $response = $this->handleRequestWithChain($request);    
        }
        
        return $response;
    }   

    final public function handleRequestWithChain($request)
    {
        $response = $this->process($request);
        if (($response === null) && ($this->successor !== null)) {
            $response = $this->successor->handleRequestWithChain($request);
        }
        $this->cache->setResponse($response);

        return $response;
    }
//code ...    
}

Final thoughts

The Chain of Responsibility can be a very powerful pattern. It is also possible to implement a chain of chains, making a multidimensional structure.

0 comments :