Также известен как Template Method

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

Суть паттерна

Определяет скелет алгоритма, перекладывая ответственность за некоторые его шаги на подклассы.

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

Проблема

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

Решение

Паттерн предлагает разбить алгоритм на отдельные шаги, вынести их в отдельные методы и вызывать их в одном «шаблонном» методе друг за другом.

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

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

Строительство типовых домов

Строители используют подход, похожий на шаблонный метод при строительстве типовых домов. У них есть основной архитектурный проект, в котором расписаны шаги строительства — заливка фундамента, постройка стен, постановка крыши, установка окон, обивка и так далее.

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

Структура

Схема структуры классов паттерна Шаблонный Метод
  1. Абстрактный класс определяет шаги алгоритма и содержит шаблонный метод, состоящий из вызовов этих шагов.

    Шаги могут быть как абстрактными, так и содержать реализацию по умолчанию.

  2. Конкретный класс переопределяет некоторые (или все) шаги алгоритма.

    Конкретные классы не переопределяют сам шаблонный метод.

Псевдокод

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

class GameAI is
    // Шаблонный метод должен быть задан в базовом классе. Он состоит из вызовов
    // методов в определённом порядке. Чаще всего эти методы являются шагами
    // некоего алгоритма.
    method turn() is
        collectResources()
        buildStructures()
        buildUnits()
        attack()

    // Некоторые из этих методов могут быть реализованы прямо в базовом классе.
    method collectResources() is
        foreach this.structures
            structure.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 enough resources then
            Build farms, then barracks, then stronghold.

    method buildUnits() is
        If enough resources
            If scouts not exist, build 1 peon.
            Else build grunt.

    // ...

    method sendScouts(position) is
        If scouts exists, send scouts to position.

    method sendWarriors(position) is
        If grunts are more than 5, then send warriors to position.

// Подклассы могут не только реализовывать абстрактные шаги, но и переопределять
// шаги, уже реализованные в базовом классе.
class MonstersAI extends GameAI is
    method collectResources() is
        Do nothing.

    method buildStructures() is
        Do nothing.

    method buildUnits() is
        Do nothing.

Применимость

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

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

У вас есть несколько классов, делающих одно и то же с незначительными отличиями. Если вы редактируете один класс, то приходится вносить такие же правки и в остальные классы.

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

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

Шаги реализации

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

  2. Создайте абстрактный базовый класс. Опишите в нём шаблонный метод. Этот метод должен состоять из вызовов шагов алгоритма. Имеет смысл сделать шаблонный метод финальным, чтобы подклассы не могли переопределить его (если ваш язык это позволяет).

  3. Добавьте в абстрактный класс методы для каждого из шагов алгоритма. Стандартные шаги должны иметь реализацию по умолчанию. Изменяемые шаги должны быть объявлены абстрактными. Их нужно будет реализовать в подклассах.

  4. Подумайте о введении в алгоритм хуков. Хук — это «опциональный» шаг — метод с пустой реализацией, определённый в базовом классе. Шаблонный метод будет работать, даже если ни один подкласс не переопределит хук.

  5. Создайте конкретные классы наследниками абстрактного класса. Реализуйте в них все недостающие шаги и хуки.

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

Преимущества и недостатки

  • Облегчает повторное использование кода.
  • Вы ограничены скелетом существующего алгоритма.

Отношения с другими паттернами

  • Фабричный метод можно рассматривать как частный случай Шаблонного метода. Кроме того, Фабричный метод нередко бывает частью большого класса с Шаблонными методами.

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

Реализация в различных языках программирования

Java