Паттерн Шаблонный метод
Часть раздела Объектно-ориентированное программирование путешествия по 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Шаблонный метод идеально подходит, когда у вас есть алгоритмы с одинаковой структурой, но различающиеся в конкретных шагах, таких как конвейеры импорта/экспорта данных, генераторы отчетов или тестовые фреймворки.
Задание
ЛегкоДавайте создадим систему генерации отчетов, используя паттерн Шаблонный метод (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: "+dataOutputReport(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")
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker Pool