chain-of-responsibility Цепочка обязанностей (англ. Chain of Responsibility) - относится к классу поведенческих шаблонов. Служит для ослабления связи между отправителем и получателем запроса. При этом сам по себе запрос может быть произвольным.

Шаблон не просто так называется цепочкой обязанностей. По сути, это набор обработчиков, которые по очереди получают запрос, а затем решают, обрабатывать его или нет. Если запрос не обработан, то он передается дальше по цепочке. Если же он обработан, то шаблон сам решает передавать его дальше или нет.

Нам нужно подписать какой-то документ(Contract). У него есть какая-то важность(Contract->importance), от которой зависит в каком департаменте(Department) будет подписан документ. Последний департамент в цепочке всегда обработает документ.

Применение:

  • фреймворк для записи журналов, где каждый элемент цепи самостоятельно принимает решение, что делать с сообщением для логирования;
  • фильтр спама;
  • кеширование: первый объект является экземпляром, к примеру, интерфейса Memcached. Если запись в кеше отсутствует, вызов делегируется интерфейсу базы данных;
  • Yii Framework: CFilterChain — это цепочка фильтров действий контроллера. Точка вызова передаётся от фильтра к фильтру по цепочке и только если все фильтры скажут “да”, действие в итоге может быть вызвано.
  • AbstractDepartment.php
                  
    <?php
    
    namespace DesignPatterns\Behavioral\ChainOfResponsibility;
    
    /**
     * Class AbstractDepartment
     * @package DesignPatterns\Behavioral\ChainOfResponsibility
     */
    abstract class AbstractDepartment
    {
        /**
         * @var string
         */
        protected $name;
    
        /**
         * @var int
         */
        protected $importance;
    
        /**
         * @var AbstractDepartment
         */
        protected $parent;
    
        /**
         * @return string
         */
        public function getName(): string
        {
            return $this->name;
        }
    
        /**
         * @return string
         */
        public function getImportance(): string
        {
            return $this->importance;
        }
    
        /**
         * @return AbstractDepartment
         */
        public function getParent(): AbstractDepartment
        {
            return $this->parent;
        }
    
        /**
         * @param Contract $contract
         *
         * @return Contract
         */
        abstract public function getSign(Contract $contract): Contract;
    
        /**
         * @param Contract $contract
         *
         * @return Contract
         */
        protected function setSign(Contract $contract)
        {
            $contract->setSign($this->getName());
    
            return $contract;
        }
    }
    
                  
  • Contract.php
                  
    <?php
    
    namespace DesignPatterns\Behavioral\ChainOfResponsibility;
    
    /**
     * Class Contract
     * @package DesignPatterns\Behavioral\ChainOfResponsibility
     */
    class Contract
    {
        /**
         * @var int
         */
        protected $importance;
    
        /**
         * @var string
         */
        protected $sign;
    
        /**
         * Contract constructor.
         *
         * @param int $importance
         */
        public function __construct(int $importance)
        {
            $this->importance = $importance;
        }
    
        /**
         * @return int
         */
        public function getImportance(): int
        {
            return $this->importance;
        }
    
        /**
         * @param string $name
         *
         * @return void
         */
        public function setSign(string $name)
        {
            $this->sign = $name;
        }
    
        /**
         * @return string
         */
        public function getSign()
        {
            return $this->sign;
        }
    }
    
                  
  • Department.php
                  
    <?php
    
    namespace DesignPatterns\Behavioral\ChainOfResponsibility;
    
    /**
     * Class Department
     * @package DesignPatterns\Behavioral\ChainOfResponsibility
     */
    class Department extends AbstractDepartment
    {
        /**
         * Department constructor.
         *
         * @param string $nameDepartment
         * @param int $importance
         * @param AbstractDepartment|null $parent
         */
        public function __construct(string $nameDepartment, int $importance, AbstractDepartment $parent = null)
        {
            $this->name = $nameDepartment;
            $this->importance = $importance;
            $this->parent = $parent;
        }
    
        /**
         * @param Contract $contract
         *
         * @return Contract
         */
        public function getSign(Contract $contract): Contract
        {
            if ($this->getImportance() < $contract->getImportance() && $this->parent) {
                return $this->parent->getSign($contract);
            }
    
            return $this->setSign($contract);
        }
    }
    
                  
  • Test case:

    ChainOfResponsibilityTest.php
      
    <?php
    
    namespace DesignPatterns\Tests\Behavioral\ChainOfResponsibility;
    
    use DesignPatterns\Behavioral\ChainOfResponsibility\Contract;
    use DesignPatterns\Behavioral\ChainOfResponsibility\Department;
    use PHPUnit_Framework_TestCase;
    
    class ChainOfResponsibilityTest extends PHPUnit_Framework_TestCase
    {
        public function testWithOutParent()
        {
            $contract = new Contract(78);
    
            $mainDepartment = new Department('MAIN', 30);
            $contractWithSign = $mainDepartment->getSign($contract);
    
            $this->assertGreaterThan($mainDepartment->getImportance(), $contract->getImportance());
            $this->assertEquals('MAIN', $contractWithSign->getSign());
        }
    
        public function testWithParent()
        {
            $contract = new Contract(18);
    
            $mainDepartment = new Department('MAIN', 30);
            $subDepartment = new Department('SUB', 20, $mainDepartment);
            $contractWithSign = $subDepartment->getSign($contract);
    
            $this->assertGreaterThan($contract->getImportance(), $subDepartment->getImportance());
            $this->assertEquals('SUB', $contractWithSign->getSign());
    
            $newContract = new Contract(25);
            $contractWithSign = $subDepartment->getSign($newContract);
    
            $this->assertGreaterThan($subDepartment->getImportance(), $newContract->getImportance());
            $this->assertEquals('MAIN', $contractWithSign->getSign());
        }
    }