Паттерн Strategy
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 90 из 107.
Паттерн Strategy позволяет определить семейство алгоритмов, инкапсулировать каждый из них и сделать их взаимозаменяемыми во время выполнения. В отличие от Observer, где несколько объектов реагируют на одно событие, Strategy позволяет одному объекту динамически переключаться между различными вариантами поведения.
В Go мы реализуем Strategy, определяя interface для алгоритма и внедряя различные реализации в структуру context:
type PaymentStrategy interface {
Pay(amount float64) string
}
type CreditCard struct{}
func (c CreditCard) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f via Credit Card", amount)
}
type PayPal struct{}
func (p PayPal) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f via PayPal", amount)
}Структура context хранит ссылку на интерфейс стратегии, что позволяет заменить алгоритм без изменения кода context:
type Checkout struct {
strategy PaymentStrategy
}
func (c *Checkout) SetStrategy(s PaymentStrategy) {
c.strategy = s
}
func (c *Checkout) ProcessPayment(amount float64) string {
return c.strategy.Pay(amount)
}Теперь вы можете изменить способ оплаты во время выполнения:
checkout := &Checkout{}
checkout.SetStrategy(CreditCard{})
fmt.Println(checkout.ProcessPayment(100.00)) // Оплачено 100.00 через Credit Card
checkout.SetStrategy(PayPal{})
fmt.Println(checkout.ProcessPayment(50.00)) // Оплачено 50.00 через PayPalПаттерн Strategy идеально подходит, когда у вас есть несколько способов выполнить операцию и нужно выбрать один из них на основе пользовательского ввода, конфигурации или условий во время выполнения. Распространённые варианты использования включают алгоритмы сортировки, методы сжатия и правила валидации.
Задание
ЛегкоДавайте создадим систему форматирования текста с использованием паттерна Strategy! Вы создадите различные стратегии форматирования, которые можно переключать во время выполнения, позволяя одному и тому же обработчику текста создавать результаты в разных стилях без изменения его основного кода.
Вы организуете код в трёх файлах:
formatter.go: Define ваш интерфейс стратегии и конкретные стратегии форматирования.Create интерфейс
TextFormatterс методомFormat(text string) string, который преобразует текст согласно правилам стратегии.Implement три стратегии форматирования:
UppercaseFormatter: converts текст в верхний регистрSnakeCaseFormatter: converts пробелы в символы подчёркивания и makes текст строчными буквамиTitleFormatter: capitalizes первую букву каждого слова (слова разделены пробелами)
processor.go: Create вашу структуру context, которая использует стратегию форматирования.Создайте структуру
TextProcessor, которая holds стратегиюTextFormatter. Добавьте следующие методы:SetFormatter(f TextFormatter)для изменения active стратегии форматированияProcess(text string) string, которая applies current formatter к тексту
Также создайте конструктор
NewTextProcessor(), который возвращает processor без установленного initial formatter.main.go: Demonstrate переключение стратегий во время выполнения.Прочитайте фрагмент текста, затем число operations форматирования. Для каждой operation прочитайте тип форматирования (
upper,snakeилиtitle), установите appropriate formatter для вашего processor, обработайте исходный текст и выведите результат.
Будут предоставлены следующие входные данные:
- Строка 1: Текст для форматирования
- Строка 2: Number операций форматирования
- Следующие строки: Тип форматирования для каждой operation
Например, для:
hello world today
3
upper
snake
titleВаш результат должен быть таким:
HELLO WORLD TODAY
hello_world_today
Hello World TodayА для:
Go Programming Language
2
snake
upperВаш результат должен быть таким:
go_programming_language
GO PROGRAMMING LANGUAGEА для:
design patterns are useful
1
titleВаш результат должен быть таким:
Design Patterns Are UsefulОбратите внимание, как один и тот же TextProcessor создаёт полностью разные результаты только благодаря переключению своей стратегии formatter. Processor не знает и не заботится о том, какой именно formatter он использует. Он просто delegates выполнение текущей установленной стратегии!
Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Прочитать текст для форматирования
text, _ := reader.ReadString('\n')
text = strings.TrimSpace(text)
// Прочитать количество операций
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// Создать новый текстовый процессор
processor := NewTextProcessor()
// Обработать каждую операцию форматирования
for i := 0; i < count; i++ {
formatType, _ := reader.ReadString('\n')
formatType = strings.TrimSpace(formatType)
// TODO: На основе formatType ("upper", "snake", или "title"),
// установить соответствующий форматтер на процессоре
// и вывести обработанный текст
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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