Статьи

Когда и почему шаблоны проектирования PHP

Есть много статей, которые объясняют, что такое шаблоны проектирования и как их реализовать; Сети не нужна еще одна из этих статей! Вместо этого в этой статье мы будем больше обсуждать, когда и почему , а не что и как .

Я представлю различные ситуации и варианты использования шаблонов, а также предоставлю краткие определения, чтобы помочь тем из вас, кто не очень знаком с этими конкретными шаблонами. Давайте начнем.

Переизданный учебник

Каждые несколько недель мы пересматриваем некоторые из любимых постов нашего читателя на протяжении всей истории сайта. Этот учебник был впервые опубликован в октябре 2012 года.

В этой статье рассматриваются некоторые из различных шаблонов гибкого проектирования , документированных в книгах Роберта К. Мартина. Эти шаблоны являются современной адаптацией оригинальных шаблонов проектирования, определенных и задокументированных «Бандой четырех» в 1994 году. Шаблоны Мартина представляют собой гораздо более свежий взгляд на шаблоны GoF, и они лучше работают с современными методами и проблемами программирования. Фактически, около 15% исходных шаблонов были заменены более новыми шаблонами, а остальные шаблоны были слегка модернизированы.


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

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

Почему: фабрики помогают содержать логику создания объектов в одном месте. Они также могут нарушать зависимости для облегчения слабой связи и внедрения зависимостей для лучшего тестирования.


Существует два часто используемых шаблона для извлечения информации из постоянного уровня или внешнего источника данных.

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

Когда: когда вам нужно получить или сохранить информацию.

Почему: он предлагает простой общедоступный интерфейс для сложных операций сохранения. Он также инкапсулирует знания о постоянстве и отделяет бизнес-логику от логики постоянства.

Фактически, шаблон шлюза — это просто конкретная реализация другого шаблона проектирования, который мы вскоре обсудим: шаблон адаптера.

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

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

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

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

Шаблон активного объекта также играл роль в ранних многозадачных системах.

Ладно ладно. Это здорово и все такое, но как мы можем найти объекты, которые нам нужны для создания?

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

Шаблон хранилища отличается от других шаблонов; он существует как часть Domain Driven Design (DDD) и не включен как часть книги Роберта К. Мартина.

Когда: Вам нужно создать несколько объектов на основе критериев поиска или когда вам нужно сохранить несколько объектов в слое постоянства.

Почему: разрешить клиентам, которым нужны конкретные объекты, работать с общим и хорошо изолированным языком запросов и персистентности. Он удаляет еще больше кода, связанного с созданием, из бизнес-логики.

Но что, если хранилище не может найти объекты? Одним из вариантов будет возвращение значения NULL , но это имеет два побочных эффекта:

  • Если вы попытаетесь вызвать метод для такого объекта, он выдаст отказанный запрос.
  • Это заставляет вас включать в ваш код многочисленные проверки на if(is_null($param)) return; ( if(is_null($param)) return; ).

Лучший подход — вернуть null объект.

Нулевой объект реализует тот же интерфейс ваших других объектов, но члены объекта возвращают нейтральное значение. Например, метод, который возвращает строку, будет возвращать пустую строку; другой член, возвращающий числовое значение, вернет ноль. Это вынуждает вас реализовывать методы, которые не возвращают значимые данные, но вы можете использовать эти объекты, не беспокоясь об отказе по завещанию или засорении вашего кода нулевыми проверками.

Когда: Вы часто проверяете на null или отказываете в завещаниях.

Почему: это может внести ясность в ваш код и заставить вас больше думать о поведении ваших объектов.

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

Когда: когда вам нужно выполнить множество операций для подготовки объектов к использованию.

Почему: для перемещения сложности от потребляющего кода к созданию кода.

Звучит хорошо, не правда ли? На самом деле, это довольно полезно во многих ситуациях. Шаблон команды широко используется для реализации транзакций. Если вы добавляете простой метод undo() к объекту команды, он может отслеживать все транзакции отмены, которые он выполнил, и при необходимости отменять их.

Итак, теперь у вас есть десять (или более) объектов команд, и вы хотите, чтобы они выполнялись одновременно. Вы можете собрать их в активный объект.

На простой и интересный активный объект возлагается только одна обязанность: вести список командных объектов и запускать их.

Когда: несколько похожих объектов должны быть выполнены с помощью одной команды.

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

Активный объект удаляет каждую команду из своего списка после выполнения команды; Это означает, что вы можете выполнить команду только один раз. Некоторые реальные примеры активного объекта:

Шаблоны дизайна здесь для решения проблем.

  • Корзина покупок — выполнение команды buy() для каждого товара удаляет их из корзины.
  • Финансовые транзакции. Группировка транзакций в единый список и выполнение их простым вызовом активного объекта менеджера списка приведет к удалению транзакций из очереди.

Шаблон активного объекта также играл роль в ранних многозадачных системах. Каждый объект внутри активного объекта будет сохранять ссылку на активный объект. Они выполняли часть своей работы, а затем возвращались в очередь. Даже в современных системах вы можете использовать активный объект, чтобы другие объекты работали, пока вы ожидаете ответа от другого приложения.


Я уверен, что вы слышали большое обещание объектно-ориентированного программирования: повторное использование кода. Ранние пользователи ООП предполагали использование универсальных библиотек и классов в миллионах различных проектов. Ну, этого никогда не было.

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

Когда: устраните дублирование простым способом.

Почему: дублирование и гибкость не проблема.

Но гибкость это приятно. Что если мне это действительно нужно?

Когда: Гибкость и возможность повторного использования важнее, чем простота.

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

Например, вы можете создать общий Calculator а затем использовать различные объекты ComputationStrategy для выполнения вычислений. Это умеренно используемый шаблон, и он наиболее эффективен, когда вам нужно определить много условных поведений.


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

Фасад — это, по сути, API — приятный и ориентированный на клиента интерфейс. Когда клиент вызывает один из этих приятных методов, фасад делегирует серию вызовов классам, которые он скрывает, чтобы предоставить клиенту необходимую информацию или желаемый результат.

Когда: чтобы упростить API или умышленно скрыть внутреннюю бизнес-логику.

Почему: Вы можете независимо управлять API, реальными реализациями и логикой.

Контроль хорош, и много раз вам нужно выполнить задачу, когда что-то меняется. Пользователи должны быть уведомлены, красные светодиоды должны мигать, должен прозвучать сигнал тревоги … Вы понимаете.

Популярный каркас Laravel прекрасно использует Фасадный паттерн.

Нулевой объект реализует тот же интерфейс, что и другие ваши объекты.

Шаблон наблюдателя предлагает простой способ наблюдения за объектами и выполнения действий при изменении условий. Существует два типа реализации наблюдателя:

  • Опрос — Объекты принимают подписчиков. Абоненты наблюдают за объектом и уведомляются о конкретных событиях. Подписчики просят наблюдаемые объекты для получения дополнительной информации, чтобы предпринять действие.
  • Push — Подобно методу опроса, объекты принимают подписчиков, и подписчики уведомляются, когда происходит событие. Но когда происходит уведомление, наблюдатель также получает подсказку, что наблюдатель может действовать.

Когда: Предоставить систему уведомлений внутри вашей бизнес-логики или во внешний мир.

Почему: шаблон предлагает способ передачи событий любому количеству различных объектов.

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

Шаблон наблюдателя может быть расширен с помощью шаблона посредника. Этот шаблон принимает два объекта в качестве параметров. Посредник присоединяется к первому параметру, и когда происходит изменение с наблюдаемым объектом, посредник решает, что делать со вторым объектом.

Когда: затронутые объекты могут не знать о наблюдаемых объектах.

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


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

Синглтон — это объект, имеющий частный конструктор и открытый метод getInstance() . Этот метод гарантирует, что существует только один экземпляр объекта.

Когда: Вам нужно достичь сингулярности и хотеть кроссплатформенное, лениво оцененное решение, которое также предлагает возможность создания посредством деривации.

Зачем: предлагать единую точку доступа при необходимости.

Другой подход к сингулярности — это шаблон проектирования с одним государством. Это решение использует хитрость, предлагаемую объектно-ориентированными языками программирования. Он имеет динамические публичные методы, которые получают или устанавливают значения статических приватных переменных. Это, в свою очередь, гарантирует, что все экземпляры таких классов имеют одинаковые значения.

Когда: прозрачность, производность и полиморфизм предпочтительны вместе с сингулярностью.

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

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


Шаблон репозитория весьма полезен для реализации методов поиска …

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

К сожалению, это не так просто в мире программирования. У вас есть класс Switch класс Light . Если ваш Switch использует Light , как он может использовать Fan ?

Легко! Скопируйте и вставьте Switch и измените его для использования Fan . Но это дублирование кода; это эквивалентно покупке другого переключателя для вентилятора. Вы можете расширить Switch до FanSwitch и использовать этот объект. Но что, если вы хотите использовать Button или RemoteControl вместо Switch ?

Это самый простой шаблон из всех когда-либо изобретенных. Он использует только интерфейс. Это все, но есть несколько разных реализаций.

