[Перевод] Что такое шаблоны проектирования?

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

Шаблоны проектирования — это допускающие многократное использование оптимизированные решения проблем программирования, с которыми мы сталкиваемся каждый день. Шаблон проектирования — это не класс или библиотека, которые мы можем просто вставить в нашу систему. Он — много больше. Это — некоторый шаблон, который должен быть реализован в надлежащей ситуации. Он не зависит от языка. Хороший шаблон проектирования должен быть таким, чтобы его можно было использовать с большинством языков (если не со всеми) в зависимости от характеристик языка. Чрезвычайно важно то, что любой шаблон проектирования необходимо использовать очень осторожно — если он применён в ненадлежащем месте, то его действие может быть разрушительным и породить много проблем для вас. Однако применённый в нужном месте в нужное время он может стать вашим спасителем.

Есть три основных типа шаблонов проектирования:

• структурный
• порождающий
• поведенческий

Структурные шаблоны, в общем случае, имеют дело с отношениями между объектами, облегчая их совместную работу.

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

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

Почему их следует использовать?
Шаблоны проектирования в принципе являются хорошо продуманными решениями проблем программирования. Многие программисты уже сталкивались ранее с этими проблемами и использовали для преодоления эти «решения». Встречая какую-то проблему, зачем заново искать решение, когда можно применить уже проверенное? Пример
Давайте представим, что вам поручено создать способ объединить два класса, которые выполняют два разных действия в зависимости от ситуации. Эти два класса интенсивно используются существующей системой в разных местах, что затрудняет их удаление и изменение существующего кода. Дополнительно к этому изменение существующего кода требует проверки всего изменённого кода, т.к. правка такого рода в системе, построенной на разных компонентах, почти всегда вносит новые ошибки. Вместо перечисленного можно использовать вариант шаблона «Стратегия» и шаблона «Адаптер», которые могут легко обращаться с названными типами сценариев.
01 _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 }

Довольно просто, не так ли? Теперь посмотрим внимательнее на шаблон «Стратегия».Шаблон «Стратегия»
5f8f043a66676b1d3db5e1bd992ba30f.jpg
Изображение размещено с разрешения владельцев сайта cioinnervoice.wordpress.com

Шаблон «Стратегия» является поведенческим шаблоном проектирования, который позволяет вам решать, какой план действий должна принять программа, основываясь на определённом контексте при выполнении. Вы закладываете два различных алгоритма внутри двух классов и решаете в ходе выполнения, с какой стратегией следует работать.

В нашем примере выше стратегия устанавливается в зависимости от того, какой была переменная $context в то время, когда класс подвергался обработке. Если вы даёте ей контекст для класса_один, то будет использован класс_один и наоборот.

Замечательно, но где я могу использовать это?
1d539688f879176fd1166100c30231e3.jpg

Предположим, что вы в данный момент разрабатываете класс, который может или обновить или создать новую пользовательскую запись. Хотя ему требуются те же самые входы (имя, адрес, номер мобильного телефона и т.п.), но в зависимости от ситуации он должен использовать различные функции при обновлении и создании. Здесь для выполнения можно, вероятно, сразу же использовать условный переход «if-else», но как быть, если этот класс понадобится и в другом месте? Тогда нужно будет переписать полностью весь этот оператор условного перехода. Не было бы проще просто указать ваш контекст?

01 

Теперь «обычный» шаблон «Стратегия» предполагает помещение ваших алгоритмов внутри другого класса, но в данном случае другой класс был бы нерациональным решением. Помните, что вы не обязаны точно следовать шаблону. Варианты работают, пока концепция остаётся прежней, и это решает проблему.Шаблон «Адаптер»
adapterintro.jpg
Изображение размещено с разрешения владельцев сайта www.uxcell.com

Шаблон «Адаптер» является структурным шаблоном проектирования, который позволяет перепрофилировать класс с другим интерфейсом, делая его доступным для системы, которая использует различные методы вызова.

Это также позволяет изменять некоторые из входов, получаемых от класса клиента, превращая его в нечто совместимое с функциями класса Adaptee.

Как можно использовать это?
adapter.jpg

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

Сравните эти две реализации:

Подход без адаптера

