Шаблонный метод (Template Method)

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

Сигналы применения

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

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

Роли

  • Abstract Class содержит final шаблонный метод;
  • Primitive Operations — обязательные переопределяемые шаги;
  • Hook — необязательный шаг с реализацией по умолчанию;
  • Concrete Class реализует вариативные части;
  • Client вызывает только шаблонный метод.

Пример на Java

abstract class SampleAnalyzer {
    public final double analyze(List<Double> source) {
        if (source.isEmpty()) throw new IllegalArgumentException("empty");
        List<Double> prepared = normalize(source);
        double result = calculate(prepared);
        afterCalculation(result);
        return result;
    }
    protected List<Double> normalize(List<Double> source) {
        return source.stream().filter(Double::isFinite).toList();
    }
    protected abstract double calculate(List<Double> values);
    protected void afterCalculation(double result) {} // необязательный hook
}
final class AverageAnalyzer extends SampleAnalyzer {
    protected double calculate(List<Double> values) {
        return values.stream().mapToDouble(Double::doubleValue).average().orElseThrow();
    }
}
double value = new AverageAnalyzer()
        .analyze(List.of(10.0, 20.0, 30.0));

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

Проектирование базового класса

Шаблонный метод обычно делают final, а шаги — protected. Чем больше hooks и предусловий, тем труднее подклассу соблюдать контракт основы.

Нужно описать пустые значения, исключения и вызовы hooks. Иначе изменение основы неожиданно ломает подклассы — возникает fragile base class.

Компромиссы

  • общий порядок задан один раз;
  • дублирование между вариантами уменьшается;
  • наследование связывает реализации сильнее композиции;
  • подкласс ограничен предусмотренными точками расширения;
  • слишком общий базовый класс превращается в набор флагов и пустых hooks;
  • LSP нарушается, если подкласс ослабляет гарантии алгоритма.

Чем отличается

Вопросы на собеседовании

  1. Зачем final? Чтобы подкласс не нарушил порядок и гарантии.
  2. Что такое hook? Необязательная точка расширения с поведением по умолчанию.
  3. Главный недостаток? Жёсткая связь через наследование.
  4. Когда выбрать Strategy? Когда алгоритм нужно подменять во время выполнения, тестировать отдельно или свободно комбинировать.
  5. Может ли шаблонный метод быть в интерфейсе? default-метод способен задать часть каркаса, но интерфейс не хранит состояние и имеет иные ограничения доступа.

Связи: Поведенческие паттерны, Принципы SOLID, Шаблоны проектирования.