Вы когда-либо задавались вопросом, что такое шаблоны проектирования? В этой статье будет разъяснено, почему шаблоны проектирования имеют существенное значение, и будет приведено несколько примеров на PHP, поясняющих, когда и где их следует использовать.
Шаблоны проектирования — это допускающие многократное использование оптимизированные решения проблем программирования, с которыми мы сталкиваемся каждый день. Шаблон проектирования — это не класс или библиотека, которые мы можем просто вставить в нашу систему. Он — много больше. Это — некоторый шаблон, который должен быть реализован в надлежащей ситуации. Он не зависит от языка. Хороший шаблон проектирования должен быть таким, чтобы его можно было использовать с большинством языков (если не со всеми) в зависимости от характеристик языка. Чрезвычайно важно то, что любой шаблон проектирования необходимо использовать очень осторожно — если он применён в ненадлежащем месте, то его действие может быть разрушительным и породить много проблем для вас. Однако применённый в нужном месте в нужное время он может стать вашим спасителем.
Есть три основных типа шаблонов проектирования:
• структурный
• порождающий
• поведенческий
Структурные шаблоны, в общем случае, имеют дело с отношениями между объектами, облегчая их совместную работу.
Порождающие шаблоны обеспечивают механизмы инстанцирования, облегчая создание объектов способом, который наиболее соответствует ситуации.
Поведенческие шаблоны используются в коммуникации между объектами, делая её более лёгкой и гибкой.
Почему их следует использовать?
Шаблоны проектирования в принципе являются хорошо продуманными решениями проблем программирования. Многие программисты уже сталкивались ранее с этими проблемами и использовали для преодоления эти «решения». Встречая какую-то проблему, зачем заново искать решение, когда можно применить уже проверенное?
Пример
Давайте представим, что вам поручено создать способ объединить два класса, которые выполняют два разных действия в зависимости от ситуации. Эти два класса интенсивно используются существующей системой в разных местах, что затрудняет их удаление и изменение существующего кода. Дополнительно к этому изменение существующего кода требует проверки всего изменённого кода, т.к. правка такого рода в системе, построенной на разных компонентах, почти всегда вносит новые ошибки. Вместо перечисленного можно использовать вариант шаблона «Стратегия» и шаблона «Адаптер», которые могут легко обращаться с названными типами сценариев.
01 <?php
02 class StrategyAndAdapterExampleClass {
03 private $_class_one;
04 private $_class_two;
05 private $_context;
06
07 public function __construct( $context ) {
08 $this->_context = $context;
09 }
10
11 public function operation1() {
12 if( $this->_context == "context_for_class_one" ) {
13 $this->_class_one->operation1_in_class_one_context();
14 } else ( $this->_context == "context_for_class_two" ) {
15 $this->_class_two->operation1_in_class_two_context();
16 }
17 }
18 }
Довольно просто, не так ли? Теперь посмотрим внимательнее на шаблон «Стратегия».
Шаблон «Стратегия»
Изображение размещено с разрешения владельцев сайта cioinnervoice.wordpress.com
Шаблон «Стратегия» является поведенческим шаблоном проектирования, который позволяет вам решать, какой план действий должна принять программа, основываясь на определённом контексте при выполнении. Вы закладываете два различных алгоритма внутри двух классов и решаете в ходе выполнения, с какой стратегией следует работать.
В нашем примере выше стратегия устанавливается в зависимости от того, какой была переменная $context в то время, когда класс подвергался обработке. Если вы даёте ей контекст для класса_один, то будет использован класс_один и наоборот.
Замечательно, но где я могу использовать это?
Предположим, что вы в данный момент разрабатываете класс, который может или обновить или создать новую пользовательскую запись. Хотя ему требуются те же самые входы (имя, адрес, номер мобильного телефона и т.п.), но в зависимости от ситуации он должен использовать различные функции при обновлении и создании. Здесь для выполнения можно, вероятно, сразу же использовать условный переход «if-else», но как быть, если этот класс понадобится и в другом месте? Тогда нужно будет переписать полностью весь этот оператор условного перехода. Не было бы проще просто указать ваш контекст?
01 <?php
02 class User {
03
04 public function CreateOrUpdate($name, $address, $mobile, $userid = null)
05 {
06 if( is_null($userid) ) {
07 // Это значит, что пользователь ещё не существует, надо создать новую запись
08 } else {
09 // Это значит, что пользователь уже существует, необходимо обновить на базе данного идентификатора пользователя
10 }
11 }
12 }
Теперь «обычный» шаблон «Стратегия» предполагает помещение ваших алгоритмов внутри другого класса, но в данном случае другой класс был бы нерациональным решением. Помните, что вы не обязаны точно следовать шаблону. Варианты работают, пока концепция остаётся прежней, и это решает проблему.
Шаблон «Адаптер»
Изображение размещено с разрешения владельцев сайта www.uxcell.com
Шаблон «Адаптер» является структурным шаблоном проектирования, который позволяет перепрофилировать класс с другим интерфейсом, делая его доступным для системы, которая использует различные методы вызова.
Это также позволяет изменять некоторые из входов, получаемых от класса клиента, превращая его в нечто совместимое с функциями класса Adaptee.
Как можно использовать это?
Другим понятием для ссылки на класс адаптера является «обёртка», которая по существу позволяет «обернуть» действия в класс и повторно использовать эти действия в надлежащих ситуациях. Классическим примером может быть создание доменного класса для классов таблиц. Вместо вызова различных классов таблиц и последовательного вызова их функций можно вложить все эти методы в один, используя класс адаптера. Это не только позволяет вам повторно использовать любые требуемые действия, но также избавляет от необходимости переписывать код, если вам нужно использовать то же действие в другом месте.
Сравните эти две реализации:
Подход без адаптера
1 <?php
2 $user = new User();
3 $user->CreateOrUpdate( //inputs );
4
5 $profile = new Profile();
6 $profile->CreateOrUpdate( //inputs );
Если бы нам нужно было сделать это снова в другом месте или даже использовать этот код в другом проекте, то мы должны были бы набрать всё заново.
Лучше
Указанное противоположно действиям вроде приведённых ниже:
1 <?php
2 $account_domain = new Account();
3 $account_domain->NewAccount( //inputs );
В данной ситуации мы имеем обёрточный класс, который был бы нашим доменным классом Account:
01 <?php
02 class Account()
03 {
04 public function NewAccount( //inputs )
05 {
06 $user = new User();
07 $user->CreateOrUpdate( //subset of inputs );
08
09 $profile = new Profile();
10 $profile->CreateOrUpdate( //subset of inputs );
11 }
12 }
Таким образом, можно использовать домен Account снова везде, где требуется, — дополнительно вы оказываетесь в состоянии обёртывать другие классы также под вашим доменным классом.
Шаблон «Фабричный метод»
Изображение размещено с разрешения владельцев сайта www.lankanewspappers.com
Шаблон «Фабричный метод» является порождающим шаблоном проектирования, который делает именно то, что означает это слово: этот класс действует как фабрика экземпляров объектов.
Основной целью этого шаблона является вложение порождающей процедуры, которая может свести различные классы в одну функцию. Если фабричному методу обеспечить надлежащий контекст, то он будет в состоянии вернуть правильный объект.
Когда можно использовать это?
Наилучшей ситуацией для использования шаблона «Фабричный метод» является наличие нескольких различных вариантов одного объекта. Допустим, имеется класс «кнопка»; у этого класса есть различные варианты — например, ImageButton (кнопка изображения), InputButton (кнопка ввода) и FlashButton (флэш-кнопка). В зависимости от места может потребоваться создать различные кнопки — именно здесь можно использовать «фабрику» для создания кнопок для вас!
Начнём с создания наших трёх классов:
01 <?php
02 abstract class Button {
03 protected $_html;
04
05 public function getHtml()
06 {
07 return $this->_html;
08 }
09 }
10
11 class ImageButton extends Button {
12 protected $_html = "..."; //Здесь должен быть задан HTML, требуемый для вашей кнопки на базе изображения
13 }
14
15 class InputButton extends Button {
16 protected $_html = "..."; //Здесь должен быть задан HTML, требуемый для вашей нормальной кнопки (<input type="button"... />);
17 }
18
19 class FlashButton extends Button {
20 protected $_html = "..."; //Здесь должен быть задан HTML, требуемый для вашей флэш-кнопки
21 }
Теперь можно создать наш фабричный класс:
01 <?php
02 class ButtonFactory
03 {
04 public static function createButton($type)
05 {
06 $baseClass = 'Button';
07 $targetClass = ucfirst($type).$baseClass;
08
09 if (class_exists($targetClass) && is_subclass_of($targetClass, $baseClass)) {
10 return new $targetClass;
11 } else {
12 throw new Exception("The button type '$type' is not recognized.");
13 }
14 }
15 }
Полученный код можно использовать, например, так:
1 $buttons = array('image','input','flash');
2 foreach($buttons as $b) {
3 echo ButtonFactory::createButton($b)->getHtml()
4 }
Выходом должен быть HTML всех ваших типов кнопок. Таким образом, вы могли бы указать, какую кнопку создать в зависимости от ситуации, а также повторно использовать условие.
Шаблон «Декоратор»
Изображение размещено с разрешения владельцев сайта www.decoratorsdarlington.co.uk
Шаблон «Декоратор» является структурным шаблоном проектирования, который позволяет нам добавлять новое или дополнительное поведение к объекту в ходе выполнения в зависимости от ситуации.
Целью является сделать так, чтобы расширенные функции могли быть применены к одному конкретному экземпляру и в то же время иметь возможность создать оригинальный экземпляр, который не имеет этих новых функций. Это также позволяет комбинировать несколько декораторов для одного экземпляра, благодаря чему отсутствует привязка к одному декоратору для каждого экземпляра. Этот шаблон является альтернативой созданию подкласса, что относится к созданию класса, который наследует функциональность от родительского класса. В противоположность к созданию подкласса, которое добавляет поведение во время компиляции, «декорирование» позволяет добавить новое поведение в ходе выполнения, если ситуация требует этого.
Чтобы реализовать шаблон «Декоратор», можно выполнить следующие шаги:
1. Выделите оригинальный класс «Компонент» как подкласс класса «Декоратор».
2. В классе «Декоратор» добавьте указатель «Компонент» как поле.
3. Переместите «Компонент» в конструктор «Декоратора», чтобы инициализировать указатель «Компонент».
4. В классе «Декоратор» перенаправьте все методы «Компонент» на указатель «Компонент».
5. В классе «Декоратор» отмените любой метод (любые методы) «Компонент», поведение которого (которых) должно быть модифицировано.
Данные этапы размещены с разрешения владельцев сайта en.wikipedia.org/wiki/Decorator_pattern
Когда можно использовать это?
Наиболее удобно использовать шаблон «Декоратор» для объекта, требующего нового поведения, только когда на это имеется запрос по ситуации. Допустим, имеется HTML-элемент компоновки, ссылка на выход из системы, и вы желаете, чтобы она делала немного различающиеся действия, основываясь на текущей странице. Для этого можно использовать шаблон «Декоратор».
Сначала зададим различные требуемые «декорации»:
• Если мы находимся на главной странице и зарегистрированы, то эта ссылка должна быть «обёрнута» в теги h2.
• Если мы находимся на другой странице и зарегистрированы, то эта ссылка должна быть «обёрнута» в теги подчёркивания.
• Если мы зарегистрированы, то эта ссылка должна быть «обёрнута» в теги полужирного шрифта.
После задания наших «декораций» можно их запрограммировать:
01 <?php
02 class HtmlLinks {
03 //Некоторые способы, имеющиеся для всех html-ссылок
04 }
05
06 class LogoutLink extends HtmlLinks {
07 protected $_html;
08
09 public function __construct() {
10 $this->_html = "<a href="logout.php">Logout</a>";
11 }
12
13 public function setHtml($html)
14 {
15 $this->_html = $html;
16 }
17
18 public function render()
19 {
20 echo $this->_html;
21 }
22 }
23
24 class LogoutLinkH2Decorator extends HtmlLinks {
25 protected $_logout_link;
26
27 public function __construct( $logout_link )
28 {
29 $this->_logout_link = $logout_link;
30 $this->setHtml("<h2>" . $this->_html . "</h2>");
31 }
32
33 public function __call( $name, $args )
34 {
35 $this->_logout_link->$name($args[0]);
36 }
37 }
38
39 class LogoutLinkUnderlineDecorator extends HtmlLinks {
40 protected $_logout_link;
41
42 public function __construct( $logout_link )
43 {
44 $this->_logout_link = $logout_link;
45 $this->setHtml("<u>" . $this->_html . "</u>");
46 }
47
48 public function __call( $name, $args )
49 {
50 $this->_logout_link->$name($args[0]);
51 }
52 }
53
54 class LogoutLinkStrongDecorator extends HtmlLinks {
55 protected $_logout_link;
56
57 public function __construct( $logout_link )
58 {
59 $this->_logout_link = $logout_link;
60 $this->setHtml("<strong>" . $this->_html . "</strong>");
61 }
62
63 public function __call( $name, $args )
64 {
65 $this->_logout_link->$name($args[0]);
66 }
67 }
Затем мы должны быть в состоянии использовать это, например, следующим образом:
01 $logout_link = new LogoutLink();
02
03 if( $is_logged_in ) {
04 $logout_link = new LogoutLinkStrongDecorator($logout_link);
05 }
06
07 if( $in_home_page ) {
08 $logout_link = new LogoutLinkH2Decorator($logout_link);
09 } else {
10 $logout_link = new LogoutLinkUnderlineDecorator($logout_link);
11 }
12 $logout_link->render();
Здесь можно видеть, как мы оказываемся в состоянии скомбинировать несколько декораторов, если это требуется. Поскольку все декораторы используют магическую функцию __call, то мы можем всё ещё вызвать методы оригинальной функции. Если принять, что мы в настоящий момент находимся внутри главной страницы и зарегистрированы, то HTML-выход должен быть следующим:
1 <strong><h2><a href="logout.php">Logout</a></h2></strong>
Шаблон «Одиночка»
Изображение размещено с разрешения владельцев сайта intoxicologist.wordpress.com
Шаблон проектирования «Одиночка» является порождающим шаблоном проектирования, который обеспечивает наличие одного единственного экземпляра какого-то конкретного класса во время выполнения и глобальную точку доступа к этому единственному экземпляру.
Это облегчает задание точки «координации» для других объектов, также использующих данный единственный экземпляр, поскольку его переменные будут неизменными для любых вызовов.
Когда можно использовать это?
Если требуется перевести какой-либо специфический экземпляр из одного класса в другой, то можно использовать шаблон «Одиночка», чтобы устранить проведение этого экземпляра через конструктор или аргумент. Предположим, вы создали класс Session, который имитирует глобальный массив $_SESSION. Поскольку для этого класса его экземпляр требуется создать только один раз, то можно реализовать шаблон «Одиночка», как, например:
01 <?php
02 class Session
03 {
04 private static $instance;
05
06 public static function getInstance()
07 {
08 if( is_null(self::$instance) ) {
09 self::$instance = new self();
10 }
11 return self::$instance;
12 }
13
14 private function __construct() { }
15
16 private function __clone() { }
17
18 // Мы можем использовать любые другие способы сеанса
19 ...
20 ...
21 ...
22 }
23
24 // Получить экземпляр сеанса
25 $session = Session::getInstance();
Сделав так, мы можем получать доступ к нашему экземпляру сеанса из различных частей нашего кода, даже в разных классах. Эти данные будут сохраняться в течение всех вызовов getInstance.
Заключение
Имеется ещё много шаблонов проектирования, которые полезно знать; в этой статье я остановился только на некоторых из наиболее известных, которые я использую при программировании. Если есть интерес прочитать о других шаблонах проектирования, то страница Википедии Design Patterns (Шаблоны проектирования) содержит много информации об этом. Если этого недостаточно, то вы всегда можете прочитать книгу «Design Patterns: Elements of Reusable Object-Oriented Software» («Шаблоны проектирования: элементы многократно используемого объектно-ориентированного программного обеспечения»), которая считается одной из лучших по рассматриваемой теме.
И последнее: используя эти шаблоны проектирования, всегда проверяйте, что вы пытаетесь решить правильно поставленную задачу. Как я упоминал ранее, эти шаблоны проектирования необходимо использовать очень осторожно: они — при использовании в ненадлежащем контексте — могут ухудшить ситуацию, но при правильном использовании они просто жизненно необходимы.
Если вы нашли данную статью полезной, то почему бы не ознакомиться с предложением PHP-скриптов на Envato Market. Там представлены тысячи полезных скриптов, которые могут ускорить вашу разработку и улучшить конечный результат. Имеются системы бронирования, контактные формы AJAX, системы информационных бюллетеней и многое другое.
Автор: LukinB