Menu
Coddy logo textTech

Паттерн Шаблонный метод

Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 94 из 107.

Паттерн Шаблонный метод (Template Method) определяет скелет алгоритма в базовом типе, позволяя подтипам переопределять отдельные шаги без изменения общей структуры. В то время как Декоратор (Decorator) добавляет поведение путем оборачивания объектов, Шаблонный метод управляет логикой алгоритма и позволяет настраивать его отдельные этапы.

В Go, поскольку наследование отсутствует, мы реализуем этот паттерн с помощью встраивания структур в сочетании с интерфейсами. Структура «шаблон» определяет структуру алгоритма и вызывает методы, которые можно настроить:

type DataProcessor interface {
    ReadData() string
    ProcessData(data string) string
    SaveData(result string) string
}

type BaseProcessor struct {
    Impl DataProcessor
}

func (b *BaseProcessor) Execute() string {
    data := b.Impl.ReadData()
    result := b.Impl.ProcessData(data)
    return b.Impl.SaveData(result)
}

Конкретные реализации предоставляют свои собственные версии каждого шага, в то время как поток алгоритма остается неизменным:

type CSVProcessor struct{}

func (c CSVProcessor) ReadData() string       { return "csv-data" }
func (c CSVProcessor) ProcessData(d string) string { return "processed-" + d }
func (c CSVProcessor) SaveData(r string) string    { return "Saved: " + r }

type JSONProcessor struct{}

func (j JSONProcessor) ReadData() string       { return "json-data" }
func (j JSONProcessor) ProcessData(d string) string { return "parsed-" + d }
func (j JSONProcessor) SaveData(r string) string    { return "Stored: " + r }

Шаблонный метод Execute координирует выполнение шагов в фиксированном порядке:

csvProc := &BaseProcessor{Impl: CSVProcessor{}}
fmt.Println(csvProc.Execute())  // Сохранено: processed-csv-data

jsonProc := &BaseProcessor{Impl: JSONProcessor{}}
fmt.Println(jsonProc.Execute()) // Сохранено: parsed-json-data

Шаблонный метод идеально подходит, когда у вас есть алгоритмы с одинаковой структурой, но различающиеся в конкретных шагах, таких как конвейеры импорта/экспорта данных, генераторы отчетов или тестовые фреймворки.

challenge icon

Задание

Легко

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

Вы распределите свой код по трем файлам:

  • report.go: Определите интерфейс и базовый процессор, который управляет алгоритмом генерации отчета.

    Создайте интерфейс ReportGenerator с тремя методами, которые представляют этапы генерации отчета:

    • GatherData() string — извлекает необработанные данные для отчета
    • FormatData(data string) string — преобразует данные в формат отчета
    • OutputReport(formatted string) string — формирует итоговое сообщение вывода

    Создайте структуру ReportProcessor, которая содержит реализацию ReportGenerator. Добавьте метод Generate() string, который последовательно выполняет три шага: сбор, форматирование, затем вывод — возвращая финальный результат.

  • generators.go: Реализуйте конкретные генераторы отчетов, которые настраивают каждый шаг.

    Создайте два типа отчетов:

    • SalesReport с полем Region (string)
      • GatherData() возвращает "sales-data-" + регион
      • FormatData(data) возвращает "SALES REPORT: " + data
      • OutputReport(formatted) возвращает "Printed: " + formatted
    • InventoryReport с полем Warehouse (string)
      • GatherData() возвращает "inventory-" + склад
      • FormatData(data) возвращает "*** " + data + " ***"
      • OutputReport(formatted) возвращает "Exported: " + formatted
  • main.go: Продемонстрируйте, как одна и та же структура алгоритма выдает разные результаты в зависимости от реализации.

    Считайте тип отчета ("sales" или "inventory") и значение конфигурации (регион для продаж, склад для инвентаризации). Создайте соответствующий генератор, оберните его в ReportProcessor, вызовите Generate() и выведите результат.

Будут предоставлены следующие входные данные:

  • Строка 1: Тип отчета ("sales" или "inventory")
  • Строка 2: Значение конфигурации (название региона или название склада)

Например, для входных данных:

sales
North

Ваш вывод должен быть:

Printed: SALES REPORT: sales-data-North

И для входных данных:

inventory
MainHub

Ваш вывод должен быть:

Exported: *** inventory-MainHub ***

И для входных данных:

sales
West

Ваш вывод должен быть:

Printed: SALES REPORT: sales-data-West

Обратите внимание, как ReportProcessor всегда вызывает одни и те же три шага в одном и том же порядке, но каждый тип отчета предоставляет свою собственную реализацию этих шагов. Скелет алгоритма остается неизменным, в то время как детали меняются — это и есть паттерн Шаблонный метод в действии!

Шпаргалка

Паттерн Шаблонный метод (Template Method) определяет основу алгоритма в базовом типе, позволяя подтипам переопределять определенные шаги без изменения общей структуры.

В Go этот паттерн реализуется с использованием встраивания структур в сочетании с интерфейсами. «Шаблонная» структура определяет структуру алгоритма и вызывает методы, которые можно настроить:

type DataProcessor interface {
    ReadData() string
    ProcessData(data string) string
    SaveData(result string) string
}

type BaseProcessor struct {
    Impl DataProcessor
}

func (b *BaseProcessor) Execute() string {
    data := b.Impl.ReadData()
    result := b.Impl.ProcessData(data)
    return b.Impl.SaveData(result)
}

Конкретные реализации предоставляют свои собственные версии каждого шага, в то время как поток алгоритма остается неизменным:

type CSVProcessor struct{}

func (c CSVProcessor) ReadData() string       { return "csv-data" }
func (c CSVProcessor) ProcessData(d string) string { return "processed-" + d }
func (c CSVProcessor) SaveData(r string) string    { return "Saved: " + r }

type JSONProcessor struct{}

func (j JSONProcessor) ReadData() string       { return "json-data" }
func (j JSONProcessor) ProcessData(d string) string { return "parsed-" + d }
func (j JSONProcessor) SaveData(r string) string    { return "Stored: " + r }

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

csvProc := &BaseProcessor{Impl: CSVProcessor{}}
fmt.Println(csvProc.Execute())  // Saved: processed-csv-data

jsonProc := &BaseProcessor{Impl: JSONProcessor{}}
fmt.Println(jsonProc.Execute()) // Stored: parsed-json-data

Шаблонный метод идеально подходит, когда алгоритмы имеют одинаковую структуру, но различаются в конкретных шагах, таких как конвейеры импорта/экспорта данных, генераторы отчетов или фреймворки для тестирования.

Попробуйте сами

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Чтение типа отчета
	scanner.Scan()
	reportType := scanner.Text()
	
	// Чтение значения конфигурации (регион или склад)
	scanner.Scan()
	configValue := scanner.Text()

	// TODO: Создать соответствующий генератор на основе reportType
	// - Если reportType равен "sales", создать SalesReport с Region, установленным в configValue
	// - Если reportType равен "inventory", создать InventoryReport с Warehouse, установленным в configValue
	
	// TODO: Создать ReportProcessor с этим генератором
	
	// TODO: Вызвать Generate() и вывести результат
	_ = reportType
	_ = configValue
	fmt.Println("TODO: Generate and print the report")
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование