Рубріки: HighloadТеорія

Шаблони проєктування: їх види, особливості й переваги

Андрій Денисенко

Що таке патерн проєктування

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

Шаблони проєктування — це «описи об’єктів, що взаємодіють, та класів, які створено для вирішення загальної проблеми проєктування в конкретному контексті» (з книги «Шаблони проєктування: елементи об’єктно-орієнтованого програмного забезпечення, що повторно використовуються» — Еріх Гамма та ін.).

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

Описи більшості шаблонів дуже формальні, тому їх можна застосовувати в різних контекстах. Але в цих описах є спільні розділи:

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

У деяких каталогах шаблонів надаються інші корисні відомості, наприклад про застосування шаблону, етапи його реалізації та взаємовідносини з іншими шаблонами.

Історія появи шаблонів проєктування

Як з’являються шаблони? Коли одне й те саме рішення успішно застосовується в різних проєктах, хтось дає йому назву і докладний опис.

Шаблони проєктування дозволяють вирішувати найпоширеніші проблеми в об’єктно-орієнтованому проєктуванні (ООП).

Концепцію шаблонів уперше описав Крістофер Олександр у книзі «A Pattern Language: Towns, Buildings, Construction» («Мова шаблонів: міста, будівлі, конструкції»). У ній описано «мову» проєктування міської архітектури. Одиницями цієї мови є шаблони. Вони вказують, якою висоти мають бути вікна, скільки поверхів має бути в будівлі, якою має бути площа зон озеленення поруч і таке інше.

Цю ідею підхопили чотири автори: Еріх Гамма, Джон Вліссідес, Ральф Джонсон і Річард Хелм. Саме вони в 1994 році опублікували книгу «Шаблони проєктування: повторно використовувані елементи об’єктно-орієнтованого програмного забезпечення», у якій застосували концепцію шаблонів проєктування до програмування.

У книзі представлено 23 шаблони для вирішення різних завдань об’єктно-орієнтованого проєктування. Вона дуже швидко стала бестселером. Через довгу назву її стали називати «книгою банди чотирьох», що скоро скоротили до просто «книга GoF».

Згідно з думкою авторів, шаблони проєктування в основному ґрунтуються на зазначених нижче принципах ООП:

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

З того часу було відкрито десятки інших шаблонів ООП. Підхід із застосуванням шаблонів став дуже популярним в інших сферах програмування, тому тепер існує безліч інших шаблонів за межами сфери ООП.

Плюси та мінуси використання шаблонів проєктування

Плюси

Навіть якщо ви раніше не мали уявлення про шаблони, ви все одно реалізували деякі з них, не знаючи про це. Тоді навіщо витрачати час на їх вивчення?

Шаблони проєктування — це комплект випробуваних та протестованих рішень найпоширеніших проблем у сфері проєктування ПЗ.

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

Підвищена ефективність. У вирішенні проблем розробникам не доводиться винаходити велосипед. Вони застосовують перевірені, надійні рішення. Завдяки застосуванню шаблонів їхня робота стає набагато ефективнішою.

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

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

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

Спільна мова. Шаблони проєктування визначають спільну мову, якою ви разом зі своєю командою можете спілкуватися ефективніше. Не потрібно пояснювати, що таке одинак (або синглет), якщо ви знаєте шаблон та його назву.

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

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

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

Підвищення кваліфікації. Знання шаблонів проєктування допомагає вам знаходити подібні фрагменти в коді.

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

Мінуси

Шаблони проєктування часто критикують. Розглянемо типові аргументи проти їх використання.

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

Неефективність рішень. Шаблони — це спроба систематизувати підходи, які вже знайшли широке застосування. Якщо застосовувати шаблони буквально, без адаптації до контексту проєкту, вони не принесуть користі.

Невиправдане використання. Недостатньо просто знати й застосовувати шаблони проєктування. Потрібно знати, коли та як їх застосовувати. Інакше є ризик створення антишаблону, який або не підвищує ефективність, або навіть знижує її. Іноді можна вирішити проблему простіше.

