Сайт вебмастера

Шаблоны проектирования для PHP

21-06-2019Время чтения ~ 6 мин.PHP/ООП 19824

Начну небольшой цикл статей посвященных шаблонам проектирования с реализацией на PHP. Постепенно эту запись буду редактировать, чтобы собрать все ссылки скопом.

Все шаблоны я писал сам, но, естественно, ориентировался на существующий код и описания (почти всё с википедии и книг по Java). В основном, конечно это Java и C#, поскольку паттерны были придуманы для «больших» языков, а реализации в PHP лишь пытаются повторить их код. Это одна из проблем, поскольку PHP сам по себе уже имеет ряд готовых решений где использование шаблона просто не имеет смысла. Например шаблон Prototype (Прототип) реализуется через clone и ничего придумывать не нужно. Или шаблон Iterator (Итератор) по сути сводится к перебору массива, поскольку массив в PHP — универсальная структура и может содержать не только простые данные, вроде строк и чисел, но и сложные — объекты. Конечно, можно перевести код этих шаблонов с Java на PHP, но большого смысла это иметь не будет, разве что для теоретического изучения.

Шаблонов проектирования много и разобраться в них сходу не получится. Мои варианты шаблонов имеют пару особенностей. Первая — это реальный исполняемый код. То есть вы можете запустить на сервере index.php и получите результат в браузер. Сам шаблон и вся его работа размещена в одном файле для удобства (чтобы не прыгать по нескольким файлам).

Вторая особенность — я постарался убрать всю абстракцию и оставить только тот код, который имеет непосредственное отношение к алгоритму шаблона. К сожалению, почти все существующие примеры и описания паттернов проектирования описывают какую-то жуткую абстракцию реального мира: то уточки, то пиццерии, то мебельные фабрики, то мастера-строители. Это всё интересно читать, но когда дело доходит до программирования, то возникают сложности перенести эту абстракцию на свою реальную задачу: просто неясно как именно эти уточки могут помочь в написании, например роутинга или логирования?

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

Еще часто приводится излишне усложненный код, например типы входных параметров, или лишние интерфейсы, потому что так сделано в примерах на Java. В данном случае — это излишний код, который усложняет понимание сути паттерна.

Ну и самое неприятное, это то, что в Сети порой встречаются авторы, которые приводят ошибочные описания и примеры паттернов. Ну например на одном известном сайте есть утверждение, что Facade (Фасад) не должен сам инстанцировать классы, а должен их принимать как параметры. Это не просто ложное утверждение, а просто противоречит самой сути Фасада.

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

Какие бывают паттерны

Первое, что нужно понять — это типы паттернов. Они делятся на

  • Основные (Fundamental)
  • Порождающие (Creational)
  • Структурные (Structural)
  • Поведенческие (Behavioral)

Не нужно пытаться запоминать какой шаблон к какой группе относится. Достаточно лишь понять их основное назначение, а дальше можно будет ориентироваться уже по ходу дела. Более того, многие шаблоны очень похожи между собой и различие между ними будет в каких-то мелких деталях.

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

Порождающие шаблоны

Эта группа шаблонов решает задачу инстанцирования (создания) других объектов. Например есть множество мелких классов, которые будут использоваться в одном большом. Или какой-то класс может использовать методы либо одного, либо другого класса: при создании это указывается в виде параметра.

Или, например класс, который имеет определенное поведение при создании: это Singleton (Одиночка) или Multiton (Пул одиночек).

Структурные шаблоны

Эти паттерны создают связи между классами. Например Facade (Фасад) объединяет в себе несколько классов, и предоставляя свой более простой метод обращения.

Поведенческие шаблоны

Самые сложные шаблоны, которые определяют алгоритмы и взаимодействие между классами. Например Strategy (Стратегия) и Template method (Шаблонный метод) задают алгоритмы работы. А Observer (Наблюдатель) создает взаимосвязь между несколькими классами.

Где и как использовать паттерны

На самом деле мы их и так постоянно используем, только многие не знают как они называются. :-) Это напоминает игру Бендера из «12 стульев»:

На третьем ходу выяснилось, что гроссмейстер играет восемнадцать испанских партий. В остальных двенадцати черные применили хотя и устаревшую, но довольно верную защиту Филидора. Если б Остап узнал, что он играет такие мудреные партии и сталкивается с такой испытанной защитой, он крайне бы удивился. Дело в том, что великий комбинатор играл в шахматы второй раз в жизни.

Сами по себе паттерны не сделают ваш код лучше, но в некоторых случаях помогут решить стоящие задачи. Всё дело в алгоритмах. Я рекомендую начинать изучение паттернов с их демо-примера (он в конце каждого файла), чтобы понять как именно «запускается» паттерн.

Например многие паттерны «выполняются» очень просто:

$f = new Facade();
$f->start();

Абсолютно ничего необычного, а значит суть этого паттерна спрятана в самом классе.

$decoratedComponent = new Decorator(new Component());
$decoratedComponent->operation();

Здесь сложнее, поскольку мы видим, что один класс принимает другой в виде аргумента.

$a = CommonAbstract::initial('Class1');
$a->run();

Здесь мы видим статический класс, который инстанцирует другой просто по имени.

То есть шаблоны используют какой-то вариант инстанцирования других классов: либо где-то внутри себя, либо принимают заранее подготовленный объект. Когда вы будете решать какой шаблон применить в своей работе, то в первую очередь подумайте как именно нужно инстанцировать классы. Потому что в одном случае вы получаете большую гибкость (за счет замены классов), а во втором можете инкапсулировать всю логику и спрятать весь сложный код, предоставив какие-то простые публичные методы.

Список шаблонов проектирования

Похожие записи