Паттерны проектирования (Design patterns). Часть 1: Порождающие и структурные

О паттернах проектирования слышали все программисты. Хотя... Исходя из количества стебов над php-разработчиками и видя некоторые куски кода, возможно некоторые из них (php-шников) понятия не имеют, что это такое.

Но мы люди взрослые, так что пришло время для серьезного разговора. Поговорим о тех паттернах, которые применяются при разработке на php и встречаются в существующих фреймворках. Вообще, заметку писал больше для себя, чтобы не забыть, за основу взят репозиторий. Плюс еще можно почитать более фундаментальный труд (это книга, содержит описание 23 классических паттернов).

Паттерны буду описывать не в алфавитном порядке, а как мне субъективно показалось логичней.

Не стоит путать так называемые архитектурные паттерны с паттернами проектирования. Последние есть частным случаем первых. Примеры архитектурных паттернов: MVC, HMVC, MVP, MVVM и т.д. Они не есть темой заметки.

Зачастую более сложный паттерн может включать в себя один или несколько более простых.

Creational

Вначале по фабрикам.

Фабрики предназначены для создания объектов (другого класса, чем сама фабрика (это я так, на всякий случай)).

Simple Factory. Допустим, у нас есть классы велосипеда и скутера, которые реализуют какой-то общий интерфейс. Класс фабрики ConcreteFactory знает о всех объектах, которые он может создавать (код конструктора) и реализует метод для создания объекта по алиасу (createVehicle($type)).

Factory Method. Это усложнение над предыдущим паттерном: а что, если разные типы объектов нужно создавать по разному? Класс FactoryMethod снова содержит метод для создания create($type), но, в отличии от предыдущих примеров, класс абстрактный. Метод create($type) вызывает неимплементированный  метод createVehicle($type), который должен быть реализован в наследниках (GermanFactory, к примеру).

Abstract Factory. Следующее усложнение, когда мы не только не знаем как создаем объект, но и какой объект вообще создаем. (Класс AbstractFactory не содержит реализации метода для создания, методы реализованы в наследниках (JsonFactory, к примеру)).

Обратите внимания, что у двух последних паттернах базовые классы фабрики уже не знают о всех возможных создаваемых объектах, это уже решают наследники.

StaticFactory. Вообще статик это плохо, много говорить не будем, этот паттерн по смыслу очень похож на Simple Factory (хотя реализация иная), но создание всех возможных объектов происходит в одном-единственном статическом методе класса фабрики.

С фабриками закончили =) Дальше:

Builder. Предназначен для построения сложных объектов. Как видно из примера, билдеры разных объектов реализуют один интерфейс, и каждый содержит методы для построения соответствующего объекта. Но сам билдер не строит объект, этим занимается Director, метод build как раз создает и возвращает нужный объект.

Prototype. Суть очень простая: вместо создания объекта вызываем оператор клонирования. Пишут, что так выгодно использовать для представления множества записей, например, в ORM. Но так как после профилировки у меня получилось, что создать быстрее, чем клонировать (пример для профилировки), не вижу надобности детального рассмотрения паттерна. (Подозреваю, у новых версиях php оптимизировали создание объектов, проверено на версии 5.6.8, 5.4.4)

Creating 6000000 new objects... 6.680617 s Cloning  6000000 objects........... 8.660648 s

Видимо, есть смысл использовать паттерн только в случае наличия тяжелой логики в конструкторе, которую можно не использовать при клонировании, например, если в конструкторе заполняется значение полей и мы можем просто скопировать их.

Pool. Предназначен для оптимизации производительности: вместо создания и удаления объектов, оно помещаются/берутся с pool. Есть смысл использовать, если объекты долго инициализируются.

Structural

Adapter. Используется для того, чтобы адаптировать использование одного класса другим без изменения кода используемого класса. В примере адаптер электронной книги (EBookAdapter) реализует интерфейс PaperBookInterface, таким образом вроде "адаптируя" электронную книгу для использования как бумажной.

Bridge. Реализация очень похожа на предыдущий, но смысл немного иной. Применяется для разделения абстракции от имплементации. Как уже говорилось, adapter служит для того, чтобы адаптировать существующий код под какой-то другой интерфейс, в то время как bridge полезен, если планируются разные варианты имплементаций (пример, Symfony Doctrine Bridge).

Composite. Относительно простой паттерн, используется для того, чтобы обработать набор объектов одинаковым образом (в примере вызов метода render). Можно представить (примитивно, для себя) как множество объектов, по которым происходит итерация и у каждого такого объекта вызывается один и тот же метод. Пример - рендеринг форм у Symfony.

DataMapper. Используется в качестве слоя, осуществляющего двустороннюю передачу данных между приложением и хранилищем. Но, в отличии от ActiveRecord, класс, представляющий данные, не знает, что с этими данными нужно делать. Этим занимается другой класс (UserMapper в примере). Пример: Doctrine ORM.

Decorator. Используется, если нужно динамически добавлять новую функциональность в инстанс. Важный момент - классы, расширяющие функциональность по своей логике должны имплементировать тот же интерфейс, что и сам декоратор. В примерах в методах расширений сначала вызываются методы декоратора, а потом уже дополнительная обработка.

Dependency Injection. Пусть у нас есть некий класс (Connection, следуя примеру). Для работы его инстанса нужен объект Parameters. Очевидно, мы могли бы просто создать его и настроить в конструкторе Connection. Но согласно паттерну Dependency Injection, внедрить зависимость, то есть заинжектить инстанс, Parameters в Connection должна какая-то третья сущность (Service Container, к примеру). Используется для большей гибкости приложений, а также делает код логичней, при удачном использовании уменьшит потребление памяти. К примеру, Parameters может существовать в единственном инстансе, вместе того, чтобы в каждом обджекте создавать свой. Пример - Symfony Service Container

Facade. Предназначен для упрощения манипуляции с объектом, или, по другому, сокрытия реализации, для предоставления удобного интерфейса конечного пользователя класса/либы.

FluentInterface. Делает код удобно читаемым и интуитивно понятным. Реализует цепочку вызовов. Для этого каждый вызываемый метод возвращает $this. Примеры: Symfony Query Builder, jQuery.

Proxy. Довольно распространенная техника. Например, вы просто хотите расширить класс (Record), но чтобы при этом выполнилась логика родителя. Для этого в RecordProxy, переопределяя метод, просто еще и вызываем родительский.

Registry. Реализует хранилище. Здесь все просто: Есть методы set($key, $value) и get($key) для хранения и получения инстансов по ключу.