Паттерн Strategy
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 90 из 107.
Паттерн Стратегия позволяет определить семейство алгоритмов, инкапсулировать каждый из них и сделать их взаимозаменяемыми во время выполнения. В отличие от Наблюдателя, где несколько объектов реагируют на одно событие, Стратегия позволяет одному объекту динамически переключаться между различными вариантами поведения.
В Go мы реализуем паттерн «Стратегия», определяя интерфейс для алгоритма и внедряя различные реализации в структуру контекста (context struct):
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 хранит ссылку на интерфейс strategy, позволяя заменять алгоритм без изменения кода 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 через кредитную карту
checkout.SetStrategy(PayPal{})
fmt.Println(checkout.ProcessPayment(50.00)) // Оплачено 50.00 через PayPalСтратегия идеальна, когда у вас есть несколько способов выполнения операции и необходимо выбрать один из них на основе пользовательского ввода, конфигурации или условий времени выполнения. Общие варианты использования включают алгоритмы сортировки, методы сжатия и правила валидации.
Задание
ЛегкоДавайте создадим систему форматирования текста, используя паттерн Стратегия (Strategy)! Вы создадите различные стратегии форматирования, которые можно менять во время выполнения программы, что позволит одному и тому же процессору текста выдавать различные стили вывода без изменения его основного кода.
Вы распределите свой код по трем файлам:
formatter.go: Определите интерфейс стратегии и конкретные стратегии форматирования.Создайте интерфейс
TextFormatterс методомFormat(text string) string, который преобразует текст в соответствии с правилами стратегии.Реализуйте три стратегии форматирования:
UppercaseFormatter— преобразует текст в верхний регистрSnakeCaseFormatter— заменяет пробелы на подчеркивания и переводит текст в нижний регистрTitleFormatter— делает заглавной первую букву каждого слова (слова разделены пробелами)
processor.go: Создайте структуру контекста, которая использует стратегию форматирования.Создайте структуру
TextProcessor, которая содержит стратегиюTextFormatter. Добавьте следующие методы:SetFormatter(f TextFormatter)для изменения активной стратегии форматированияProcess(text string) string, который применяет текущий форматировщик к тексту
Также создайте конструктор
NewTextProcessor(), который возвращает процессор без изначально установленного форматировщика.main.go: Продемонстрируйте переключение стратегий во время выполнения.Прочитайте фрагмент текста, затем прочитайте количество операций форматирования. Для каждой операции прочитайте тип формата (
upper,snakeилиtitle), установите соответствующий форматировщик в процессоре, обработайте исходный текст и выведите результат.
Будут предоставлены следующие входные данные:
- Строка 1: Текст для форматирования
- Строка 2: Количество операций форматирования
- Следующие строки: Тип формата для каждой операции
Например, при вводе:
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 выдает совершенно разные результаты, просто меняя стратегию форматировщика. Процессор не знает и не заботится о том, какой именно форматировщик он использует — он просто делегирует задачу той стратегии, которая установлена в данный момент!
Шпаргалка
Паттерн Стратегия (Strategy) определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми во время выполнения. Он позволяет одному объекту динамически переключаться между различными вариантами поведения.
Определите интерфейс для алгоритма:
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)
}Создайте структуру контекста, которая хранит ссылку на интерфейс стратегии:
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Стратегия идеальна, когда у вас есть несколько способов выполнения операции и необходимо выбрать один из них на основе ввода пользователя, конфигурации или условий во время выполнения.
Попробуйте сами
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