Надмірне застосування. При надмірному використанні шаблонів проєктування ваш проєкт стане надто складним. Тому, перш ніж застосовувати шаблон проєктування у своєму проєкті, потрібно чітко й зрозуміло визначити проблему.

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

Шаблони проєктування — це відмовка ледарів, які не хочуть вивчати принципи ООП. А чому б не вивчити і те, й інше? Чітке розуміння принципів ООП обов’язково допоможе вам у створенні програмного забезпечення. Але якщо ви знаєте, що шаблон програмування добре вирішує певну проблему, то навіщо створювати рішення з нуля?

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

Відмінність патернів проєктування від подібних інструментів

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

  • Архітектурні шаблони є вирішеннями поширених проблем архітектури додатків у певному контексті. Вони описують архітектуру загалом і взаємозв’язок між підсистемами. Яскравим прикладом архітектурного шаблону є «модель-представлення-контролер» (Model-View-Controller — MVC). До цього часу тривають суперечки про те, чи є такі шаблони, як MVVM і MVC, архітектурними, тобто, чи охоплюють вони архітектуру цілого додатка чи підсистеми. Тому архітектурні шаблони є ширшим поняттям, ніж шаблони проєктування, які охоплюють компоненти або частини програми. В архітектурних шаблонах можуть використовуватися декілька шаблонів проєктування.
  • Шаблони часто плутають з алгоритмами, тому що ці концепції описують типові рішення широко відомих завдань. Якщо алгоритм завжди визначає чітку послідовність дій, які потрібно виконати для досягнення певної мети, шаблон є більш високорівневим описом рішення. Код реалізації одного й того самого шаблону в різних програмах буде різним.
  • На відміну від бібліотеки або платформи, які можна просто вставити в проєкт і використовувати, шаблон проєктування є прикладом підходу до вирішення проблеми.

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

Основні види патернів проєктування

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

Найпростіші та низькорівневі шаблони часто називають ідіомами. Зазвичай вони застосовуються до однієї мови програмування.

Найбільш універсальними та високорівневими є архітектурні шаблони. Їх можна застосувати практично в будь-якій мові. На відміну від інших видів шаблонів їх можна використовувати для проєктування архітектури програми цілком.

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

  1. породжувальні шаблони (Creational patterns) надають для створення об’єктів такі механізми, що підвищують гнучкість коду й дають більше можливостей для його повторного використання;
  2. структурні шаблони (Structural patterns) пояснюють, як компонувати об’єкти й класи в більші структури, зберігаючи гнучкість та ефективність цих структур;
  3. поведінкові шаблони (Behavioral patterns) забезпечують ефективний взаємозв’язок між об’єктами й розподіл обов’язків між ними.

Виділяють 23 класичні шаблони проєктування, хоча на даний момент їх виявлено набагато більше.

Породжувальні Структурні Поведінкові
Клас Фабричний метод Адаптер (клас) Інтерпретатор
Шаблонний метод
Об’єкт Абстрактна фабрика
Будівельник
Прототип
Одинак
Адаптер (об’ект)
Міст
Компонувальник
Декоратор
Фасад
Легковаговик
Замісник
Ланцюжок обов’язків
Команда
Ітератор
Посередник
Знімок
Спостерігач
Стан
Стратегія
Відвідувач

Основні шаблони

З 23 класичних шаблонів 7 вважаються найважливішими.

Це:

  • одинак ​​(Singleton);
  • фабричний метод (Factory Method);
  • фасад (Facade);
  • стратегія (Strategy);
  • спостерігач (Observer);
  • будівельник (Builder);
  • адаптер (Adapter).

Шаблон проєктування одинак

Шаблон проєктування одинак відноситься до породжувальних шаблонів.

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

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

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

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

Давайте розглянемо приклад коду реалізації одинака.

1. Створимо клас-одинак.

class SiteMap {

   // Create SiteMap object
   private static SiteMap instance = new SiteMap();

   // Declare private constructor
   // to avoid creating a class instance
   private SiteMap(){}

   // Get the only object available
   public static SiteMap getInstance(){
      return instance;
   }
   
    // Declare a method
   public void showMessage(){
      System.out.println("My Site Map!");
   }
}

2. Отримаємо єдиний екземпляр класу-одинака. Це неможливо зробити за допомогою оператора new (SingleObject object = new SiteMap(); не спрацює).

public class SingletonDemo {

   public static void main(String[] args) {
   
      // Get the only object available
      SiteMap object = SiteMap.getInstance();
      
      // Output the message
      object.showMessage();
   }
}

Шаблон проєктування фабричний метод

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

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

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

Давайте розглянемо приклад коду реалізації фабричного метода.

У Java фабричний метод — це статичний метод, що створює й повертає об’єкт класу, якому він належить.

1. Створюємо інтерфейс.

interface Shape {
    double calculateArea();
}

2. Створюємо конкретні класи, що реалізують цей інтерфейс.

class Rectangle implements Shape {

    double width;
    double height;
    
    Rectangle(double width, double height){
        this.width = width;
        this.height = height;
    }
    
    public double calculateArea() {
        return this.width * this.height;
    }
}

class Circle implements Shape {
    double radius;

    Circle(double radius){
        this.radius = radius;
    }

    public double calculateArea() {
        return Math.PI * (this.radius * this.radius);
    }
}

3. Перевіряємо роботу шаблону.

public class FactoryMethodDemo{

    public static void main(String[] args){

      Rectangle r = new Rectangle(10, 20);
      Circle c = new Circle(25);
      
      System.out.println("Rectangle area is: " + r.calculateArea());
      System.out.println("Circle area is: " + c.calculateArea());
    }
}

Шаблон проєктування фасад

Шаблон проєктування фасад — це структурний шаблон, що надає один інтерфейс (клас) для доступу до великого обсягу коду або різних об’єктів.

Фасад приховує складності різних підсистем (часто впорядкованих в одному класі) за простим інтерфейсом. Наприклад, клієнту у сфері електронної комерції потрібна лише одна точка взаємодії з брендом, яка позбавить необхідності у взаємодії з кожною системою окремо (запаси продукції, безпека, обробка платежу, виконання замовлення тощо). У цьому випадку фасад інкапсулює всі дії та системи «замовлення» в одному інтерфейсі, а клієнт не має уявлення про те, що відбувається за лаштунками.

Фасад — важлива концепція для слабозв’язаної архітектури мікросервісів.

Приклад коду фасаду.

1. Створюємо інтерфейс.

// Declare the common interface
interface Meal {
   void cook();
}

2. Створюємо класи, які реалізують інтерфейс.

// Declare classes implementing the same interface.

class Pizza implements Meal {

   @Override
   public void cook() {
      System.out.println("Cooking a pizza.");
   }
}

class Lasagna implements Meal {

   @Override
   public void cook() {
      System.out.println("Cooking a lasagna.");
   }
}

class Soup implements Meal {

   @Override
   public void cook() {
      System.out.println("Cooking a soup.");
   }
}

3. Створюємо клас фасаду.

// Create a facade class

class MealMaker {
   private Meal pizza;
   private Meal lasagna;
   private Meal soup;

   public MealMaker() {
      pizza = new Pizza();
      lasagna = new Lasagna();
      soup = new Soup();
   }

   public void cookPizza(){
      pizza.cook();
   }
   public void cookLasagna(){
      lasagna.cook();
   }
   public void cookSoup(){
      soup.cook();
   }
}

4. Застосовуємо фасад для виклику методів.

// Use the facade to cook three Meals.

public class FacadeDemo {
   public static void main(String[] args) {
      MealMaker MealMaker = new MealMaker();

      MealMaker.cookPizza();
      MealMaker.cookLasagna();
      MealMaker.cookSoup();      
   }
}

Шаблон проєктування стратегія

Шаблон проєктування стратегія — це поведінковий шаблон проєктування. Друга його назва — «політика». У шаблоні стратегії взаємозамінні алгоритми інкапсульовані в «сімейство», з якого вибирається один алгоритм відповідно до необхідності.

Наприклад, якщо сімейство алгоритмів пов’язане з сортуванням продуктів на веб-сайті електронної комерції (за розміром, кольором, ціною тощо), то стратегія вибирається залежно від дій клієнта.

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

Давайте розглянемо приклад коду реалізації стратегії.

1. Створюємо інтерфейс.

// Declare the interface
interface Strategy {
   double doOperation(double x, double y);
}

2. Створюємо конкретні класи, що використовують інтерфейс.

// Declare classes that implement the same interface.

class AddOperation implements Strategy{
   @Override
   public double doOperation(double x, double y) {
      return x + y;
   }
}

class SubstractOperation implements Strategy{
   @Override
   public double doOperation(double x, double y) {
      return x - y;
   }
}

class MultiplyOperation implements Strategy{
   @Override
   public double doOperation(double x, double y) {
      return x * y;
   }
}

class DivideOperation implements Strategy{
   @Override
   public double doOperation(double x, double y) {
      return x / y;
   }
}

3. Створюємо клас контексту.

// Create context class

class Context {
   private Strategy strategy;

   Context(Strategy strategy){
      this.strategy = strategy;
   }

   double executeStrategy(double x, double y){
      return strategy.doOperation(x, y);
   }
}

4. Застосовуємо контекст.

// Use the Context to see how behaviour changes with the change of Strategy.

public class StrategyDemo {
   public static void main(String[] args) {
      Context context = new Context(new AddOperation());        
      System.out.println("10 + 5 = " + context.executeStrategy(10, 5));

      context = new Context(new SubstractOperation());      
      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));

      context = new Context(new MultiplyOperation());       
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));

      context = new Context(new DivideOperation());       
      System.out.println("10 / 5 = " + context.executeStrategy(10, 5));
   }
}

5. Результат:

10 + 5 = 15.0
10 - 5 = 5.0
10 * 5 = 50.0
10 / 5 = 2.0

Шаблон проєктування спостерігач

Шаблон проєктування спостерігач — це поведінковий шаблон, який зв’язує об’єкт (суб’єкт) із залежними від нього об’єктами (спостерігачами) за моделлю «один до багатьох».

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

Наведемо приклад коду реалізації спостерігача.

1. Оголосимо інтерфейс суб’єкта.

import java.util.ArrayList;
import java.util.List;

// Declare the Subject interface

interface Subject{
    void add(Observer observer);
    void remove(Observer observer);
    void notify(Message message);
}

2. Оголосимо інтерфейс спостерігача.

// Declare the Observer interface

interface Observer{
    void update(Message message);
}

3. Оголосимо конкретний клас суб’єкта.

// Declare concrete Subject

class Publisher implements Subject {
     
    private List observers = new ArrayList<>();
 
    @Override
    public void add(Observer o) {
        observers.add(o);
    }
 
    @Override
    public void remove(Observer o) {
        observers.remove(o);
    }
 
    @Override
    public void notify(Message m) {
        for(Observer o: observers) {
            o.update(m);
        }
    }
}

4. Оголосимо конкретні класи спостерігачів.

// Declare concrete Observers

class Subscriber1 implements Observer 
{
    @Override
    public void update(Message m) {
        System.out.println("Subscriber 1 received a message: " + m.getContent());
    }
}

class Subscriber2 implements Observer 
{
    @Override
    public void update(Message m) {
        System.out.println("Subscriber 2 received a message: " + m.getContent());
    }
}

5. Оголосимо об’єкт стану.

// Declare the state object

class Message 
{
    final String content;
     
