Про архитектуру приложений на 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/
Добавить комментарий