Шаблони або патерни проектування звичною мовою. Частина перша.


Дізнайтесь більше про нові кар'єрні можливості в EchoUA. Цікаві проекти, ринкова оплата, гарний колектив. Надсилайте резюме та приєднуйтеся до нас.

Шаблони або патерни проектування – це керівництво для рішенням проблем, що повторюються. Це не класи, пакети або бібліотеки, які можна було б підключити до вашого застосування і сидіти в очікуванні дива. Вони швидше є методиками, як вирішувати певні проблеми в певних ситуаціях.

Вікіпедія описує їх таким чином:

Шаблон проектування, чи патерн, у розробці програмного забезпечення – повторювана архітектурна конструкція, що є вирішенням проблеми проектування, у рамках деякого часто виникаючого контексту.

Будьте обережні

  • шаблони проектування не є рішенням усіх ваших проблем;
  • не намагайтеся використати їх в обов’язковому порядку – це може привести до негативних наслідків. Шаблони – це підходи до рішення проблем, а не рішення для пошуку проблем;
  • якщо їх правильно використати в потрібних місцях, то вони можуть стати порятунком, а інакше можуть привести до жахливого безладу.

Також зверніть увагу, що приклади нижче написані на PHP 7. Але це не повинно вас зупиняти, адже принципи залишаються такими ж.

Типи шаблонів

Патерни бувають наступних трьох видів:

  1. Що породжують.
  2. Структурні.
  3. Поведінкові.

Якщо говорити простими словами, то це шаблони, які призначені для створення екземпляра об’єкту або групи пов’язаних об’єктів.

Вікіпедія свідчить:

Шаблони, що породжують – шаблони проектування, які абстрагують процес інстаціювання. Вони дозволяють зробити систему незалежною від способу створення, композиції і представлення об’єктів. Шаблон, що породжує класи, використовує спадкоємство, щоб змінювати успадковний клас, а шаблон, що породжує об’єкти, делегує інстанціювання іншому об’єкту.

Існують наступні шаблони, що породжують :

Проста фабрика (Simple Factory)

Вікіпедія свідчить:

У об’єктно-орієнтованому програмуванні (ООП), фабрика – це об’єкт для створення інших об’єктів. Формально фабрика – це функція або метод, який повертає об’єкти прототипу, що змінюється, або класу з деякого виклику методу, який вважається “новим”.

Приклад з життя: Уявіть, що вам потрібно побудувати будинок, і вам потрібні двері. Було б безглузде кожного разу, коли вам потрібні двері, одягати вашу столярну форму і починати робити двері. Замість цього ви робите її на фабриці.

Простими словами: Проста фабрика генерує екземпляр для клієнта, не розкриваючи ніякої логіки.

Перейдемо до коду. У нас є інтерфейс Door і його реалізація:

interface Door{ public function getWidth (): float; public function getHeight (): float;}class WoodenDoor implements Door{ protected $width; protected $height; public function __construct (float $width, float $height) { $this ->width = $width; $this ->height = $height; } public function getWidth (): float { return $this ->width; }
public function getHeight (): float { return $this ->height; }}

Потім у нас є наша DoorFactory, яка робить двері і повертає її:

class DoorFactory{ public static function makeDoor ($width, $height): Door { return new WoodenDoor ($width, $height); }}

І потім ми можемо використати усе це:

$door = DoorFactory::makeDoor(100, 200);echo 'Width: '. $door ->getWidth ();echo 'Height: '. $door ->getHeight ();

Коли використати: Коли створення об’єкту – це не просто декілька привласнень, а якась логіка, тоді має сенс створити окрему фабрику замість повторення одного і того ж коду всюди.

Приклад на Java.

Фабричний метод (Fabric Method)

Вікіпедія свідчить:

Фабричний метод – шаблон проектування, що породжує, надає підкласам інтерфейс для створення екземплярів деякого класу. У момент створення спадкоємці можуть визначити, який клас створювати. Іншими словами, цей шаблон делегує створення об’єктів спадкоємцям батьківського класу. Це дозволяє використати в коді програми не специфічні класи, а маніпулювати абстрактними об’єктами на більш високому рівні.