    Message (String s) {
        this.content = s;
    }
 
    String getContent() {
        return content;
    }
}

6. Перевіримо взаємодію.

// Test the communication

class ObserverDemo 
{
    public static void main(String[] args) 
    {
        Subscriber1 s1 = new Subscriber1();
        Subscriber2 s2 = new Subscriber2();
         
        Publisher p = new Publisher();
         
        p.add(s1);
        p.add(s2);
         
        p.notify(new Message("First Message")); // both observers will receive the message
         
        p.remove(s1);
         
        p.notify(new Message("Second Message")); // only the second observer will receive the message
    }
}

Шаблон проєктування будівельник

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

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

Наприклад, якщо є клас з безліччю полів, виникає проблема «телескопічних конструкторів». Дуже важко запам’ятати порядок проходження полів і легко помилитися, створюючи код. За допомогою шаблону проєктування «будівельник» ця проблема вирішується так:

Переходимо до прикладу коду реалізації будівельника.

class User{

    // Required fields
    private String email;
    private String password;

    // Optional fields
    private String fullName;
    private int age;
    private String phoneNumber;

    public static class Builder {
        private User newUser;

        public Builder(){
            newUser = new User();
        }

        public Builder setEmail(String email){
            newUser.email = email;
            return this;
        }

        public Builder setPassword(String password){
            newUser.password = password;
            return this;
        }

        public Builder setFullName(String fullName){
            newUser.fullName = fullName;
            return this;
        }

        public Builder setAge(int age){
            newUser.age = age;
            return this;
        }

        public Builder setPhoneNumber(String phoneNumber){
            newUser.phoneNumber = phoneNumber;
            return this;
        }

        public User build(){
            return newUser;
        }

    }

    public void print(){
        System.out.print(
            "User:     " + this.fullName + "\n" +
            "Email:    " + this.email + "\n" +
            "Password: " + this.password + "\n" +
            "Age:      " + this.age + "\n" +
            "Phone:    " + this.phoneNumber + "\n");
        }
}

class BuilderDemo{
    public static void main(String[] args){
        User myUser = new User.Builder()
            .setEmail("name@example.com")
            .setPassword("secret123")
            .setFullName("New User")
            .setAge(30)
            .setPhoneNumber("555-55-55")
            .build();

        myUser.print();
    }
}

Шаблон проєктування адаптер

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

Шаблони адаптерів корисні, коли потрібно створити послідовний API зі змішаних інтерфейсів.

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

Приклад коду реалізації адаптера:

// Declare the 1st interface
interface Transport
{
    void travel();
}

// Declare the class implementing the 1st interface
class Car implements Transport{
    public void travel(){
        System.out.println("Traveling by car");
    }
}

// Declare the client class
class Passenger{
    public void travel(Transport transport){
        transport.travel();
    }
}

// Declare the 2nd interface
interface Animal
{
    void walk();
}

// Declare the class implementing the 2nd interface
class Horse implements Animal{
    public void walk(){
        System.out.println("Riding a horse");
    }
}

// Declare the adapter
class HorseAdapter implements Transport
{
    Horse horse;
    public HorseAdapter(Horse horse)
    {
        this.horse = horse;
    }
 
    public void travel()
    {
        horse.walk();
    }
}

// Run the demo
public class AdapterDemo{
    public static void main(String[] args){
        Passenger p = new Passenger();
        Car c = new Car();
        p.travel(c);

        Horse h = new Horse();
        Transport hTransport = new HorseToTransportAdapter(h);
        hTransport.travel();
    }
}

В результаті отримуємо:

Traveling by car
Riding a horse

Породжувальні шаблони проєктування (Creational)

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

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

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

Породжувальні шаблони проєктування:

  • фабричний метод (Factory Method);
  • абстрактна фабрика (Abstract Factory);
  • будівельник (Builder);
  • одинак ​​(Singleton);
  • прототип (Prototype).