Когда: Вам нужно соединять объекты и поддерживать гибкость.

Почему: потому что это самый простой способ достижения гибкости при соблюдении принципа инверсии зависимостей и принципа открытого закрытия.

PHP динамически типизируется. Это означает, что вы можете опустить интерфейсы и использовать разные объекты в одном и том же контексте — рискуя получить отказ по наследству. Тем не менее, PHP также позволяет определять интерфейсы, и я рекомендую вам использовать эту замечательную функциональность, чтобы прояснить смысл вашего исходного кода.

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

Шаблон адаптера просто создает соответствие между бизнес-логикой и чем-то еще. Мы уже видели такой паттерн в действии: паттерн шлюза.

Когда: Вам необходимо создать соединение с уже существующим и потенциально изменяющимся модулем, библиотекой или API.

Почему: чтобы ваша бизнес-логика опиралась только на общедоступные методы, предлагаемые адаптером, и позволяла легко менять другую сторону адаптера.

Если какой-либо из вышеперечисленных шаблонов не соответствует вашей ситуации, вы можете использовать …

Это очень сложная модель. Мне лично это не нравится, потому что обычно проще придерживаться другого подхода. Но для тех особых случаев, когда другие решения терпят неудачу, вы можете рассмотреть шаблон моста.

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

Почему: предлагать повышенную гибкость за счет значительной сложности.


Учтите, что у вас есть скрипт с похожими командами, и вы хотите сделать один вызов для их запуска. Подождите! Разве мы не видели что-то подобное раньше? Шаблон активного объекта?

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

Когда: Вы должны применить действие к нескольким подобным объектам.

Почему: чтобы уменьшить дублирование и упростить вызов похожих объектов.

Вот пример: у вас есть приложение, способное создавать и размещать Orders . Предположим, у вас есть три заказа: $order1 , $order2 и $order3 . Вы можете вызывать place() для каждого из них, или вы можете содержать эти ордера в объекте $compositeOrder объект и вызывать его метод place() . Это, в свою очередь, вызывает метод place() для всех содержащихся объектов Order .


Шлюзы только получают и сохраняют необработанные данные.

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

Но мы все знаем, что операторы switch...case менее желательны, потому что они вызывают нежелательное сильное разветвление наших объектов. Поэтому забудьте оператор switch...case и рассмотрите шаблон состояния. Шаблон состояния состоит из нескольких объектов: объекта для координации вещей, интерфейса, представляющего абстрактное состояние, а затем нескольких реализаций — по одной для каждого состояния. Каждое состояние знает, какое состояние следует за ним, и оно может уведомить координирующий объект, чтобы установить его новое состояние на следующее в строке.

Когда: FSM-подобная логика должна быть реализована.

Почему: для устранения проблем оператора switch...case и для лучшего понимания смысла каждого отдельного состояния.

Диспенсер для продуктов питания может иметь main класс, который имеет ссылку на state класс. Возможные классы состояний могут быть примерно такими: WaitingForCoin , WaitingForCoin , SelectedProduct , WaitingForConfirmation , DeliveringProduct , ReturningChange . Каждое состояние выполняет свою работу и создает следующий объект состояния для отправки в класс координатора.


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

Шаблон декоратора может помочь в этих ситуациях. Это очень просто: взять существующий функционал и добавить к нему. Это достигается путем расширения исходного класса и предоставления новых функций во время выполнения. Старые клиенты продолжают использовать новый объект так же, как и старый, а новые клиенты будут использовать как старый, так и новый функционал.

Когда: Вы не можете изменить старые классы, но вы должны реализовать новое поведение или состояние.

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

Простой пример — печать данных. Вы печатаете некоторую информацию для пользователя в виде простого текста, но вы также хотите предоставить возможность печати в HTML. Шаблон декоратора — это одно из таких решений, которое позволяет сохранить обе функциональные возможности.

Если ваша проблема расширения функциональности отличается — скажем, у вас сложная древовидная структура объектов, и вы хотите добавить функциональность сразу ко многим узлам — простая итерация невозможна, но посетитель может быть жизнеспособным решением. Недостатком, однако, является то, что реализация шаблона посетителя требует модификации старого класса, если он не предназначен для приема посетителя.

Когда: Декоратор не подходит и допускаются некоторые дополнительные сложности.

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


Используйте шаблоны проектирования для решения ваших проблем, но только если они подходят.

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

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

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

Если вы новичок в разработке шаблонов, я надеюсь, что эта статья дала вам представление о том, как шаблоны могут быть полезны в ваших приложениях. Спасибо за прочтение!