Приклад з життя: Розглянемо приклад з менеджером по найму. Неможливо одній людині провести співбесіди з усіма кандидатами на усі вакансії. Залежно від вакансії він повинен розподілити етапи співбесіди між різними людьми.

Простими словами: Менеджер надає спосіб делегування логіки створення екземпляра дочірнім класам.

Перейдемо до коду. Розглянемо наведений вище приклад про HR- менеджера. Спочатку у нас є інтерфейс Interviewer і декілька реалізацій для нього:

interface Interviewer{ public function askQuestions ();}class Developer implements Interviewer{ public function askQuestions (){ echo 'Запитує про шаблони проектування!'; }}class CommunityExecutive implements Interviewer{ public function askQuestions (){ echo 'Запитує про роботу із співтовариством'; }}

Тепер створимо нашого HiringManager:

abstract class HiringManager{ // Фабричний метод abstract public function makeInterviewer (): Interviewer; public function takeInterview (){ $interviewer = $this ->makeInterviewer ();
$interviewer ->askQuestions (); }}

І тепер будь-який дочірній клас може розширювати його і надавати необхідного інтерв’юера:

class DevelopmentManager extends HiringManager{ public function makeInterviewer (): Interviewer { return new Developer (); }}class MarketingManager extends HiringManager{ public function makeInterviewer (): Interviewer { return new CommunityExecutive (); }}

Приклад використання :

$devManager = new DevelopmentManager ();$devManager ->takeInterview (); // Висновок: Запитує про шаблони проектування!$marketingManager = new MarketingManager ();$marketingManager ->takeInterview (); // Висновок: Запитує про роботу із співтовариством

Коли використати: Корисний, коли є деяка загальна обробка в класі, але необхідний підклас динамічно визначається під час виконання. Іншими словами, коли клієнт не знає, який саме підклас йому може знадобитися.

Приклади на Java і Python.

Абстрактна фабрика (Abstract Factory)

Вікіпедія свідчить:

Абстрактна фабрика – паттерн проектування, що породжує, надає інтерфейс для створення сімейств взаємозв’язаних або взаємозалежних об’єктів, не специфікуючи їх конкретних класів. Шаблон реалізується створенням абстрактного класу Factory, який є інтерфейсом для створення компонентів системи (наприклад, для віконного інтерфейсу він може створювати вікна і кнопки). Потім пишуться класи, що реалізовують цей інтерфейс.

Приклад з життя: Розширимо наш приклад про двері з простої фабрики. Залежно від ваших потреб вам знадобляться дерев’яні двері з одного магазину, залізні двері – з іншого або пластикова – з третього. Крім того, вам знадобиться відповідний фахівець: столяр для дерев’яних дверей, зварювальник для залізних дверей і так далі. Як ви можете помітити, тут є залежність між дверима.

Простими словами: Фабрика фабрик. Фабрика, яка групує індивідуальні, але пов’язані/залежні фабрики без вказівки їх конкретних класів.

Звернемося до коду. Використовуємо приклад про двері. Спочатку у нас є інтерфейс Door і декілька його реалізацій:

