Menu
Coddy logo textTech

Паттерн 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

Стратегия идеальна, когда у вас есть несколько способов выполнения операции и необходимо выбрать один из них на основе пользовательского ввода, конфигурации или условий времени выполнения. Общие варианты использования включают алгоритмы сортировки, методы сжатия и правила валидации.

challenge icon

Задание

Легко

Давайте создадим систему форматирования текста, используя паттерн Стратегия (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"),
		// установить соответствующий форматировщик в процессоре
		// и вывести обработанный текст
		
	}
}
quiz iconПроверьте себя

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

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