Шаблоны проектирования предназначены только для архитекторов Java — по крайней мере, вы, возможно, всегда так думали. На самом деле шаблоны проектирования полезны всем. Если эти инструменты не являются прерогативой «архитектурных астронавтов», то что же они собой представляют? Почему они полезны в приложениях PHP? В этой статье объясняются эти проблемы.
Книга «Шаблоны проектирования» познакомила сообщество разработчиков программного обеспечения с шаблонами проектирования. Авторами книги являются Эрих Гамма, Ричард Хелм, Ральф Джонсон и Джон Влиссайдс Дизайн (широко известный как «Банда четырех»). Основные концепции представленных шаблонов проектирования очень просты. После многих лет практики разработки программного обеспечения Гамма и другие обнаружили определенные закономерности с фиксированными проектами, во многом подобно тому, как архитекторы проектируют дома и здания, разрабатывая шаблоны того, где должна быть ванная комната или как должна быть построена кухня. Использование этих шаблонов или шаблонов проектирования означает более быстрое проектирование более качественных зданий. Та же концепция применима и к программному обеспечению.
Шаблоны проектирования не только представляют собой полезный способ ускоренной разработки надежного программного обеспечения, но и позволяют удобно инкапсулировать большие идеи. Например, вы можете сказать, что вы пишете систему обмена сообщениями, обеспечивающую слабую связь, или вы можете сказать, что вы пишете шаблон с именем Observer.
Продемонстрировать ценность шаблонов на небольших примерах очень сложно. Часто это кажется излишним, поскольку шаблоны действительно работают в больших базах кода. В этой статье не демонстрируется большое приложение, поэтому вам нужно подумать о том, как применить принципы примера в вашем собственном большом приложении, а не о самом коде, продемонстрированном в этой статье. Это не означает, что вам не следует использовать шаблоны в небольших приложениях. Многие хорошие приложения начинаются с небольших приложений и постепенно превращаются в большие, поэтому нет причин не использовать такие надежные методы кодирования. Теперь, когда вы понимаете шаблоны проектирования и их полезность, давайте взглянем на пять наиболее часто используемых шаблонов в PHP V5.
Заводской образец
Первоначально в книге «Шаблоны проектирования» многие шаблоны проектирования поощряли использование слабой связи. Чтобы понять эту концепцию, лучше всего поговорить о трудном пути, который проходят многие разработчики, работая над большими системами. Когда вы меняете один фрагмент кода, могут возникнуть проблемы, а в других частях системы, которые, как вы когда-то считали, совершенно не связанными, могут возникать каскадные сбои.
Проблема в жесткой связи. Функции и классы в одной части системы сильно зависят от поведения и структуры функций и классов в других частях системы. Вам нужен набор шаблонов, которые позволят этим классам взаимодействовать друг с другом, но вы не хотите тесно связывать их вместе, чтобы избежать блокировки. В больших системах большая часть кода зависит от нескольких ключевых классов. Трудности могут возникнуть, когда эти классы необходимо изменить. Например, предположим, что у вас есть класс User, который читает данные из файла. Вы хотите изменить его на другой класс, который читает из базы данных, однако весь ваш код ссылается на исходный класс, который читает из файла. В это время будет очень удобно использовать заводской режим.
Фабричный шаблон — это класс, который имеет определенные методы, создающие за вас объекты. Вы можете использовать фабричные классы для создания объектов без прямого использования new. Таким образом, если вы хотите изменить тип создаваемого объекта, вам нужно всего лишь изменить фабрику. Весь код, использующий эту фабрику, автоматически изменяется.
В листинге 1 показан пример фабричного класса. Серверная часть уравнения состоит из двух частей: базы данных и набора страниц PHP, которые позволяют вам добавлять отзывы, запрашивать список отзывов и получать статьи, связанные с конкретным отзывом.
Листинг 1. Factory1.php
<?php интерфейс IUser { функция getName(); }
класс User реализует IUser { публичная функция __construct($id) { }
публичная функция getName() { вернуть «Джек»; } }
класс UserFactory { общедоступная статическая функция Create($id) { вернуть нового пользователя ($id); } }
$uo = UserFactory::Create(1); echo($uo->getName()."n" ); ?> |
Интерфейс IUser определяет, какие действия должен выполнять пользовательский объект. Реализация IUser называется User, а фабричный класс UserFactory создает объекты IUser. Эти отношения можно представить с помощью UML на рисунке 1.
![](https://images.downcodes.com/u/info_img/2009-06/01/5z80255940ov.jpg) Рис. 1. Фабричный класс и связанный с ним интерфейс IUser и пользовательский класс. |
Если вы запустите этот код в командной строке с помощью интерпретатора PHP, вы получите следующие результаты:
Тестовый код запросит объект User у фабрики и выведет результат метода getName.
Существует вариант фабричного шаблона, в котором используются фабричные методы. Эти общедоступные статические методы класса создают объекты этого типа. Этот метод полезен, если важно создавать объекты такого типа. Например, предположим, что вам нужно создать объект, а затем установить ряд свойств. Эта версия шаблона фабрики инкапсулирует процесс в одном месте, поэтому вам не нужно копировать сложный код инициализации и вставлять его в базу кода. В листинге 2 показан пример использования фабричного метода.
Листинг 2. Factory2.php
<?php интерфейс IUser { функция getName(); }
класс User реализует IUser { общедоступная статическая функция Load($id) { вернуть нового пользователя ($id); }
публичная статическая функция Create( ) { вернуть нового пользователя (ноль); }
публичная функция __construct($id) { }
публичная функция getName() { вернуть «Джек»; } }
$uo = Пользователь::Load( 1 ); echo($uo->getName()."n" ); ?> |
Этот код намного проще. Он имеет только один интерфейс IUser и класс User, реализующий этот интерфейс. Класс User имеет два статических метода для создания объектов. Эти отношения можно представить с помощью UML на рисунке 2.
![](https://images.downcodes.com/u/info_img/2009-06/01/g132h83068u1.jpg) Рисунок 2. Интерфейс IUser и пользовательский класс с фабричным методом |
Запуск сценария в командной строке дает те же результаты, что и в листинге 1, а именно:
Как упоминалось выше, иногда такие режимы могут показаться излишними в небольших средах. Однако лучше всего изучить эту надежную форму программирования, которую можно применять к проектам любого размера.
Режим одного элемента
Некоторые ресурсы приложения являются эксклюзивными, поскольку существует только один ресурс этого типа. Например, соединения с базой данных через дескриптор базы данных являются эксклюзивными. Вы хотите использовать дескриптор базы данных совместно с вашим приложением, поскольку это требует дополнительных затрат при поддержании открытого или закрытого соединения, особенно в процессе получения одной страницы.
Одноэлементный режим удовлетворяет этому требованию. Если приложение одновременно содержит один и только один объект, то этот объект является синглтоном. Код в листинге 3 показывает одиночный элемент подключения к базе данных в PHP V5.
Листинг 3. Singleton.php
<?php require_once("DB.php");
класс DatabaseConnection { публичная статическая функция get() { статический $db = ноль; если ($db == ноль) $db = новое соединение с базой данных(); вернуть $дб; }
частный $_handle = ноль; частная функция __construct() { $dsn = 'mysql://root:password@localhost/photos'; $this->_handle =& DB::Connect( $dsn, array() ); }
дескриптор публичной функции() { вернуть $this->_handle; } }
print( "Handle = ".DatabaseConnection::get()->handle()."n" ); print( "Handle = ".DatabaseConnection::get()->handle()."n" ); ?> |
Этот код показывает один класс с именем DatabaseConnection. Вы не можете создать собственное соединение с базой данных, поскольку конструктор является частным. Но используя статический метод get, вы можете получить только один объект DatabaseConnection. UML для этого кода показан на рисунке 3.
![](https://images.downcodes.com/u/info_img/2009-06/01/z8384803w24o.jpg) Рисунок 3. Один элемент подключения к базе данных |
Лучшим доказательством является то, что дескриптор базы данных, возвращаемый методом handle, один и тот же при обоих вызовах. Вы можете запустить код в командной строке, чтобы увидеть это.
% php синглтон.php Дескриптор = идентификатор объекта № 3 Дескриптор = идентификатор объекта № 3 % |
Два возвращаемых дескриптора являются одним и тем же объектом. Если вы используете один элемент подключения к базе данных во всем своем приложении, вы можете повторно использовать один и тот же дескриптор везде.
Вы можете использовать глобальные переменные для хранения дескрипторов базы данных, однако этот подход подходит только для небольших приложений. В более крупных приложениях избегайте использования глобальных переменных и используйте объекты и методы для доступа к ресурсам. Шаблон наблюдателя
Паттерн Observer дает вам еще один способ избежать жесткой связи между компонентами. Схема очень проста: объект делает себя наблюдаемым, добавляя метод, который позволяет другому объекту — наблюдателю — зарегистрироваться. Когда наблюдаемый объект изменяется, он отправляет сообщения зарегистрированным наблюдателям. Эти наблюдатели используют эту информацию для выполнения операций независимо от наблюдаемого объекта. В результате объекты могут разговаривать друг с другом, даже не понимая, почему. Простой пример — список пользователей в системе. Код в листинге 4 отображает список пользователей и при добавлении пользователя отправляет сообщение. Этот список можно просмотреть с помощью наблюдателя журнала, который отправляет сообщения при добавлении пользователя.
Листинг 4. Observer.php
<?php интерфейс IObserver { функция onChanged($sender, $args); }
интерфейс IObservable { функция addObserver($observer); }
класс UserList реализует IObservable { частный $_observers = массив();
публичная функция addCustomer($name) { foreach($this->_observers как $obs) $obs->onChanged($this, $name); }
публичная функция addObserver($observer) { $this->_observers []= $observer; } }
класс UserListLogger реализует IObserver { публичная функция onChanged($sender, $args) { echo( "'$args' добавлен в список пользователейn" ); } }
$ul = новый UserList(); $ul->addObserver(новый UserListLogger()); $ul->addCustomer("Джек"); ?> |
Этот код определяет четыре элемента: два интерфейса и два класса. Интерфейс IObservable определяет объекты, которые можно наблюдать, а UserList реализует этот интерфейс, чтобы зарегистрироваться как наблюдаемые. Список IObserver определяет, как стать наблюдателем. UserListLogger реализует интерфейс IObserver. Эти элементы показаны в UML на рисунке 4.
![](https://images.downcodes.com/u/info_img/2009-06/01/l328iq239e03.jpg) Рисунок 4. Наблюдаемый список пользователей и регистратор событий списка пользователей. |
Если вы запустите его из командной строки, вы увидите следующий вывод:
% php-наблюдатель.php «Джек» добавлен в список пользователей % |
Тестовый код создает UserList и добавляет к нему наблюдателя UserListLogger. Затем добавьте потребителя и уведомите UserListLogger об этом изменении.
Очень важно понимать, что UserList не знает, что будет делать регистратор. Может быть один или несколько прослушивателей, выполняющих другие операции. Например, у вас может быть наблюдатель, который отправляет сообщение новым пользователям, приветствуя их в системе. Ценность такого подхода в том, что UserList игнорирует все объекты, которые от него зависят, и фокусируется в первую очередь на ведении списка пользователей и отправке сообщений при изменении списка.
Этот шаблон не ограничивается объектами в памяти. Это основа систем запроса сообщений на основе базы данных, используемых в более крупных приложениях. режим цепочки команд
Шаблон цепочки команд основан на слабосвязанных темах, отправляющих сообщения, команды, запросы или что-либо еще через набор обработчиков. Каждый обработчик принимает собственное решение о том, может ли он обработать запрос. Если это возможно, запрос обрабатывается и процесс останавливается. Вы можете добавлять или удалять обработчики из системы, не затрагивая другие обработчики. В листинге 5 показан пример этого шаблона.
Листинг 5. Chain.php
<?php интерфейсICommand { функция onCommand($name, $args); }
класс CommandChain { частные $_commands = массив();
публичная функция addCommand($cmd) { $this->_commands []= $cmd; }
публичная функция runCommand($name, $args) { foreach($this->_commands как $cmd) { if ($cmd->onCommand($name, $args)) возвращаться; } } }
класс UserCommand реализует ICommand { публичная функция onCommand($name, $args) { if ($name!= 'addUser') вернет false; echo( "UserCommand обрабатывает 'addUser'n"); вернуть истину; } }
класс MailCommand реализует ICommand { публичная функция onCommand($name, $args) { if ($name!= 'mail') вернет false; echo("MailCommand обрабатывает 'mail'n"); вернуть истину; } }
$cc = новая CommandChain(); $cc->addCommand(новая UserCommand()); $cc->addCommand(новая MailCommand()); $cc->runCommand('addUser', null); $cc->runCommand('mail', null); ?> |
Этот код определяет класс CommandChain, который поддерживает список объектов ICommand. Оба класса могут реализовать интерфейс ICommand — один отвечает на запросы почты, а другой — на добавление пользователей. На рисунке 5 показан UML.
![](https://images.downcodes.com/u/info_img/2009-06/01/3hz26s8v118w.jpg) Рисунок 5. Цепочка команд и связанные с ней команды |
Если вы запустите сценарий, содержащий тестовый код, вы получите следующий результат:
% php-цепочка.php Пользовательская команда, обрабатывающая «addUser» MailCommand обрабатывает почту % |
Код сначала создает объект CommandChain и добавляет к нему два экземпляра объекта команды. Затем запустите две команды, чтобы увидеть, кто ответил на команды. Если имя команды совпадает с UserCommand или MailCommand, код завершается ошибкой и никаких действий не происходит. Шаблон цепочки команд полезен при создании масштабируемой архитектуры обработки запросов, и с его помощью можно решить многие проблемы. шаблон стратегии
Последний шаблон проектирования, который мы рассмотрим, — это шаблон стратегии. В этом шаблоне алгоритмы извлекаются из сложных классов и поэтому могут быть легко заменены. Например, если вы хотите изменить рейтинг страниц в поисковых системах, режим «Стратегия» — хороший выбор. Подумайте о частях поисковой системы: одна просматривает страницы, другая ранжирует каждую страницу, а третья сортирует результаты на основе рейтинга. В сложных примерах все эти части относятся к одному классу. Используя шаблон «Стратегия», вы можете поместить часть расположения в другой класс, чтобы изменить способ расположения страницы, не затрагивая остальную часть кода поисковой системы.
В качестве более простого примера в листинге 6 показан класс списка пользователей, который позволяет найти набор пользователей на основе набора политик plug-and-play.
Листинг 6. Strategy.php
<?php интерфейс IStrategy { функция фильтра ($ запись); }
класс FindAfterStrategy реализует IStrategy { личное $_name;
публичная функция __construct($name) { $this->_name = $name; }
фильтр общедоступной функции ($ запись) { return strcmp($this->_name, $record) <= 0; } }
класс RandomStrategy реализует IStrategy { фильтр общедоступной функции ($ запись) { вернуть rand(0, 1) >= 0,5; } }
класс UserList { частный $_list = массив();
публичная функция __construct($names) { если ($names!= null) { foreach($names как $name) { $this->_list []= $name; } } }
публичная функция add($name) { $this->_list []= $name; }
публичная функция find($filter) { $recs = массив(); foreach($this->_list как $user) { если ($filter->filter($user)) $recs []= $user; } вернуть $рекс; } }
$ul = новый UserList(массив("Энди", "Джек", "Лори", "Меган" )); $f1 = $ul->find(new FindAfterStrategy("J")); print_r($f1);
$f2 = $ul->find(new RandomStrategy()); print_r($f2); ?> |
![](https://images.downcodes.com/u/info_img/2009-06/01/20745fk4j88c.jpg) Рисунок 6. Список пользователей и политика, используемая для выбора пользователей |
Класс UserList — это оболочка массива имен. Он реализует метод find, который использует одну из нескольких стратегий для выбора подмножества этих имен. Эти стратегии определяются интерфейсом IStrategy, который имеет две реализации: одна выбирает пользователя случайным образом, а другая выбирает все имена после указанного имени. Когда вы запустите тестовый код, вы получите следующий результат:
%php стратегия.php Множество ( [0] => Джек [1] =>Лори [2] =>Меган ) Множество ( [0] => Энди [1] =>Меган ) % |
Тестовый код запускает один и тот же список пользователей для обеих стратегий и отображает результаты. В первом случае стратегия ищет любое имя, следующее за J, поэтому вы получите Джека, Лори и Меган. Вторая стратегия выбирает имена случайным образом, каждый раз получая разные результаты. В данном случае результаты — Энди и Меган.
Шаблон «Стратегия» идеально подходит для сложных систем управления данными или систем обработки данных, которым требуется высокая степень гибкости в фильтрации, поиске или обработке данных.
Заключение
В этой статье представлены лишь некоторые из наиболее распространенных шаблонов проектирования, используемых в приложениях PHP. Дополнительные шаблоны проектирования продемонстрированы в книге «Шаблоны проектирования». Не позволяйте мистике архитектуры сбить вас с толку. Шаблоны — замечательная идея, которая работает на любом языке программирования и на любом уровне навыков.