Паттерн Шаблонный метод
Часть раздела Объектно-ориентированное программирование путешествия по 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-dataTemplate Method идеально подходит, когда у вас есть алгоритмы, которые имеют одинаковую структуру, но различаются на определённых этапах, например в конвейерах импорта/экспорта data, генераторах отчётов или тестовых фреймворках.
Задание
ЛегкоДавайте создадим систему генерации отчетов с использованием паттерна 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. Добавьте methodGenerate() 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")
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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Потренируйтесь самостоятельно: Онлайн-компилятор Go