Вы когда-либо задавались вопросом, что такое шаблоны проектирования? В этой статье будет разъяснено, почему шаблоны проектирования имеют существенное значение, и будет приведено несколько примеров на 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, системы информационных бюллетеней и многое другое.
Поделиться с друзьями
-->

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


  1. stepanp
    20.08.2016 02:33
    +25

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


    1. AndreyRubankov
      20.08.2016 10:38
      +1

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

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


    1. KvanTTT
      20.08.2016 12:14
      +2

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


      1. Flammar
        24.08.2016 09:46

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


    1. VladimirKochetkov
      21.08.2016 18:26
      +4

      Одно из наиболее адекватных мнений о злоупотреблении паттернами (на мой взгляд) было озвучено много лет назад на просторах RSDN:

      Дело в том, что в английском языке для нашего слова «шаблон» есть целых два слова — pattern и template, и они имеют несколько разный смысл.

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

      template — это то, на основе чего что-то генерируют. Отчеты можно формировать на основе шаблонов-template. Шаблоны отправляемых писем — это тоже templates.

      Так вот книга называется «Design Patterns». То есть сборник паттернов для распознавания известных конструкций. Конечно простому переводчику сложно заметить такие тонкости, поэтому в нашем переводе паттерны стали сначала шаблонами, а потом и вовсе (о ужас!) приемами.

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

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


      1. lair
        21.08.2016 23:22

        Вообще, эта позиция плохо сочетается с A Pattern Language Александера, который и заложил формат "проблема — контекст — решение". Даже если говорить о распознавании, то оно здесь применимо к "проблемной" части, а не к решению. Но на самом же деле, pattern у Александера — это не что-то, по чему распознают. Это образец, по которому решают проблему.


        Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice

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


        1. VladimirKochetkov
          23.08.2016 02:04

          В большей степени соглашусь, хотя в своей книге «Применение шаблонов проектирования: дополнительные штрихи» Влиссидес критикует определение Александера, называя его первым в числе основных заблуждений о шаблонах :) Критикует правда — не вполне за то, о чем идет речь здесь, но тем не менее. С другой стороны, сам Александер признает, что описываемые им шаблоны появились именно вследствие сопоставления успешных архитектурных проектов и выделения (распознавания) в них общего, направленного на решение той или иной конкретной проблемы в заданном контексте. Т.е. применимость шаблонов для распознавания заложена в них изначально, по их определению и способу появления на свет.

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


          1. lair
            23.08.2016 11:17

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

            Можно сколько угодно критиковать определение Александера, но отрицать тот факт, что GoF построен по тому же принципу, достаточно сложно.


            Т.е. применимость шаблонов для распознавания заложена в них изначально, по их определению и способу появления на свет.

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


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

            Влечет. Можно увидеть проблему, которой не было.


  1. babylon
    20.08.2016 09:00
    -5

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


  1. sshikov
    20.08.2016 09:39
    +2

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

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


    1. AndreyRubankov
      20.08.2016 10:45
      +2

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

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


      1. miktim
        20.08.2016 11:44

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


        1. AndreyRubankov
          20.08.2016 11:58
          +1

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

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


          1. miktim
            20.08.2016 12:24

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


            1. sshikov
              22.08.2016 11:50
              +1

              Да не, дело скорее не в терминах.


              Есть определение паттерна Фабрика (в чем суть проблемы, и что делать), и есть его реализация. Так вот, реализация очень сильно языковозависима. Вплоть до полного ее отсутствия за ненужностью, потому что в конкретном языке именно эта проблема не актуальна/тривиальна.


              1. Flammar
                24.08.2016 10:24

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


            1. sshikov
              22.08.2016 11:54
              +1

              Погуглите скажем несколько заметок Mario Fusco, о том, во что вырождаются ряд паттернов GoF после появления в Java 8 лямбд.


              1. miktim
                23.08.2016 09:27

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


              1. Flammar
                24.08.2016 10:09

                Публикации на тему вырождения дизайн-паттернов в нечто тривиальное в функциональных ЯП были сделаны Петером Норвигом ещё в 1996, всего лишь через два года после книги GoF.


                1. sshikov
                  28.08.2016 15:32

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


                  Кстати, вырождаются не паттерны вообще, а некоторые (как правило, специфичные для ООП), потому что у функциональных языков есть свои типовые надязыковые конструкции, даже если их паттернами не зовут.


      1. Flammar
        24.08.2016 09:58

        Вот к примеру в js любой callback — это реализация паттерна стратегия
        С объектно-ориентированной точки зрения, всё функциональное программирование — это применение паттерна «стратегия». А асинхронная обработка с continuation-passing всегда будет напоминать chain of responsibility.


        1. AndreyRubankov
          24.08.2016 10:33

          Да, оно то похоже, но все же есть пара замечаний.
          Паттерны «стратегия», если простыми словами, — это когда аргумент функции (метода) принимает на вход «кусок кода» (функцию или имплементацию стратегии в ООП), который будет выполняться в работе данной функции.

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

          > асинхронная обработка с continuation-passing
          Это больше похоже на стратегию. Вызывается асинхронный метод, и в него передается callback функция, в которую будет выполнен возврат после выполнения асинхронного кода.

          Chain of responsibility — это механизм, лежащий в основе pub/sub.
          Dispatcher (на него подписываются и через него кидают event) внутри содержит хеш-таблицу сообщение -> список слушателей. Когда кто-то публикует сообщение, диспатчер берет список слушателей и поочередно выполняет их.

          (но тут я не прав: «chain of responsibility по учебнику» будет заходить в каждую функцию и говорить «пришло такое сообщение», функция же должна будет посмотреть на это сообщение и решить выполнять над ним логику или нет)


    1. sasha1024
      22.08.2016 11:22
      +1

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


      1. sshikov
        22.08.2016 11:39

        Ну я бы не называл это костылями, по одной простой причине — это все-таки изначально некие над-языковые конструкции, которые описывают, как решать типовую проблему, имея в наличии некоторый набор инструментов. Ну т.е., грубо говоря, есть список чисел, нужно получить "сумму". На выходе для одного языка цикл, а для другого скажем какой-то вариант fold/reduce. И кто из них костыли?


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


        Иными словами — "что-то совсем очевидное" не перестает быть решением типовой проблемы. Оно просто становится простым решением.


        1. sasha1024
          22.08.2016 13:18

          Скажем так: это некие внеязыковые конструкции, которые описывают, как решать типовую проблему.

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

          Т.е. паттерн в широком смысле — полезная внеязыковая конструкция. Паттерн в узком смысле — полезная внеязыковая конструкция, не совсем тривиальная в реализации.


  1. AlexeyFrolov
    20.08.2016 11:02
    +6

    шел 2016 год…


    1. AndreyRubankov
      20.08.2016 11:18
      +9

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


      1. marshinov
        20.08.2016 13:32
        +7

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


      1. OnYourLips
        20.08.2016 18:28
        -6

        > абстрактный класс от интерфейса

        Это очень хороший вопрос, который показывает, имел ли человек опыт в разработке ПО или нет.
        Те, кто не имел, отвечают примерно так: «Абстрактный класс может иметь реализованные методы, а интерфейс — нет» и прочую ерунду, связанную с технической стороной.


        1. ZurgInq
          20.08.2016 18:53
          +4

          Я даже не побоюсь спросить, а кроме технической ерунды, какие еще различия?


          1. DistortNeo
            20.08.2016 19:23

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

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

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


            1. Kitmouse
              22.08.2016 11:23

              Простите, за, возможно, глупый вопрос, но как с технической стороны сделаны «быстрые» интерфейсы с полиморфизмом (времени выполнения) без vtable или более дорогих средств?
              Звучит очень круто, хотелось бы узнать. В голову ничего не лезет, а запросы в гугл ничего не дали.


              1. DistortNeo
                22.08.2016 12:06
                +1

                Нет, vtable, естественно, используются, без них никуда. Разница же такая:

                1. Разная стратегия использования при наследовании. Если вы хотите иметь возможность перезаписать метод в классе-потомке, то делаете его виртуальным и оставляете открытым. При этом любой вызов открытых виртуальных методов будет осуществляться через vtable.

                Методы интерфейса же являются автоматически virtual и sealed. Если хотите перезаписать метод интерфейса, то просто наследуете его второй раз, при этом старые методы будут неявно объявлены во второй раз. Это приводит к тому, что при вызове метода компилятор генерит оптимальный код, а не лезет в vtable.

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

                2. Огромная разница в потреблении памяти. В C++ создаётся по отдельному полю vtable для каждого из множественно наследуемых классов, дополнительная память тратится на разруливание виртуального наследования. В C# информация о множественном наследовании сидит в единственном vtable на каждый объект в виде ссылок на vtable интерфейсов.

                Обычно такая двойная адресация не вызывает проблем, т.к. выполняется только один раз. Единственное исключение: generic-методы с interface constraint, где двойная адресация происходит при каждом вызове интерфейсного метода.

                В C++ же такое сделать попросту невозможно, т.к. объекты при множественном наследовании не могут располагаться по одному адресу. Из-за этого для каждого из базовых классов и приходится заводить по vtable.

                P.S. Подумал — а ведь если это развить, то на отдельную публикацию пойдёт, надо бы заняться. Реально куча интересных нюансов вылезает.


          1. OnYourLips
            20.08.2016 19:29

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


          1. AndreyRubankov
            20.08.2016 19:43

            К предыдущим двум ответам, стоит лишь добавить, что Абстрактный класс определяет иерархию («это есть», «является»), в то время, как Интерфейс лишь говорит о возможности («может»).

            Самолет — это механизм.
            Птица — это живое существо.
            Самолет и Птица может летать (летающий объект).
            В небе можно увидеть Летающие объекты.


      1. Danik-ik
        20.08.2016 18:33
        +3

        Не знают. А что удивительного? Где-то рождаются дети. Кто-то осваивает новую специальность или специализацию. Новые люди, Карл!
        Между тем вся страна электрифицирована, а коммунизм не наступил, хотя должен был, кажется.
        Даже если принять как догму, что «все в отрасли должны это знать» (кому, кстати, должны-то?) — в отрасли всегда полно и новичков, и кустарей «на пару строк».
        А когда все программисты до одного будут это знать — это будет признаком застоя и печали.


        1. AndreyRubankov
          20.08.2016 19:30

          А ведь не только новички не знают. Вот к новичкам претензий нету. Многие, у кого за плечами уже по 2-4 года не знают ответов на указанные вопросы.

          А на счет застоя, я не соглашусь. Когда все будут знать ответы на базовые вопросы, тогда произойдет качественный скачек развития всего ИТ мира.
          Вот только чтобы знать ответы на все базовые вопросы, нужно очень много времени потратить. «Программирование — это не работа, это образ жизни».


          1. c4boomb
            22.08.2016 02:30
            +1

            (Я знаю как устроена хеш-таблица)
            Сколько раз вам за карьеру программиста пригодилось знание о том как устроенна хеш-таблица? То что редко используется — забывается, это естественный процесс. В нужный момент прочитать/вспомнить — не составит проблем.


            1. withoutuniverse
              22.08.2016 11:03

              Вот есть UI поток, а есть другие Background потоки. И вы такой, на зная сложность вставки в хэштаблицу, решили добавить 1 запись туда. А она взяла и пересоздалась и начала пересчитывать хэши для корзин, так как места не хватало уже и UI подвис.
              Или привыкли вы за O(1) забирать из ArrayList в UI, а сейчас решили из середины LinkedList достать и снова UI подвис.
              Или удалить из середины ArrayList запись, или, что еще хуже, прямо в UI перебрать все элементы в нем и удалить по фильтру что-то.
              Продолжать примеры?
              Хорошего разработчика от посредственного эти вещи и отличают — он знает, что использовать и как это использовать.


              1. DistortNeo
                22.08.2016 11:24

                Я бы сказал, что это особенно критично для сетевых игр, где огромное значение имеет не время в среднем, а наихудший сценарий.


      1. red75prim
        22.08.2016 11:21
        +1

        Массив от списка — ладно, вставка/удаление О(n) против О(1), индексация O(1) против O(n). Хотя неплохо бы ещё знать про константные составляющие, которые на современных процессорах в некоторых случаях делают выгодным использование массива вместо списка.


        Устройство хеш-таблицы? Хм. Вообще-то их много разных. Достаточно знать, что при большом количестве коллизий производительность падает.


        А общее отличие абстрактного класса от интерфейса зачем? Слишком зависит от языка. В С++ абстрактные классы и интерфейсы различаются только наличием non-pure methods. В C# интерфейсы — это отдельная языковая сущность. В Rust вообще абстрактных классов нет.


  1. warlock13
    20.08.2016 11:14
    +1

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


    1. DistortNeo
      20.08.2016 16:11
      +1

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

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

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


  1. heilage
    20.08.2016 11:34

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


    1. AndreyRubankov
      20.08.2016 12:08

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


  1. miktim
    20.08.2016 14:56
    -1

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


  1. Shamov
    20.08.2016 15:41

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


    1. Danik-ik
      20.08.2016 19:09
      +5

      Серьёзно? Вы думаете, в отрасли застой и нет новых людей, том числе без нормального образования? Может, и школы с банальным 1+1 стоит закрыть? Мы-то всё уже выучили!


      1. Shamov
        20.08.2016 21:44
        -2

        Хорошо, принимается. Честно говоря, я сразу не заметил, что код в статье на PHP.


        1. withoutuniverse
          21.08.2016 12:18

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


          1. Shamov
            21.08.2016 12:50
            -2

            Ключевой момент не в том, что в PHP нет застоя, а в том, что в нём приток новых людей обеспечивается в основном за счёт людей без нормального образования (которые даже про паттерны не в курсе). В других языках такой проблемы либо нет, либо она стоит не настолько остро. Я, например, не представляю себе человека, который окончил бы университет по специальности Computer Science, а затем вдруг взял и выбрал PHP в качестве точки приложения полученных знаний и навыков. Это был бы нонсенс. Образование помешало бы ему совершить подобный выбор. Но такой выбор совершенно спокойно делают люди без образования. Он не кажется им ошибочным. В общем, PHP — это Basic наших дней.


            1. withoutuniverse
              21.08.2016 12:58

              Наверное для вас будет новостью, но фейсбук и вконтакте написаны на PHP.
              Мыслите стереотипно. Я провел множество собеседований по Java, почти все люди с высшим образованием в сфере it. Но и имея 5-летний опыт работы люди могли не знать, как работает Хэш Таблица, не знать, что такое шаблон Адаптер и т.д.


              1. Shamov
                21.08.2016 13:52
                -1

                Про фейсбук и вконтакт я в курсе. Они активно используют дешёвую рабочую силу. Можно сказать, это главная составляющая их успеха. По крайней мере, была на начальном этапе. Сейчас-то они уже могут себе позволить и более квалифицированные кадры, но генотип так просто не изменишь. Приходится работать с тем, что есть. Это суровая реальность. Эффектно взлететь и быстро раскрутиться, сварганив прототип в собственном гараже, можно только на дешёвом ресурсе. И, к сожалению, эта основа останется в проекте навсегда. Если ты построил в гараже моторизированный велосипед, то в дальнейшем можно будет только увеличивать его размер, наращивать мощность двигателя и прикручивать к нему всякие штуки типа полифонического гудка с поддержкой mp3. Развить этот велосипед в атомную подводную лодку никак не получится. У такой лодки совершенно иной цикл развития. И начинается он не в гараже.

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


                1. Serg046
                  23.08.2016 14:13
                  -1

                  Пусть и не поддерживаю насмешку над php, но хочу немного поддержать. Действительно и вк и фейсбук в дальнейшем были вынуждены писать свои трансляторы и движки для php (если не ошибаюсь, это про компиляцию в с/с++), чтобы не выкидывать тонну существующего кода. Однако перетестировать его все равно пришлось по новой (разве что повторное юнит тестирование не требовалось).


                  1. Shamov
                    23.08.2016 14:41
                    -1

                    На самом деле, никакой насмешки у меня тоже не было. Только трезвый и хладнокровный анализ ситуации. Не понимаю, как здравомыслящий человек может отрицать то, что PHP — это современный Basic, а Java — современный Visual Basic. Самое смешное в том, что когда я выбирал движок для блога, я выбрал WordPress, а не Octopress, который написан на Ruby. При том, что Ruby как язык намного круче PHP. Но ключевую роль при выборе сыграло то, что комьюнити вокруг WordPress намного больше.


                  1. Shamov
                    23.08.2016 21:30
                    -1

                    Кстати, интересный вопрос, почему так получилось. Было ли это предопределено с самого начала или просто случайность?

                    Ответ на этот вопрос содержится в цитате самого Расмуса Лердорфа: «Я ненастоящий программист. Я компоную всякие штуки, пока это не начинает работать. Потом иду дальше. Настоящий программист скажет: „Ок, это работает, но тут утечка памяти, надо пофиксить“. А я просто перезапускаю Apache каждые 10 запросов»


                    1. GlukKazan
                      22.12.2016 09:37

                      Только канон, только хардкор. Я очень уважаю творчество Макса Фрая, но скандинавские боги выступающие плечом к плечу вместе с олимпийскими — это уже из цикла "бывал ли Перун в Афинах". Нордический фольклор вполне самодостаточен без Кецалькоатля.


                      1. Shamov
                        24.08.2016 12:10
                        -1

                        Ну, если у молодых горячих разработчиков это считается оскорблением, то я тогда тоже не удивлён.

                        Вот, кстати, про Java любопытно. Вы часто встречали проекты, в которых Java компилируется в бинарный код? Я имею в виду, кроме как под Android. А ведь такие компиляторы есть и для десктопных платформ. Но только никто ими не пользуется. Потому что это никому не нужно.


                      1. Serg046
                        24.08.2016 15:51
                        -1

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

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


                        1. withoutuniverse
                          24.08.2016 18:12

                          Разве кто-то сменил технологию? Транслятор сменили и всё. Как писали на php, так и пишут.
                          Да и Windows множество участков кода имеет на c# — нет проблем в таком подходе.


                          1. Serg046
                            24.08.2016 19:50
                            -1

                            Разве кто-то сменил технологию? Транслятор сменили и всё. Как писали на php, так и пишут.

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

                            Разве кто-то сменил технологию? Транслятор сменили и всё. Как писали на php, так и пишут.
                            Да и Windows множество участков кода имеет на c# — нет проблем в таком подходе.

                            Я выделил жирным проблему. Windows начал использовать c# не вместо, а вместе с существующим кодом. И к тому моменту c# уже протестирован. Ну совсем разные вещи. Попробуйте взглянуть на ситуацию с точки зрения бизнеса.


            1. Grox
              21.08.2016 23:01

              .


  1. varanio
    20.08.2016 21:15

    В паттерне «фабричный метод» в примере я бы пихнул стат. метод createButton в абстрактный класс, а класс ButtonFactory грохнул бы.
    Зачем плодить полупустые сущности, раз там все равно тупой статический метод?


    1. AndreyRubankov
      20.08.2016 21:32
      +1

      Этот подход даже имеет свое название: Static Factory Method.


    1. withoutuniverse
      21.08.2016 13:07
      -1

      И это очень плохо, так как базовый класс знает, как будут создаваться наследники. И что еще хуже, он будет знать, куда этот объект будет сохраняться или откуда будут браться данные. Например, есть у вас статичный метод, принимающий код ошибки, а возвращающий строку с её описанием для UI. И лезет он за этими данными в BD/Network/XML/Etc. — вот тут жесткая связь данных и их способа хранения, никогда так не делайте :)


      1. varanio
        21.08.2016 17:02

        > И это очень плохо, так как базовый класс знает, как будут создаваться наследники.
        Согласен с этим.
        Но как это знание тут может помешать на практике? Т.е. грубо говоря, что в случае с отдельным классом, что в случае с тем же классом — при добавлении наследников в иерархию, статический метод должен дорабатываться.

        > Например, есть у вас статичный метод, принимающий код ошибки, а возвращающий строку с её описанием для UI
        А при чем тут фабрика вообще, если возвращается строка?


        1. withoutuniverse
          21.08.2016 17:16

          Я в синтаксисе php не силен и мои рассуждения строятся на том, что я реализую некий интерфейс, чего со статик методами не сделать. Какая будет реализация — уже абстракция.
          Т.е. я могу прокинуть любую фабрику для инстанциирования объекта.

          Возвращается объект типа String — каким он будет, определяет фабрика.
          Если на выходе будет не String, а Product, как в примерах из википедии — суть не изменится, это фабрика.


    1. samizdam
      21.08.2016 13:55
      +1

      Порой такой компромиссный подход может сработать.
      Но, по мере усложнения приложения, как правило не долго.
      Разделение ответственности: Инстанцирование кнопки — одна ответственность. Реализация функциональности кнопки — другая.
      Т.о. отделив создание объекта от него самого, через ввод фабрики, вы сможете
      а). Вводить в приложение различные фабрики, например на этапе исполнения, либо через конфигурацию
      б). Замочить сам процесс создания кнопки, что позволит оттестировать код с разных сторон.


      1. AndreyRubankov
        21.08.2016 14:20

        У static factory method немного другой смысл. Он предназначен, чтобы создавать закрытые библиотеки / фреймворки, который можно замокать, но нельзя расширить.
        Это облегчает жизнь, когда нужно поддерживать обратную совместимость. Появляется возможность безболезненно менять строение внутренних классов, главное, чтобы они соответствовали интерфейсу. Можно добавлять, удалять, изменять имплементации (они внутренние и никто не может на них завязаться).

        Пример тому в Java — EnumSet.
        Позволяет создать множество из enum объектов. При необходимости, в метод, принимающий EnumSet можно передать мок или свою реализацию. Но вот залезть в этот «фреймворк» и каким-то образом изменить его стандартное поведение, не получится.

        В зависимости от количества элементов в Enum, этот класс может выдать разные реализации, некоторые будут построены на битовой маске int, некоторые на битовой маске long, если 32 или 62 бита не хватит — можно использовать массив. Нужно будет создать множество из одного элемента — пожалуйста. Если тесты производительности покажут, что Конкретная имплементация для 2 элементов будет работать лучше, чем имплементация с битовой маской — можно добавить ее и просто обновив версию пользователь получит прирост производительности без каких либо изменений своего кода.


      1. varanio
        21.08.2016 17:03

        угу, только для простых случаев


  1. kanstantsin
    22.08.2016 11:22

    Если убрать номера строк примерах, есть шанс, что будет работать подсветка синтаксиса (не уверен, как она работает на хабре).


  1. Visseri
    22.08.2016 11:23

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


  1. lyhoshva
    22.08.2016 11:24

    В статье у паттерна «Адаптер» пример реализации паттерна «Фасад».

    Шаблон фасад (англ. Facade) — структурный шаблон проектирования, позволяющий скрыть сложность системы путём сведения всех возможных внешних вызовов к одному объекту, делегирующему их соответствующим объектам системы.


    1. Visseri
      22.08.2016 16:49

      и к тому же вообще не раскрыта суть Адаптера, который приводит интерфейсы несовместимых типов к единому интерфейсу)


      1. lyhoshva
        22.08.2016 17:13

        Но, по факту, это не вина автора хаба, так как это только перевод.