Шаблонный метод (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 нарушается, если подкласс ослабляет гарантии алгоритма.
Чем отличается
- Strategy заменяет алгоритм объектом и использует композицию;
- Фабричный метод Factory Method бывает переопределяемым шагом Template Method;
- Строитель Builder собирает объект, Template Method задаёт порядок алгоритма;
- Цепочка обязанностей Chain of Responsibility динамически связывает обработчики.
Вопросы на собеседовании
- Зачем
final? Чтобы подкласс не нарушил порядок и гарантии. - Что такое hook? Необязательная точка расширения с поведением по умолчанию.
- Главный недостаток? Жёсткая связь через наследование.
- Когда выбрать Strategy? Когда алгоритм нужно подменять во время выполнения, тестировать отдельно или свободно комбинировать.
- Может ли шаблонный метод быть в интерфейсе?
default-метод способен задать часть каркаса, но интерфейс не хранит состояние и имеет иные ограничения доступа.
Связи: Поведенческие паттерны, Принципы SOLID, Шаблоны проектирования.