Template Method (Шаблонный метод)¶
Категория: паттерн поведения.
Проблема¶
Несколько алгоритмов почти одинаковы по структуре: одна и та же последовательность шагов, но одна или несколько шагов реализованы по-разному в разных вариантах. Если писать каждый вариант отдельным независимым методом/классом, общая часть логики дублируется и её приходится синхронно поддерживать сразу в нескольких местах.
Решение¶
- Общий алгоритм оформляется как метод в базовом классе - шаблонный метод. Он вызывает набор более мелких шагов в фиксированном порядке.
- Шаги, одинаковые для всех вариантов, реализуются прямо в базовом классе.
- Шаги, которые различаются, объявляются
abstract(обязательны к переопределению) либоvirtualс реализацией по умолчанию (хук - подкласс может переопределить его, а может оставить как есть). - Сам шаблонный метод обычно не делают
virtual, чтобы подклассы не могли случайно изменить порядок шагов или пропустить часть логики.
Структура¶
AbstractClass- объявляет шаблонный метод и набор шагов (абстрактных и/или виртуальных).ConcreteClass- переопределяет нужные шаги, не трогая сам шаблонный метод.
Когда применять¶
- Несколько классов реализуют вариации одного и того же алгоритма, отличающиеся только частью шагов.
- Нужно контролировать структуру алгоритма в одном месте (в базовом классе), позволяя подклассам настраивать только определённые точки расширения.
- Общая логика должна быть переиспользована без копипасты между вариантами.
Плюсы¶
- Устраняет дублирование общей части алгоритма.
- Гарантирует единый порядок шагов во всех вариантах - его невозможно случайно нарушить в подклассе.
- Хуки дают точечные точки расширения без необходимости переопределять весь алгоритм целиком.
Минусы¶
- Основан на наследовании, а не на композиции - подкласс жёстко привязан к структуре базового класса и не может легко "собрать" алгоритм из частей разных базовых классов.
- Чем больше шагов и хуков, тем сложнее читать итоговое поведение конкретного подкласса - нужно смотреть и в базовый класс, и в переопределения.
- В runtime нельзя поменять поведение отдельного шага без создания нового подкласса (в отличие от Strategy, где реализация подставляется динамически).
Стратегия vs Шаблонный метод¶
Оба паттерна выделяют "изменяемую часть" поведения, но делают это по-разному: - Template Method использует наследование: изменяемая часть - это переопределённый метод в подклассе, порядок шагов зафиксирован в базовом классе. - Strategy использует композицию: изменяемая часть - это отдельный объект, подставляемый в контекст, который можно поменять в рантайме без создания нового класса.
Как правило, если нужно менять поведение динамически или без построения иерархии наследования - выбирают Strategy; если общей логики много, а меняется только один-два шага - компактнее выходит Template Method.
Реализация в C# через делегаты и методы расширения¶
Классический вариант через наследование - не единственный в .NET. Шаблонный метод можно выразить и без иерархии классов: - локальный шаблонный метод на основе делегатов - метод принимает делегаты на изменяемые шаги как параметры, вместо того чтобы требовать создания подкласса; - методы расширения - общий алгоритм пишется как метод расширения над интерфейсом с несколькими абстрактными операциями, что позволяет обойтись без базового класса вовсе.
Оба варианта дают тот же эффект (общая логика в одном месте, различающиеся шаги - подключаемые), но без накладных расходов на построение иерархии наследования - что часто удобнее с точки зрения тестируемости.
Пример в .NET Framework / BCL¶
Stream.CopyTo- общий алгоритм копирования, использующий переопределяемыеRead/Writeу конкретных наследниковStream.- Жизненный цикл ASP.NET Web Forms (
Page_Init,Page_Load,Page_PreRender, ...) - классический (хоть и устаревший) пример шаблонного метода на уровне целого фреймворка. - Базовые классы
IEnumerator/абстрактные коллекции, где общий алгоритм обхода фиксирован, а конкретное получение элемента переопределяется.
Пример реализации на C#¶
Открыть TemplateMethod.cs отдельно
Скачать TemplateMethod.cs