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());
        }
    }