PHP и Temporal Coupling

от автора

Про архитектуру приложений на PHP было написано не один десяток статей, но на данной проблеме больше акцентируют внимание разработчики Java и C#. Суть ее заключается в жесткой зависимости одного свойства на другом.

Представим следующую ситуацию:

Использование сеттера

<?php  class Page {     /**      * @var string      */     private $content;      /**      * @param $content      */     public function setContent($content)     {         $this->content = $content;     } }  class PageBuilder {     /**      * @var Page      */     private $page;      /**      * @param $page      */     public function setPage($page)     {         $this->page = $page;     }      /**      * @param $content      * @return $this      */     public function setContent($content)     {         $this->page->setContent($content);          return $this;     }      /**      * @return Page      */     public function build()     {         return $this->page;     } } 

$pageBuilder = new PageBuilder(); $pageBuilder->setPage(new Page()); $pageBuilder->setContent('Test content'); $pageBuilder->build(); 

В данном примере видно, что $pageBuilder->build() является потенциально опасным и может привести к фатальной ошибке если $pageBuilder->setPage(new Page()) не был предварительно вызван. Другая часто встречающаяся ошибка — использование методов init() или initialization():

Использование инициализатора

class Page {     // Class }  class PageBuilder {     /**      * @var Page      */     private $page;      /**      * Initialization      */     public function init()     {         $this->page = new Page();     }      /**      * @param $content      * @return $this      */     public function setContent($content)     {         $this->page->setContent($content);          return $this;     }      /**      * @return Page      */     public function build()     {         return $this->page;     } } 

$pageBuilder = new PageBuilder(); $pageBuilder->init(); $pageBuilder->setContent('Test content'); $pageBuilder->build(); 

Если мы забудем вызвать метод init(), нас также ждут неприятности. Данный код является отличным примером плохой архитектуры приложения. Методы-инициализаторы пытаются вести себя как конструкторы, которыми не являются по определению.

Для избежания Temporal Coupling нужно всегда пользоваться правилами:

  • экземпляр класса должен быть готов для использования сразу же после создания;
  • конструкторы не должны выполнять никакой логики кроме инициализации свойств класса;

Инъекция зависимости через конструктор

Это решение является оптимальным и предпочтительным в большинстве случаев. Мы можем использовать механизмы Dependency Injection из Symfony, Laravel или других современных фреймворков.

Инъекция через конструктор

class Page {     // Class }  class PageBuilder {     /**      * @var Page      */     private $page;      /**      * PageBuilder constructor.      * @param Page $page      */     public function __construct(Page $page)     {         $this->page = $page;     }     // Методы-сеттеры }  $pageBuilder = new PageBuilder(); $pageBuilder->setContent('Test content'); $pageBuilder->build(); 

Абстрактная фабрика

Немного модифицируем наш код, добавив абстрактную фабрику:

Абстрактная фабрика

<?php  class Page {     // Class }  class PageBuilder {     /**      * @var Page      */     private $page;      /**      * PageBuilder constructor.      * @param Page $page      */     public function __construct(Page $page)     {         $this->page = $page;     }      /**      * @param $content      * @return $this      */     public function setContent($content)     {         $this->page->setContent($content);          return $this;     }      /**      * @return Page      */     public function build()     {         return $this->page;     } }  class PageBuilderFactory implements FactoryInterface {     /**      * @param Page|null $page      * @return PageBuilder      */     public function create(Page $page = null)     {         if (null === $page) {             $page = new Page();         }          return new PageBuilder($page);     } }  $pageBuilderFactory = new PageBuilderFactory(); $pageBuilder = $pageBuilderFactory->create(); $pageBuilder->setContent('Test content'); $pageBuilder->build(); 

Как видим, экземпляр класса Page создан без явного вызова и будет доступен нашему билдеру.

Заключение

Temporal Coupling нужно всегда избегать, вне зависимости от сложности приложения, влияния code-review или других факторов. Также помните что конструкторы должны выполнять только логику, связанную с инъекциями. Иначе вы рискуете получить ухудшение быстродействия на этапе создания экземпляров класса.

Полезные ссылки

ссылка на оригинал статьи https://habrahabr.ru/post/281330/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *