Autumn SALE

Шаблонний метод

Також відомий як: Template Method

Суть патерна

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

Патерн Шаблонний метод

Проблема

Ви пишете програму для дата-майнінгу в офісних документах. Користувачі завантажуватимуть до неї документи різних форматів (PDF, DOC, CSV), а програма повинна видобути з них корисну інформацію.

У першій версії ви обмежилися обробкою тільки DOC файлів. У наступній версії додали підтримку CSV. А через місяць «прикрутили» роботу з PDF документами.

Класи дата-майнінгу містять багато дублювань

Класи дата-майнінгу містять багато дублювань.

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

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

Рішення

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

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

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

Шаблонний метод містить виклики методів-кроків

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

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

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

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

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

Аналогія з життя

Будівництво типових будинків

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

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

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

Структура

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

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

Псевдокод

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

Структура класів прикладу патерна Шаблонний метод

Приклад класів штучного інтелекту для простої гри.

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

class GameAI is
    // Шаблонний метод повинен бути заданий у базовому класі.
    // Він складається з викликів методів у певному порядку.
    // Здебільшого, ці методи є кроками якогось алгоритму.
    method turn() is
        collectResources()
        buildStructures()
        buildUnits()
        attack()

    // Деякі з цих методів можуть бути реалізовані безпосередньо
    // у базовому класі.
    method collectResources() is
        foreach (s in this.builtStructures) do
            s.collect()

    // А деякі можуть бути повністю абстрактними.
    abstract method buildStructures()
    abstract method buildUnits()

    // До речі, клас може мати більше одного шаблонного методу.
    method attack() is
        enemy = closestEnemy()
        if (enemy == null)
            sendScouts(map.center)
        else
            sendWarriors(enemy.position)

    abstract method sendScouts(position)
    abstract method sendWarriors(position)

// Підкласи можуть надавати свою реалізацію кроків алгоритму, не
// змінюючи сам шаблонний метод.
class OrcsAI extends GameAI is
    method buildStructures() is
        if (there are some resources) then
            // Будувати ферми, потім бараки, а потім цитадель.

    method buildUnits() is
        if (there are plenty of resources) then
            if (there are no scouts)
                // Побудувати раба, додати до групи розвідників.
            else
                // Побудувати піхотинця, додати до групи воїнів.

    // ...

    method sendScouts(position) is
        if (scouts.length > 0) then
            // Відправити розвідників на позицію.

    method sendWarriors(position) is
        if (warriors.length > 5) then
            // Відправити воїнів на позицію.

// Підкласи можуть не тільки реалізовувати абстрактні кроки, але
// й перевизначати кроки, вже реалізовані в базовому класі.
class MonstersAI extends GameAI is
    method collectResources() is
        // Нічого не робити.

    method buildStructures() is
        // Нічого не робити.

    method buildUnits() is
        // Нічого не робити.

Застосування

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

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

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

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

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

Кроки реалізації

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

  2. Створіть абстрактний базовий клас. Визначте в ньому шаблонний метод. Цей метод повинен складатися з викликів кроків алгоритму. Є сенс у тому, щоб зробити шаблонний метод фінальним, аби підкласи не могли перевизначити його (якщо ваша мова програмування це дозволяє).

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

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

  5. Створіть конкретні класи, успадкувавши їх від абстрактного класу. Реалізуйте в них всі кроки та хуки, яких не вистачає.

Переваги та недоліки

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

Відносини з іншими патернами

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

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

Приклади реалізації патерна

Шаблонний метод на C# Шаблонний метод на C++ Шаблонний метод на Go Шаблонний метод на Java Шаблонний метод на PHP Шаблонний метод на Python Шаблонний метод на Ruby Шаблонний метод на Rust Шаблонний метод на Swift Шаблонний метод на TypeScript