The first behavioral pattern of this part of the series is named Chain of Responsibility. Its intent is organizing a chain of objects to handle a request such as a method call.
When a ConcreteHandler does not know how to satisfy a request from a Client, or it is not designed at all to do so, it delegates to the next Handler in the chain, which it maintains a reference to.
This pattern is often used in conjunction with Composite, where some Leaf or Container object delegates an operation to their parent by default. As another example, localization is often handled with a Chain of Responsibility: when the German translation adapter does not find a term for a translation key, if falls back to the English language adapter or even to displaying the key itself.
The coupling is reduced to a minimum: the Client class does not know which concrete class handles the request; the chain is configured at the creation of the object graph, at runtime, and ConcreteHandlers do not know which object is their successor in the chain. The behavior is successfully distributed between the objects, where the nearest object in the chain has priority on stepping in and assuming the responsibility to satisfy the request.
Participants:
- Client: makes a request to an Handler.
- Handler abstraction: accepts a request and satisfies it in some way.
- ConcreteHandlers: accept a request, try to satisfy it and delegate to the next handler if not successful.
<?php /** * The Handler abstraction. Objects that want to be a part of the * ChainOfResponsibility must implement this interface directly or via * inheritance from an AbstractHandler. */ interface KeyValueStore { /** * Obtain a value. * @param string $key * @return mixed */ public function get($key); } /** * Basic no-op implementation which ConcreteHandlers not interested in * caching or in interfering with the retrieval inherit from. */ abstract class AbstractKeyValueStore implements KeyValueStore { protected $_nextHandler; public function get($key) { return $this->_nextHandler->get($key); } } /** * Ideally the last ConcreteHandler in the chain. At least, if inserted in * a Chain it will be the last node to be called. */ class SlowStore implements KeyValueStore { /** * This could be a somewhat slow store: a database or a flat file. */ protected $_values; public function __construct(array $values = array()) { $this->_values = $values; } public function get($key) { return $this->_values[$key]; } } /** * A ConcreteHandler that handles the request for a key by looking for it in * its own cache. Forwards to the next handler in case of cache miss. */ class InMemoryKeyValueStore implements KeyValueStore { protected $_nextHandler; protected $_cached = array(); public function __construct(KeyValueStore $nextHandler) { $this->_nextHandler = $nextHandler; } protected function _load($key) { if (!isset($this->_cached[$key])) { $this->_cached[$key] = $this->_nextHandler->get($key); } } public function get($key) { $this->_load($key); return $this->_cached[$key]; } } /** * A ConcreteHandler that delegates the request without trying to * understand it at all. It may be easier to use in the user interface * because it can specialize itself by defining methods that generates * html, or by addressing similar user interface concerns. * Some Clients see this object only as an instance of KeyValueStore * and do not care how it satisfy their requests, while other ones * may use it in its entirety (similar to a class-based adapter). * No client knows that a chain of Handlers exists. */ class FrontEnd extends AbstractKeyValueStore { public function __construct(KeyValueStore $nextHandler) { $this->_nextHandler = $nextHandler; } public function getEscaped($key) { return htmlentities($this->get($key), ENT_NOQUOTES, 'UTF-8'); } } // Client code $store = new SlowStore(array('pd' => 'Philip K. Dick', 'ia' => 'Isaac Asimov', 'ac' => 'Arthur C. Clarke', 'hh' => 'Helmut Heißenbüttel')); // in development, we skip cache and pass $store directly to FrontEnd $cache = new InMemoryKeyValueStore($store); $frontEnd = new FrontEnd($cache); echo $frontEnd->get('ia'), "\n"; echo $frontEnd->getEscaped('hh'), "\n";Some implementation notes:
- the Chain of Responsibility may already exist in the object graph, like in the Composite's case.
- moreover, the Handler abstraction may already exist or not. The best choice is a segregated Handler interface with only the handleRequest() operation; do not force a chain in a hierarchy only because the latter already exists.
- there is also the possibility of introducing an abstract class but since the request handling is an orthogonal concern, the concrete classes may already inherit from other classes.
- the Handler (or next Handler) is injected in the Client or in the previous Handler via the constructor or a setter.
- the request object is often a ValueObject and may be implemented as a Flyweight. In php, it can be a scalar type such as a string; note that in some languages a String is an immutable ValueObject.
Excellent example and notes. I've read about this pattern at http://www.ibm.com/developerworks/library/os-php-designptrns/ but after your post and comments it is much clearer to me now.
ReplyDeleteGreat article! Your Practical PHP Patterns series is cool. I don't like these cars and car factory and all this tutorials on other sites. They are too theoretical and the whole thing about design patterns is that one must know in which situation which pattern is best suited to get the most elegant architecture.
ReplyDeleteThanks for this!