Розглянемо приклад використання породжувального шаблону проєктування.

Припустимо, необхідно забезпечити взаємодію з кількома серверами баз даних, наприклад MySQL і PostgreSQL. Якщо як бекенд використовується MySQL, але в майбутньому ви плануєте перейти на PostgreSQL, то потрібно змінити весь код. Але шаблон проєктування «фабрика» забезпечить слабке зв’язування, просту реалізацію та створення таких видів об’єктів.

Фабричний метод: створення об’єктів із загальним інтерфейсом та передача обов’язків щодо створення екземплярів підкласам

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

Абстрактна фабрика: створення сімейства взаємозалежних об’єктів

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

Будівельник: покроковий шаблон для створення складних об’єктів із відділенням конструювання від подання

Шаблон проєктування будівельник покроково створює складний об’єкт із простих об’єктів. Такий будівельник не залежить від інших об’єктів.

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

Шаблон прототип забезпечує створення дубліката об’єкта з урахуванням продуктивності.

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

Одинак: обмеження кількості об’єктів класу одним екземпляром

Шаблон проєктування одинак — один із найпростіших шаблонів проєктування.

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

Структурні шаблони проєктування (Structural)

Структурні шаблони проєктування відповідають за об’єднання класів і об’єктів у більші структури, які забезпечують нову функціональність.

Структурні шаблони проєктування:

  • адаптер (Adapter);
  • міст (Bridge);
  • компонувальник (Composite);
  • декоратор (Decorator);
  • фасад (Facade);
  • легковаговик (Flyweight);
  • заступник (Proxy).

Давайте розглянемо приклад використання структурного шаблону проєктування.

Коли два інтерфейси несумісні один з одним і необхідно забезпечити взаємозв’язок між ними, використовується шаблон проєктування адаптер. Цей шаблон перетворює інтерфейс класу на інший інтерфейс або клас, на який очікує клієнт. Він дозволяє двом несумісним класам працювати спільно.

Адаптер: адаптація інтерфейсу до іншого існуючого класу для забезпечення взаємодії несумісних інтерфейсів

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

Міст: метод відокремлює інтерфейс від реалізації

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

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

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

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

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

Декоратор: динамічне розширення (додавання або перевизначення) функцій

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

Цей шаблон створює клас декоратора, який є оболонкою існуючого класу та додає нових функцій, не змінюючи сигнатури методів класу.

Фасад: визначення високорівневого інтерфейсу для спрощення використання великого тіла коду

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

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

Легковаговик: зменшення споживання пам’яті за рахунок спільного використання даних із подібними об’єктами

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

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

Замісник: подання об’єкта іншим об’єктом для контролю доступу та спрощення

Коли використовується шаблон проєктування замісник, один клас представляє функції іншого класу.

При використанні цього шаблону створюється об’єкт, що містить вихідний об’єкт і надає інтерфейс взаємодії із зовнішнім світом.

Поведінкові шаблони проєктування (Behavioral)

Поведінкові шаблони проєктування відповідають за взаємозв’язок між об’єктами та за розподіл обов’язків між ними.

Поведінкові шаблони проєктування:

  • ланцюжок обов’язків (Chain of responsibility);
  • команда (Command);
  • ітератор (Iterator);
  • посередник (Mediator);
  • знімок (Memento);
  • спостерігач (Observer);
  • стан (State);
  • стратегія (Strategy);
  • шаблонний метод (Template method);
  • відвідувач (Visitor).

Приклад використання шаблону поведінкового проєктування

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

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

Ланцюжок обов’язків: метод делегування команд ланцюжку обробних об’єктів

Як випливає з назви, шаблон проєктування ланцюжок обов’язків створює ланцюжок об’єктів-отримувачів запиту. Цей шаблон відокремлює відправника від одержувача запиту, залежно від типу запиту.

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

Команда: інкапсуляція запиту команди в об’єкті

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

Ітератор: забезпечує ітеративний (послідовний) доступ до елементів колекції

Шаблон проєктування команда широко застосовується в середовищах програмування Java і .Net. Він використовується для послідовного звернення до об’єктів колекції без необхідності знати їхнє базове подання.

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

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

Знімок: процес збереження та відновлення вихідного стану об’єкта

Шаблон проєктування знімок використовується для відновлення попереднього стану об’єкта.

Спостерігач: визначення способу сповіщення об’єктів про зміни в інших об’єктах

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

Стан: зміна поведінки об’єкта у відповідь на зміну стану

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

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

Стратегія: інкапсуляції алгоритму в класі

У разі використання шаблону проєктування стратегія поведінку класу чи його алгоритм можна змінювати під час виконання.

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

Відвідувач: визначення нової операції над класом без змінення класу

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

Шаблонний метод: визначення скелета операції та надання підкласам права на уточнення деяких кроків

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

Висновок

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

На завершення наводимо детальну таблицю шаблонів проєктування.

Призначення Шаблон проєктування Аспекти, які можуть змінюватись
Породжувальні Абстрактна фабрика сімейства об’єктів продуктів
Будівельник спосіб створення складного об’єкта
Фабричний метод підклас об’єкта, екземпляр якого створюється
Прототип клас об’єкта, екземпляр якого створюється
Одинак єдиний екземпляр класу
Структурні Адаптер інтерфейс об’єкта
Міст реалізація об’єкта
Компонувальник структура й склад об’єкта
Декоратор обов’язки об’єкта без створення підкласів
Фасад інтерфейс підсистеми
Легковаговик витрати на зберігання об’єктів
Замісник спосіб доступу до об’єкта; його розташування
Поведінкові Ланцюжок обов’язків об’єкт, який може виконати запит
Команда час та спосіб виконання запиту
Інтерпретатор граматика й мова інтерпретації
Ітератор спосіб доступу до елементів цілого та їх обходу
Посередник підбір об’єктів для взаємодії та спосіб їх взаємодії один з одним
Знімок приватна інформація, яка зберігається поза об’єктом, та час її збереження
Спостерігач кількість об’єктів, які залежать від іншого об’єкта; спосіб підтримання актуальності залежних об’єктів
Стан стан об’єкта
Стратегія алгоритм
Шаблонний метод кроки алгоритму
Відвідувач операції, які можна застосувати до об’єктів без змінення класів

Повний код прикладів можна знайти в цьому репозиторії.

Наостанок тримайте курс з шаблонів проєктування:

 

Останні статті

Українські програмісти створили Lağoda QT — гру-головоломку кримськотатарською мовою

Українські програмісти створили безплатну гру-головоломку Lağoda QT.  Кожен рівень — вірш одного з видатних кримськотатарських…

07.05.2024

В Copilot для Microsoft 365 додали українську мову

Корпорація Microsoft оголосила про підтримку української мови у Copilot для Microsoft 365. Українська мова входить…

07.05.2024

Google безплатно навчатиме створювати чат-боти за допомогою Gemini. Потрібно тільки знання Python

Корпорація Google запустила реєстрацію задля участі в безплатній програмі Startup School: Gen AI. Програма безплатна…

07.05.2024

Вакансій і наймів більше, а зарплати — менше: що відбувалося на ринку праці у квітні

В квітні на ринку праці збільшилася кількість вакансій для IT-фахівців. На DOU та Djinni спостерігались…

07.05.2024

І всього лише $300. Китайці представили ноутбук на базі RISC-V для ШІ-девелоперів

Китайський стартап SpacemiT представив MuseBook — ноутбук на базі восьмиядерного процесора K1 RISC-V, орієнтований на…

06.05.2024

Учасники Brave1 створили ШІ-платформу HARVESTER для органів держбезпеки

Учасники Brave1, українська команда MATHESIS, розробила для органів держбезпеки платформу HARVESTER на основі штучного інтелекту.…

06.05.2024