interface Door{ public function getDescription ();}class WoodenDoor implements Door{ public function getDescription (){
echo 'Я дерев'яні двері'; }}class IronDoor implements Door{ public function getDescription (){ echo 'Я залізні двері'; }}

Потім у нас є декілька DoorFittingExpert для кожного типу дверей :

interface DoorFittingExpert{ public function getDescription ();}class Welder implements DoorFittingExpert{ public function getDescription (){ echo 'Я працюю тільки із залізними дверима'; }}class Carpenter implements DoorFittingExpert{ public function getDescription (){ echo 'Я працюю тільки з дерев'яними дверима'; }}

Тепер у нас є DoorFactory, яка дозволить нам створити сімейство пов’язаних об’єктів. Тобто фабрика дерев’яних дверей надасть нам дерев’яні двері і експерта по дерев’яних дверях. Аналогічно для залізних дверей:

interface DoorFactory{ public function makeDoor (): Door; public function makeFittingExpert (): DoorFittingExpert;}// Дерев'яна фабрика поверне дерев'яні двері і столяраclass WoodenDoorFactory implements DoorFactory{ public function makeDoor (): Door { return new WoodenDoor (); } public function makeFittingExpert (): DoorFittingExpert { return new Carpenter (); }}// Залізна фабрика поверне залізні двері і сварщикаclass IronDoorFactory implements DoorFactory{ public function makeDoor (): Door { return new IronDoor (); }
public function makeFittingExpert (): DoorFittingExpert { return new Welder (); }}

Приклад використання :

$woodenFactory = new WoodenDoorFactory ();$door = $woodenFactory ->makeDoor ();$expert = $woodenFactory ->makeFittingExpert ();$door ->getDescription (); // Висновок: Я дерев'яні двері$expert ->getDescription (); // Висновок: Я працюю тільки з дерев'яними дверима// Аналогічно для залізних дверей$ironFactory = new IronDoorFactory ();$door = $ironFactory ->makeDoor ();$expert = $ironFactory ->makeFittingExpert ();$door ->getDescription (); // Висновок: Я залізні двері$expert ->getDescription (); // Висновок: Я працюю тільки із залізними дверима

Як ви можете помітити, фабрика дерев’яних дверей інкапсулює столяра і дерев’яні двері, а фабрика залізних дверей інкапсулює залізні двері і зварювальника. Це дозволило нам переконатися, що для кожних дверей ми отримаємо потрібного нам експерта.

Коли використати: Коли є взаємозв’язані залежності з не дуже простою логікою створення.

Приклади на Java і Python.

Будівельник (Builder)

Вікіпедія свідчить:

Будівельник – шаблон проектування, що породжує, який надає спосіб створення складеного об’єкту. Призначений для вирішення проблеми антипатерну “Телескопічний конструктор”.

Приклад з життя: Уявіть, що ви прийшли в McDonalds і замовили конкретний продукт, наприклад, Бігмак, і вам готують його без зайвих питань. Це приклад простої фабрики. Але є випадки, коли логіка створення може включати більше кроків. Наприклад, ви хочете індивідуальний сендвіч в Subway: у вас є декілька варіантів того, як він буде зроблений. Який хліб ви хочете? Які соуси використати? Який сир? У таких випадках на допомогу приходить шаблон ” Будівельник”.

Простими словами: Шаблон дозволяє вам створювати різні види об’єкту, уникаючи засмічення конструктора. Він корисний, коли може бути декілька видів об’єкту або коли потрібна безліч кроків, пов’язаних з його створенням.

Давайте я покажу на прикладі, що таке “Телескопічний конструктор”. Колись ми усі бачили конструктор на зразок такого:

public function __construct ($size, $cheese = true, $pepperoni = true, $tomato = false, $lettuce = true){}

Як ви можете помітити, кількість параметрів конструктора може різко збільшитися, і стане складно розуміти розташування параметрів. Крім того, цей список параметрів продовжуватиме рости, якщо ви захочете додати нові варіанти. Це і є “Телескопічний конструктор”.

Перейдемо приміром в коді. Адекватною альтернативою буде використання шаблону ” Будівельник”. Спочатку у нас є Burger, який ми хочемо створити:

class Burger{ protected $size; protected $cheese = false; protected $pepperoni = false; protected $lettuce = false; protected $tomato = false; public function __construct (BurgerBuilder $builder) {
$this ->size = $builder ->size; $this ->cheese = $builder ->cheese; $this ->pepperoni = $builder ->pepperoni; $this ->lettuce = $builder ->lettuce; $this ->tomato = $builder ->tomato; }}

Потім ми беремо ” Будівельника”:

class BurgerBuilder{ public $size; public $cheese = false; public $pepperoni = false; public $lettuce = false; public $tomato = false; public function __construct (int $size) { $this ->size = $size; } public function addPepperoni (){ $this ->pepperoni = true; return $this; } public function addLettuce (){ $this ->lettuce = true; return $this; } public function addCheese (){ $this ->cheese = true; return $this; } public function addTomato()
{ $this ->tomato = true; return $this; } public function build (): Burger { return new Burger ($this); }}

Приклад використання :

$burger =  (new BurgerBuilder (14)) ->addPepperoni () ->addLettuce () ->addTomato () ->build ();

Коли використати: Коли може бути декілька видів об’єкту і потрібно уникнути “телескопічного конструктора”. Головна відмінність від ” фабрики” – це те, що вона використовується, коли створення займає один крок, а ” будівельник” застосовується при безлічі кроків.

Приклади на Java і Python.

Прототип (Prototype)

Вікіпедія свідчить:

Задає види створюваних об’єктів за допомогою екземпляра-прототипу і створює нові об’єкти шляхом копіювання цього прототипу. Він дозволяє піти від реалізації і дозволяє наслідувати принцип “програмування через інтерфейси”. В якості повертаючого типу вказується інтерфейс / абстрактний клас на вершині ієрархії, а класи-спадкоємці можуть підставити туди спадкоємця, що реалізовує цей тип.

Приклад з життя: Пам’ятайте Доллі? Овечка, яка була клонована. Не поглиблюватимемося, головне – це те, що тут все обертається навколо клонування.

Простими словами: Прототип створює об’єкт, грунтований на існуючому об’єкті за допомогою клонування.

Тобто він дозволяє вам створювати копію існуючого об’єкту і модернізувати його згідно з вашими потребами, замість того, щоб створювати об’єкт наново.

Звернемося до коду. У PHP це може бути легко реалізовано з використанням clone:

class Sheep{ protected $name; protected $category; public function __construct (string $name, string $category = 'Гірська овечка') { $this ->name = $name; $this ->category = $category; } public function setName (string $name) { $this ->name = $name; } public function getName (){ return $this ->name; } public function setCategory (string $category) { $this ->category = $category; } public function getCategory (){ return $this ->category; }}

Потім він може бути клонований таким чином:

$original = new Sheep ('Джолли');echo $original ->getName (); // Джолли
echo $original ->getCategory (); // Гірська овечка// Клонуємо і модифікуємо то що треба$cloned = clone $original;$cloned ->setName ('Доллі');echo $cloned ->getName (); // Доллиecho $cloned ->getCategory (); // Гірська овечка

Також ви можете використати чарівний метод __clone для зміни клонуючої поведінки.

Коли використати: Коли потрібний об’єкт, схожий на існуючий об’єкт, або коли створення буде дорожче за клонування.

Приклади на Java і Python.

Одинак (Singleton)

Вікіпедія свідчить:

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

Приклад з життя: У країні одночасно може бути тільки один президент. Один і той же президент повинен діяти, коли того вимагають обставини. Президент тут є одинаком.

Простими словами: Забезпечує той факт, що створюваний об’єкт є єдиним об’єктом свого класу.

Взагалі шаблон одинак визнаний антипатерном, необхідно уникати його надмірного використання. Він необов’язково поганий і може мати корисні застосування, але використати його потрібно з обережністю, тому що він вводить глобальний стан у ваше застосування і його зміну в одному місці може вплинути на інші частини додатка, що викличе труднощі при відладці. Інший мінус – це те, що він робить ваш код пов’язаним.

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

final class President{ private static $instance; private function __construct()
{ // Ховаємо конструктор } public static function getInstance (): President { if (!self::$instance) { self::$instance = new self (); } return self::$instance; } private function __clone (){ // Відключаємо клонування } private function __wakeup (){ // Відключаємо десериализацию }}

Приклад використання :

$president1 = President::getInstance();$president2 = President::getInstance();var_dump($president1 === $president2); // true

Приклад на Java.

Переклад статті “Design Patterns for Humans”

Київ, Харків, Одеса, Дніпро, Запоріжжя, Кривий Ріг, Вінниця, Херсон, Черкаси, Житомир, Хмельницький, Чернівці, Рівне, Івано-Франківськ, Кременчук, Тернопіль, Луцьк, Ужгород, Кам'янець-Подільський, Стрий - за статистикою саме з цих міст програмісти найбільше переїжджають працювати до Львова. А Ви розглядаєте relocate?


Trends: патерни проектування

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *