← Назад к вопросам

Зачем нужен шаблонный метод?

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();
    }
}

Преимущества

  1. Снижение дублирования - общая логика в одном месте
  2. Контроль - базовый класс определяет структуру
  3. Легкость расширения - новый класс = новая реализация метода
  4. Соответствие DRY - "Don't Repeat Yourself"
  5. Явная структура - ясно, какие шаги есть в алгоритме

Недостатки

  1. Жёсткая иерархия - требует наследования
  2. Сложность - может быть избыточным для простых случаев
  3. Хрупкость - изменение базового класса влияет на все подклассы

Альтернатива: 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 нужен для:

  • Определения скелета алгоритма с переопределяемыми шагами
  • Устранения дублирования кода
  • Контроля над порядком выполнения
  • Создания расширяемых фреймворков

Это один из самых полезных паттернов в разработке фреймворков и приложений, требующих расширяемости.

Зачем нужен шаблонный метод? | PrepBro