1  CreateOrUpdate( //inputs );
4 
5  $profile = new Profile();
6  $profile->CreateOrUpdate( //inputs );

Если бы нам нужно было сделать это снова в другом месте или даже использовать этот код в другом проекте, то мы должны были бы набрать всё заново.

Лучше

Указанное противоположно действиям вроде приведённых ниже:

1  NewAccount( //inputs );

В данной ситуации мы имеем обёрточный класс, который был бы нашим доменным классом Account:
01  CreateOrUpdate( //subset of inputs );
08         
09        $profile = new Profile();
10        $profile->CreateOrUpdate( //subset of inputs );
11    }
12  }

Таким образом, можно использовать домен Account снова везде, где требуется, — дополнительно вы оказываетесь в состоянии обёртывать другие классы также под вашим доменным классом.Шаблон «Фабричный метод»
factoryintro.jpg
Изображение размещено с разрешения владельцев сайта www.lankanewspappers.com

Шаблон «Фабричный метод» является порождающим шаблоном проектирования, который делает именно то, что означает это слово: этот класс действует как фабрика экземпляров объектов.

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

Когда можно использовать это?
factory.jpg

Наилучшей ситуацией для использования шаблона «Фабричный метод» является наличие нескольких различных вариантов одного объекта. Допустим, имеется класс «кнопка»; у этого класса есть различные варианты — например, ImageButton (кнопка изображения), InputButton (кнопка ввода) и FlashButton (флэш-кнопка). В зависимости от места может потребоваться создать различные кнопки — именно здесь можно использовать «фабрику» для создания кнопок для вас!

Начнём с создания наших трёх классов:

01  _html;
08      }
09  }
10 
11  class ImageButton extends Button {
12      protected $_html = "..."; //Здесь должен быть задан HTML, требуемый для                                                          вашей кнопки на базе изображения
13  }
14 
15  class InputButton extends Button {
16      protected $_html = "..."; //Здесь должен быть задан HTML, требуемый для 	                                                  вашей нормальной кнопки ();
17  }
18 
19  class FlashButton extends Button {
20      protected $_html = "..."; //Здесь должен быть задан HTML, требуемый для                                                          вашей флэш-кнопки
21  }

Теперь можно создать наш фабричный класс:
01  

Полученный код можно использовать, например, так:
1  $buttons = array('image','input','flash');
2  foreach($buttons as $b) {
3      echo ButtonFactory::createButton($b)->getHtml()
4  }

Выходом должен быть HTML всех ваших типов кнопок. Таким образом, вы могли бы указать, какую кнопку создать в зависимости от ситуации, а также повторно использовать условие.Шаблон «Декоратор»
decoratorintro.jpg

Изображение размещено с разрешения владельцев сайта www.decoratorsdarlington.co.uk

Шаблон «Декоратор» является структурным шаблоном проектирования, который позволяет нам добавлять новое или дополнительное поведение к объекту в ходе выполнения в зависимости от ситуации.

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

Чтобы реализовать шаблон «Декоратор», можно выполнить следующие шаги:

1. Выделите оригинальный класс «Компонент» как подкласс класса «Декоратор».
2. В классе «Декоратор» добавьте указатель «Компонент» как поле.
3. Переместите «Компонент» в конструктор «Декоратора», чтобы инициализировать указатель «Компонент».
4. В классе «Декоратор» перенаправьте все методы «Компонент» на указатель «Компонент».
5. В классе «Декоратор» отмените любой метод (любые методы) «Компонент», поведение которого (которых) должно быть модифицировано.

Данные этапы размещены с разрешения владельцев сайта en.wikipedia.org/wiki/Decorator_pattern

Когда можно использовать это?
decorator.jpg

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

Сначала зададим различные требуемые «декорации»:

• Если мы находимся на главной странице и зарегистрированы, то эта ссылка должна быть «обёрнута» в теги h2.
• Если мы находимся на другой странице и зарегистрированы, то эта ссылка должна быть «обёрнута» в теги подчёркивания.
• Если мы зарегистрированы, то эта ссылка должна быть «обёрнута» в теги полужирного шрифта.
После задания наших «декораций» можно их запрограммировать:

01  _html = "Logout";
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("

" . $this->_html . "

"); 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("" . $this->_html . ""); 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("" . $this->_html . ""); 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  

Logout

Шаблон «Одиночка»
singletonintro.jpg

Изображение размещено с разрешения владельцев сайта intoxicologist.wordpress.com

Шаблон проектирования «Одиночка» является порождающим шаблоном проектирования, который обеспечивает наличие одного единственного экземпляра какого-то конкретного класса во время выполнения и глобальную точку доступа к этому единственному экземпляру.

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

Когда можно использовать это?
singleton.jpg

Если требуется перевести какой-либо специфический экземпляр из одного класса в другой, то можно использовать шаблон «Одиночка», чтобы устранить проведение этого экземпляра через конструктор или аргумент. Предположим, вы создали класс Session, который имитирует глобальный массив $_SESSION. Поскольку для этого класса его экземпляр требуется создать только один раз, то можно реализовать шаблон «Одиночка», как, например:

01  

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

Заключение


Имеется ещё много шаблонов проектирования, которые полезно знать; в этой статье я остановился только на некоторых из наиболее известных, которые я использую при программировании. Если есть интерес прочитать о других шаблонах проектирования, то страница Википедии Design Patterns (Шаблоны проектирования) содержит много информации об этом. Если этого недостаточно, то вы всегда можете прочитать книгу «Design Patterns: Elements of Reusable Object-Oriented Software» («Шаблоны проектирования: элементы многократно используемого объектно-ориентированного программного обеспечения»), которая считается одной из лучших по рассматриваемой теме.

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

Если вы нашли данную статью полезной, то почему бы не ознакомиться с предложением PHP-скриптов на Envato Market. Там представлены тысячи полезных скриптов, которые могут ускорить вашу разработку и улучшить конечный результат. Имеются системы бронирования, контактные формы AJAX, системы информационных бюллетеней и многое другое.

Комментарии (18)

  • 20 августа 2016 в 02:33

    +6

    Обмажутся своими паттернами, а потом создают абстрактный класс через абстрактный метода абстрактной фабрики для того чтобы вызвать метод сигнлтона через обсервер
    • 20 августа 2016 в 10:38

      0

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

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

    • 20 августа 2016 в 12:14

      +1

      Не забываем также про принципы KISS (Keep it short and simple) и YAGNI (You ain’t gonna need it).

  • 20 августа 2016 в 09:00

    –1

    Паттерн это ТЗ, которое вы пишете самому себе на том языке на котором программируете и затем имплементируете в классах и фреймворках.
  • 20 августа 2016 в 09:39

    0

    Он не зависит от языка.

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

    • 20 августа 2016 в 10:45

      0

      Так и есть, в каждом языке свои паттерны.

      В мире ООП одни подходы, описанные в статье, в функциональном мире — другие, а в js как получится =)
      Вот к примеру в js любой callback — это реализация паттерна стратегия, хоть и выглядит совсем не так, но суть у них одна.
      А система обработки событий — это ничто иное как chain of responsibility.

      • 20 августа 2016 в 11:44

        0

        Я полагаю, паттерны языковонезависимы. Это как соглашения, просто на уровень выше. Люди смертны, а код вечен?
        • 20 августа 2016 в 11:58

          0

          Вот именно, это соглашение как писать код и в каждом языке своя идеология написания кода. То, что на ООП делается через классы, в ФП делается через коллбеки, замыкания и керринг. А в js через классы, коллбеки, замыкание и керринг.

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

          • 20 августа 2016 в 12:24

            0

            Так дело в терминах? Ага. «Фабрика» неокрепшие умы в ступор вводит.
  • 20 августа 2016 в 11:02

    +2

    шел 2016 год…
    • 20 августа 2016 в 11:18

      +2

      Шел 2016 год… Многие до сих пор не знают чем отличается массив от списка, абстрактный класс от интерфейса и как устроена хеш-таблица…
      • 20 августа 2016 в 13:32

        +1

        я бы сказал, что есть обратная тенденция: современные задачи зачастую требуют не глубоких (или хотя-бы поверхностных) знаний, а возможности накодить здесь и сейчас.
  • 20 августа 2016 в 11:14

    0

    Как сказал В.И. Ленин, паттерны нужны для того, чтобы быстро объяснить другому программисту что за #$!*% вы написали, а не для того, чтобы создавать фабрики фабрик (это и без паттернов отлично делается).

    • 20 августа 2016 в 16:11

      0

      Именно так. Код первичен, паттерн вторичен, а не наоборот.

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

      Фабрики фабрик вообще идут параллельно и растут корнями из TDD.

  • 20 августа 2016 в 11:34

    0

    По сути все паттерны сводятся так или иначе к идее введения дополнительного уровня абстракции. Различаются только способы взаимодействия между уровнями. По идее эти способы могут быть очень гибкими и не ограничиваться только общепринятыми шаблонами. Почему и зачем насаждаются конкретные реализации, под которые люди начинают подгонять свой код (вместо того, чтобы на основе базовой идеи создать наиболее подходящее в конкретном случае взаимодействие) — мне лично не очень понятно.
    • 20 августа 2016 в 12:08

      0

      На самом деле никто не насаждает писать только через описанные паттерны. Более того, никто даже не обязывает реализовывать паттерны 1 в 1 как в примерах. Пример написан лишь для понимания как это может выглядеть.
      У всего хорошего всего есть побочный эффект, если этим злоупотреблять. Витамины в большом количестве тоже причиняют вред. Понимание когда нужно использовать конкретный паттерн — это показатель зрелости специалиста.
  • 20 августа 2016 в 14:56

    0

    Полезная статья. Паттерны, дети — это типовые решения. Никто не обязывает им следовать. Никто не обязывает натягивать ваше решение, как сову на глобус.Но, если желаете быть понятыми
  • 20 августа 2016 в 15:41

    +1

    Серьёзно? Базовая статья про паттерны в 2016-м… через 20 лет после книги «Банды четырёх»?
    Что дальше? Туториал по использованию колеса? Или добротная обзорная статья о всех плюсах и минусах использования огня?

© Habrahabr.ru