Drupal разработчики редко имеют дело с паттернами проектирования объектов, т.к. в основном им приходится работать в уже сформированной среде. Мы используем шаблоны каждый день, но даже не задумываемся над этим. Имея представление о паттернах вам будет легче общаться с коллегами - достаточно сказать название паттерна и все понимают о чем идет речь. Вместо изобретения очередного велосипеда вы можете использовать уже отработанные эффективные решения. Паттерны проектирования одни и те же в разных языках программирования, отличается только реализация. Так что зная шаблоны вы всегда сможете найти им применение в своей карьере. Кстати, вопрос о паттернах проектирования один из самых частых при собеседованиях.
Порождающие паттерны
Одиночка (Singleton)
“Одиночка” - это, наверное, самый известный архитектурный паттерн. Его назначение - это создавать единственный экземпляр класса и предоставлять единую точку доступа к объекту.
В Drupal с помощью шаблона “Одиночка” реализован доступ к настройками сайта. На схеме внизу изображено как это работает:
Во время начальной загрузки ядра Drupal инициализирует настройки используя Settings::initialize()
. После этого экземпляр класса помещается в защищенную переменную self::$instance
, откуда он берется в дальнейшем. Настройки сайта являются общим ресурсом, поэтому шаблон “Одиночка” позволяет Drupal обращаться к настройкам только один раз при инициализации.
Фабричный метод (Factory method)
“Фабричный метод” определяет общий интерфейс для создания объектов. Тип объекта, что будет создан, может быть изменен в зависимости от настроек.
В Drupal “Фабричный метод” используется для получения объекта для работы с кешем. Различные типы кеша требуют собственную реализацию, поэтому у нас есть классы реализующие интерфейс CacheBackendInterface
такие как DatabaseBackend
, PhpBackend
, MemoryBackend
и т.д. для работы с разными видами кеша. К тому же для разных подсистем кеш у нас может быть разный. Поэтому был определен интерфейс CacheFactoryInterface
и его реализации DatabaseBackendFactory
, PhpBackendFactory
, MemoryBackendFactory
и т.д., которые создают объекты для работы с кешем определенного типа. Для каждого класса, реализующий интерфейс, CacheBackendInterface
у нас есть своя фабрика реализующая CacheFactoryInterface
.
Какую конкретно использовать фабрику решается в классе \Drupal\Core\Cache\CacheFactory
- в зависимости от настроек системы используется та или иная фабрика. Поэтому, например, чтобы получить объект для работы с кешем рендеринга вам нужно просто выполнить \Drupal::service(‘cache_factory’)->get(‘render’)
. Вам не нужно знать какой конкретно тип кеша используется в системе для рендеринга шаблонов. В settings.php
вы можете выставить
settings['cache']['bins']['render'] = 'cache.backend.database';
или
settings['cache']['bins']['render'] = 'cache.backend.memcache';
и фабрика вернет вам объект класса DatabaseBackend
либо MemcacheBackend
в зависимости от настроек.
Также к преимуществам фабричного метода относится то, что он позволяет возвращать уже созданные объекты, а не создавать их каждый раз заново, что сэкономит вам ресурсы (пример \Drupal\Core\Cache\MemoryBackendFactory
).
Внедрение зависимостей (Dependency injection)
“Внедрение зависимостей” - явная передача служб в объект используя конструктор или сеттеры. В Drupal мы определяем объекты-сервисы, ответственные за определенные функции. Когда нам нужны эти сервисы в наших классах мы:
- описываем зависимости как аргументы при определении сервисов в
*.services.yml
. Эти зависимости будут переданы в__construct()
метод. - используем интерфейс
ContainerFactoryPluginInterface
для плагинов. - используем интерфейс
ContainerInjectionInterface
для остальных объектов (но не везде, например Entity не поддерживают внедрение зависимостей).
На схеме описано как сервисы “config_factory”, “alias_manager”, “path_validator”, “request_context” добавляются в форму SiteInformationForm
. При создании формы мы их получаем в конструкторе и сохраняем во внутренних переменных класса - зависимости внедрены.
Данный шаблон позволяет с легкостью распределять ответственность между классами. К тому же мы не зависим от конкретной реализации зависимости - нам нужно только знать какой интерфейс она имплементирует. Поэтому, в дальнейшем мы можем передать в объект другую реализацию зависимости без изменения самого объекта.
Поведенческие паттерны
Посредник (Intermediary, Controller, Mediator)
Шаблон “Посредник” используется для реализации взаимодействия между компонентами приложения.
Вы будете удивлены, но паттерн “Посредник” является основой для главной отличительной особенности Drupal - системы хуков. У нас есть основной элемент - посредник, который предоставляет единый интерфейс через который общаются разные части системы. Компоненты-получатели не знают кто отправил запрос, а компоненты-отправители не знают кто обработает запрос.
На схеме внизу представлено как работают хуки. В нашем примере, когда компонент-отправитель EntityStorageBase
хочет уведомить о каком-либо событии (это может быть: presave, create, insert, update, delete, revision_delete, predelete) произошедшим с Entity, он вызывает посредник ModuleHandler
и передает ему название события/хука и параметры. ModuleHandler
содержит в себе ссылки на все хуки в системе в параметре implementations
. Затем он вызывает соответствующие методы-хуки c передаными параметрами.
Также паттерн “Посредник” используется в системе событий, которая в Drupal построена на основе Symfony EventDispatcher. Здесь посредником является ContainerAwareEventDispatcher
. Компоненты-отправители (в нашем примере это \Drupal\Core\Config
) уведомляют посредник о наступлении события. В переменной listeners
в посреднике находятся ссылки на все компоненты-подписчики (реализуют EventSubscriberInterface
). Когда посредник получает уведомление о событии он вызывает подписчики, которые подписаны на вызываемое событие.
Посетитель (Visitor)
Шаблон “Посетитель” используется для того чтобы отделить операции над объектом от самого объекта. Таким образом, не затрагивая структуру объекта мы можем добавить в программу какие-либо действия с ним.
Шаблон “Посетитель” мы можем найти в Twig в парсинге шаблонов - строится дерево элементов (html тегов) шаблона и затем, при обходе дерева, эти элементы обрабатываются объектами-посетителями.
Как вы видите на схеме в Twig используются разные объекты-посетители (EscaperNodeVisitor
, TwigNodeVisitor
, SelfAnalysisNodeVisitor
). Если нам нужно будет обработать элементы как-то еще, то мы можем добавить дополнительный посетитель без изменения класса элемента.
Если разные элементы должны быть обработаны по-разному, то посетитель реализует у себя эти варианты (посмотрите на пример SelfAnalysisNodeVisitor
- для каждого элемента своя реализация алгоритма). Данный шаблон хорошо подходит если нам редко приходится изменять элементы над которыми мы совершаем операции. При изменении элемента может потребоваться внести изменения в реализацию посетителей.
Структурные паттерны
Заместитель (Proxy)
Шаблон “Заместитель” применяется когда нужно использовать вместо реальных объектов объекты-заместители. Эти объекты позволят выполнять код до или после вызова методов оригинального объекта, или управлять доступом к оригинальному объекту.
В Drupal паттерн “Заместитель” реализован в классе \Drupal\Core\Session\AccountProxy
сервиса “current_user”. AccountProxy
реализует все методы класса \Drupal\Core\Session\UserSession
чьим заместителем он является. Таким образом мы можем работать с объектом класса AccountProxy
также как с объектом класса UserSession
. Данный подход позволяет устанавливать и менять текущего пользователя без повторной инициализации сервиса “current_user”. К тому же, этот сервис используется всеми сервисами как единая точка доступа к сессии текущего пользователя.
Ссылки