← Назад к вопросам
Зачем нужен шаблонный метод?
2.0 Middle🔥 191 комментариев
#SOLID и паттерны проектирования#ООП#Основы Java
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Зачем нужен шаблонный метод (Template Method)?
Шаблонный метод — это поведенческий паттерн проектирования, который определяет скелет алгоритма в базовом классе, позволяя подклассам переопределять отдельные шаги, не меняя структуру алгоритма.
Суть паттерна
Основная идея:
Один и тот же процесс состоит из похожих шагов, но некоторые шаги различаются в зависимости от типа.
Проблема без паттерна
// ПЛОХО - дублирование логики
public class PdfReport {
public void generate() {
// Шаг 1
collectData();
// Шаг 2
validateData();
// Шаг 3 - специфичен для PDF
formatAsPdf();
// Шаг 4
sendReport();
}
private void collectData() { /* */ }
private void validateData() { /* */ }
private void formatAsPdf() { /* PDF специфичное */ }
private void sendReport() { /* */ }
}
public class ExcelReport {
public void generate() {
// Шаг 1 - ДУБЛИРОВАНИЕ
collectData();
// Шаг 2 - ДУБЛИРОВАНИЕ
validateData();
// Шаг 3 - специфичен для Excel
formatAsExcel();
// Шаг 4 - ДУБЛИРОВАНИЕ
sendReport();
}
private void collectData() { /* */ }
private void validateData() { /* */ }
private void formatAsExcel() { /* Excel специфичное */ }
private void sendReport() { /* */ }
}
// Много одинакового кода!
Решение: Template Method
// Базовый класс - определяет шаблон
public abstract class Report {
// Шаблонный метод - финальный, не переопределяется
public final void generate() {
// Структура алгоритма задана здесь
collectData();
validateData();
format(); // Абстрактный метод - переопределяется подклассами
sendReport();
}
// Общие шаги - одинаковые для всех
private void collectData() {
System.out.println("Collecting data from database...");
}
private void validateData() {
System.out.println("Validating collected data...");
}
private void sendReport() {
System.out.println("Sending report to recipients...");
}
// Абстрактный метод - каждый подкласс реализует по-своему
protected abstract void format();
}
// Подклассы реализуют только различающиеся шаги
public class PdfReport extends Report {
@Override
protected void format() {
System.out.println("Formatting as PDF...");
}
}
public class ExcelReport extends Report {
@Override
protected void format() {
System.out.println("Formatting as Excel...");
}
}
public class HtmlReport extends Report {
@Override
protected void format() {
System.out.println("Formatting as HTML...");
}
}
// Использование
Report pdfReport = new PdfReport();
pdfReport.generate();
// Output:
// Collecting data from database...
// Validating collected data...
// Formatting as PDF...
// Sending report to recipients...
Report excelReport = new ExcelReport();
excelReport.generate();
// Output:
// Collecting data from database...
// Validating collected data...
// Formatting as Excel...
// Sending report to recipients...
Структура паттерна
AbstractClass
├── final generate() { // Template Method
│ ├── collectData() // Concrete Method
│ ├── validate() // Concrete Method
│ ├── format() // Abstract Method (hook)
│ └── send() // Concrete Method
├── abstract format()
Зачем нужен Template Method?
1. Устранение дублирования кода (DRY)
// Без паттерна - одна логика в каждом классе
// С паттерном - одна логика в базовом классе
2. Контроль расширения
public abstract class DataProcessor {
// Подклассы ДОЛЖНЫ переопределить эти методы
protected abstract void parseInput();
protected abstract void processData();
protected abstract void outputResult();
// Но структура (порядок) контролируется базовым классом
public final void execute() {
parseInput();
processData();
outputResult();
}
}
3. Инверсия управления (Hollywood Principle)
Не вызывай нас, мы вызовем тебя
// Фреймворк определяет скелет приложения
public abstract class ApplicationFramework {
public final void run() {
initialize();
loadConfiguration();
startApplication(); // Это вызывает наш код
setupUI();
handleEvents();
}
// Мы переопределяем только нужные части
protected abstract void loadConfiguration();
protected abstract void startApplication();
}
Практический пример: обработка платежей
public abstract class PaymentProcessor {
// Template Method
public final void processPayment(Payment payment) {
validatePayment(payment);
authorizePayment(payment); // Различается по типу платежа
capturePayment(payment);
updateInventory(payment);
sendConfirmation(payment);
}
// Общие шаги
private void validatePayment(Payment payment) {
if (payment.getAmount() <= 0) {
throw new IllegalArgumentException("Invalid amount");
}
}
private void capturePayment(Payment payment) {
System.out.println("Capturing payment...");
}
private void updateInventory(Payment payment) {
System.out.println("Updating inventory...");
}
private void sendConfirmation(Payment payment) {
System.out.println("Sending confirmation email...");
}
// Абстрактный метод - различается
protected abstract void authorizePayment(Payment payment);
}
public class CreditCardProcessor extends PaymentProcessor {
@Override
protected void authorizePayment(Payment payment) {
System.out.println("Authorizing credit card...");
// Специфичная логика для кредитной карты
}
}
public class PayPalProcessor extends PaymentProcessor {
@Override
protected void authorizePayment(Payment payment) {
System.out.println("Authorizing through PayPal...");
// Специфичная логика для PayPal
}
}
public class BankTransferProcessor extends PaymentProcessor {
@Override
protected void authorizePayment(Payment payment) {
System.out.println("Authorizing bank transfer...");
// Специфичная логика для банковского перевода
}
}
Hooks - дополнительные точки расширения
У паттерна есть концепция "hooks" — дополнительные методы для тонкого управления:
public abstract class Order {
public final void process() {
// Основные шаги
validate();
calculateTax();
prepareShipment();
// Hooks - точки расширения (default implementation)
onBeforeShipment(); // Можно переопределить
ship();
onAfterShipment(); // Можно переопределить
}
protected void onBeforeShipment() {
// Default: ничего не делаем
}
protected void onAfterShipment() {
// Default: ничего не делаем
}
// Остальные методы...
}
public class PremiumOrder extends Order {
@Override
protected void onBeforeShipment() {
// Для премиум заказов добавляем подарок
addGiftToShipment();
}
@Override
protected void onAfterShipment() {
// Отправляем благодарственное письмо
sendThankYouEmail();
}
}
Преимущества
- Снижение дублирования - общая логика в одном месте
- Контроль - базовый класс определяет структуру
- Легкость расширения - новый класс = новая реализация метода
- Соответствие DRY - "Don't Repeat Yourself"
- Явная структура - ясно, какие шаги есть в алгоритме
Недостатки
- Жёсткая иерархия - требует наследования
- Сложность - может быть избыточным для простых случаев
- Хрупкость - изменение базового класса влияет на все подклассы
Альтернатива: Strategy вместо Template Method
// Вместо наследования можно использовать composition
public class Report {
private ReportFormatter formatter;
public Report(ReportFormatter formatter) {
this.formatter = formatter;
}
public void generate() {
collectData();
validateData();
formatter.format(); // Стратегия
sendReport();
}
}
public interface ReportFormatter {
void format();
}
Заключение
Template Method нужен для:
- Определения скелета алгоритма с переопределяемыми шагами
- Устранения дублирования кода
- Контроля над порядком выполнения
- Создания расширяемых фреймворков
Это один из самых полезных паттернов в разработке фреймворков и приложений, требующих расширяемости.