Открыть сервис

Шаблон проектирования «Шаблонный метод

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

Назначение и решаемая проблема

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

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

Структура и участники

Шаблонный метод включает в себя следующие ключевые компоненты:

  • AbstractClass (Абстрактный класс): Определяет абстрактные и переопределяемые (хуки) методы, представляющие шаги алгоритма. Реализует шаблонный метод, который вызывает эти шаги в определённом порядке. Шаблонный метод обычно объявляется как final (в языках, поддерживающих такое ключевое слово) или sealed, чтобы подклассы не могли изменить его структуру.
  • ConcreteClass (Конкретный класс): Реализует абстрактные шаги алгоритма и может переопределять хуки. Может содержать несколько конкретных классов, каждый из которых предоставляет свою версию вариативных шагов.
  • Template Method (Шаблонный метод): Сам метод, реализованный в абстрактном классе. Он определяет последовательность вызовов шагов. Этот метод не должен быть переопределён в подклассах.
  • Primitive Operations (Примитивные операции): Абстрактные методы, которые обязательно должны быть реализованы в подклассах.
  • Hook Methods (Методы-хуки): Методы с пустой или стандартной реализацией в абстрактном классе. Подклассы могут, но не обязаны их переопределять. Хуки позволяют встраивать дополнительное поведение в определённые точки алгоритма.

Пример реализации на Java

Рассмотрим пример приготовления напитков. Алгоритм приготовления чая и кофе одинаков: вскипятить воду, заварить, налить в чашку, добавить добавки. Отличаются только шаги заваривания и добавок.

```java // Абстрактный класс abstract class CaffeineBeverage { // Шаблонный метод — объявлен как final, чтобы подклассы не могли его переопределить public final void prepareRecipe() { boilWater(); brew(); pourInCup(); if (customerWantsCondiments()) { // хук addCondiments(); } }

// Абстрактные шаги — обязательны к реализации abstract void brew(); abstract void addCondiments();

// Конкретные шаги — общие для всех private void boilWater() { System.out.println("Кипячение воды"); }

private void pourInCup() { System.out.println("Наливание в чашку"); }

// Хук — может быть переопределён, но не обязан boolean customerWantsCondiments() { return true; // по умолчанию — да } }

// Конкретный класс — Чай class Tea extends CaffeineBeverage { @Override void brew() { System.out.println("Заваривание чайного пакетика"); }

@Override void addCondiments() { System.out.println("Добавление лимона"); } }

// Конкретный класс — Кофе class Coffee extends CaffeineBeverage { @Override void brew() { System.out.println("Заваривание молотого кофе"); }

@Override void addCondiments() { System.out.println("Добавление сахара и молока"); }

// Переопределение хука @Override boolean customerWantsCondiments() { // Например, можно спросить у пользователя return true; } } ```

Отличия от других шаблонов

Шаблонный метод часто путают со стратегией (Strategy) и фабричным методом (Factory Method). Основные различия:

  • Шаблонный метод vs Стратегия: Шаблонный метод использует наследование и жёстко фиксирует структуру алгоритма на уровне класса. Стратегия использует композицию и позволяет подменять целые алгоритмы во время выполнения, передавая объект-стратегию. Шаблонный метод — это «скелет», а стратегия — «алгоритм целиком».
  • Шаблонный метод vs Фабричный метод: Фабричный метод является частным случаем шаблонного метода, где шаблонный метод создаёт объект. Фабричный метод фокусируется на создании одного объекта, а шаблонный метод — на последовательности действий.
  • Шаблонный метод vs Мост (Bridge): Мост разделяет абстракцию и реализацию, позволяя изменять их независимо. Шаблонный метод фиксирует порядок шагов, но позволяет переопределять их.

Применение в реальных проектах

Шаблонный метод широко используется в различных фреймворках и библиотеках:

  • Java Servlet API: Методы doGet(), doPost() и другие являются хуками, вызываемыми из шаблонного метода service(), который определяет общий жизненный цикл сервлета.
  • Java AbstractList: Метод iterator() реализован как шаблонный метод, вызывающий get() и size(), которые переопределяются в подклассах.
  • Фреймворки для тестирования (JUnit, NUnit): Методы setUp(), tearDown() — хуки, вызываемые из шаблонного метода, реализующего жизненный цикл теста.
  • Сборка игровых объектов: В игровых движках (например, Unity) метод Update() является шаблонным, вызывающим хуки Start(), OnCollisionEnter() и т. д.
  • Сборщики отчётов: Общий алгоритм генерации отчёта (сбор данных, форматирование, вывод) фиксируется в шаблонном методе, а конкретные шаги (формат вывода, источник данных) реализуются в подклассах.

Критика и ограничения

Шаблонный метод имеет ряд недостатков:

  • Ограничение гибкости: Из-за использования наследования структура алгоритма жёстко фиксируется в базовом классе. Изменить порядок шагов в подклассе невозможно без переопределения всего шаблонного метода, что нарушает его идею.
  • Усложнение иерархии классов: Для каждого варианта алгоритма требуется создавать отдельный подкласс, что может привести к разрастанию иерархии и усложнению её понимания.
  • Нарушение принципа подстановки Лисков (LSP): Если подкласс переопределяет хук таким образом, что нарушает ожидаемое поведение алгоритма, это может привести к ошибкам.
  • Сложность отладки: Из-за разнесения логики по нескольким классам отслеживание выполнения алгоритма может быть затруднено.
  • Ограничение на уровне языка: В языках, не поддерживающих ключевые слова final или sealed, разработчик полагается на дисциплину, чтобы не переопределить шаблонный метод в подклассе.

Источники

  • Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. «Приёмы объектно-ориентированного проектирования. Паттерны проектирования» (GoF).
  • Фримен Э., Фримен Э., Сьерра К., Бейтс Б. «Паттерны проектирования» (Head First Design Patterns).
  • Шаллоуэй А., Тротт Дж. «Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию».
  • Мартин Р. «Чистый код: создание, анализ и рефакторинг».

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →