Chain of Responsibility Pattern

The pattern allows:

  • more than one object an opportunity to handle a request by linking receiving objects together.

Chain of Responsibility Pattern


The Chain of Responsibility pattern consists of command objects and a series of handlers. Each handling object contains a set of logic that describes the types of command objects that it can handle, and how to pass off those that it cannot handle to the next processing object in the chain. A mechanism also exists for adding new processing objects to the end of this chain.

PHP Example

Here the message is the context to be passed through the chain of handlers. All the handlers implement Logger interface, but each one processes it differently.


<?php
/*
 * @category Design Pattern Tutorial
 * @package Chain of Responsibility Sample
 * @author Dmitry Sheiko <me@dsheiko.com>
 * @link http://dsheiko.com
 */

abstract class Logger
{
    private $_next = null;
    public function setNext(Logger $logger)
    {
        $this->_next = $logger;
        return $this->_next;
    }
    public function log($message)
    {
        $this->_log($message);
        if ($this->_next !== null) {
            $this->_next->log($message);
        }
    }
    abstract protected function _log($message);
}
class EmailLogger extends Logger
{
    public function _log($message)
    {
        echo "Sending via email: ", $message, " \n";
    }
}
class ErrorLogger extends Logger
{
    protected function _log($message)
    {
        echo "Sending to stderr: ", $message, " \n";
    }
}
class StdoutLogger extends Logger
{
    protected function _log($message)
    {
        echo "Writing to stdout: ", $message, " \n";
    }
}


/**
 * Usage
 */
$logger = new StdoutLogger();
$logger->setNext(new ErrorLogger())->setNext(new EmailLogger());
$logger->log('Something happened');

// Output:
// Writing to stdout: Something happened
// Sending to stderr: Something happened
// Sending via email: Something happened

JS/ES5 Example

In this example as soon as data object retrieved it is passed through the chain of handlers associated with concrete modules (widgets). Each handler invokes some functionality in the context of its module. It must be said, particularly in JS the solution can be more elegant by using the Publish–Subscribe pattern

/*
 * @category Design Pattern Tutorial
 * @package Chain of Responsibility Sample
 * @author Dmitry Sheiko <me@dsheiko.com>
 * @link http://dsheiko.com
 */
(function() {
var CommandChain = function() {
    var _commands = [];
    return {
        addCommand: function(command) {
            _commands.push(command);
            return this;
        },
        runCommand: function(args) {
            for (var i in _commands) {
                _commands[i].onCommand(args);
            }
        }
    };
};

var PageMetadata = (function() {
    return {
        onCommand: function(args) {
            console.log('Page title is set to ' + args.title);
        }
    };
}());
var PageMainNavigation = (function() {
    return {
        onCommand: function(args) {
            console.log('Menu refreshed to hihlight page ' + args.pageId);
        }
    };
}());
var PageContent = (function() {
    return {
        onCommand: function(args) {
            console.log('Article title changed to ' + args.title);
            console.log('Article content changed to ' + args.content);
        }
    };
}());
var DataAccess = (function() {
    return {
        get: function(pageId, chain) {
            // Here we request data, let's say it's done
            var data = {
                title: "In the Seven Kingdoms",
                content: "The novel begins with Lord Eddard Stark (Ned) in Winterfell, ancestral home of House Stark, a noble house of the Seven Kingdoms of Westeros and rulers of the North.",
                pageId: pageId
            };
            chain.runCommand(data);
        }
    };
}());
var PageSiblingNavigation = (function() {
    var pageId = 1;
    return {
        getNext : function() {
            var chain = new CommandChain();
            chain.addCommand(PageMetadata)
                .addCommand(PageMainNavigation)
                .addCommand(PageContent);

            DataAccess.get(pageId + 1, chain);
        }
    };
}());

/**
 * Usage
 */
PageSiblingNavigation.getNext();

// Output:
// Page title is set to In the Seven Kingdoms
// Menu refreshed to hihlight page 2
// Article title changed to In the Seven Kingdoms
// Article content changed to The novel begins ...

}());