Menu
Coddy logo textTech

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

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

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

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

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 }

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

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

jsonProc := &BaseProcessor{Impl: JSONProcessor{}}
fmt.Println(jsonProc.Execute()) // Записано: parsed-json-data

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

challenge icon

Задание

Легко

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

Вы организуете код в трех файлах:

  • report.go: Define ваш interface и базовый processor, который orchestrates algorithm generation отчета.

    Create interface ReportGenerator с тремя methods, представляющими шаги generation отчета:

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

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

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

    Create два типа отчетов:

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

    Прочитайте тип отчета (sales или inventory) и configuration value (название региона для sales, название склада для inventory). Create подходящий генератор, оберните его в ReportProcessor, вызовите Generate() и выведите результат с помощью print.

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

  • Строка 1: тип отчета (sales или inventory)
  • Строка 2: configuration value (название региона или название склада)

Например, если введено:

sales
North

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

Printed: SALES REPORT: sales-data-North

А если введено:

inventory
MainHub

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

Exported: *** inventory-MainHub ***

А если введено:

sales
West

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

Printed: SALES REPORT: sales-data-West

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

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

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Проверьте себя

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

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

Потренируйтесь самостоятельно: Онлайн-компилятор Go