
Шаблон «Стратегия»
В программной разработке при создании сопровождаемых, масштабируемых и гибких систем важны шаблоны проектирования. Шаблоном «Стратегия» определяется семейство алгоритмов с инкапсулированием каждого из них, ею они делаются взаимозаменяемыми. Особенно полезен этот шаблон в сценариях, где стратегии или поведения применяются динамически без изменения кодовой базы.
Когда?
- Динамические изменения поведения: когда поведение объекта нужно поменять динамически во время выполнения, исходя из условий.
- Уход от логики условного перехода: чтобы избежать нагромождения операторов
if-elseилиswitchпри выборе алгоритмов. - Переиспользуемые алгоритмы: когда имеется набор связанных алгоритмов для взаимозаменяемого применения.
Для чего?
Шаблоном «Стратегия» обеспечивается четкое разделение обязанностей посредством делегирования логики поведения различным стратегиям классов. Так повышаются гибкость и масштабируемость при соблюдении принципов объектно-ориентированного проектирования.
Как?

Шаблоном «Стратегия» определяется интерфейс для семейства алгоритмов. Каждый алгоритм инкапсулируется конкретными реализациями интерфейса, при помощи которых классом контекста применяется желаемое поведение.
Этапы:
- Определяется интерфейс «Стратегии»: для операторов if-else / switch создается интерфейс, стандартная функциональность в if-else / switch обобщается методами интерфейса.
- Создаются конкретные стратегии для каждого из операторов if-else.
- Для динамического управления отбором и выполнением стратегий используется класс контекста.
Где применяется шаблон «Стратегия»
- Платежные шлюзы: при выборе способов оплаты (кредитная карта, PayPal, криптовалюта).
- Алгоритмы сортировки: переключение между быстрой сортировкой, сортировкой слиянием или пузырьком в зависимости от размера данных.
- Сжатие: применение различных стратегий сжатия (ZIP, RAR, GZIP).
- Аутентификация: выбор механизмов аутентификации (OAuth, JWT, LDAP).
- Машинное обучение: динамическое применение алгоритмов вроде линейной регрессии, ансамбля случайного леса.
Код
Обобщенный код
package main
import "fmt"
func main() {
num1, num2 := 10, 5
operation := "add"
// Базовая логика «if-else» для различных операций
switch operation {
case "add":
fmt.Printf("Addition Result: %d\n", num1+num2)
case "subtract":
fmt.Printf("Subtraction Result: %d\n", num1-num2)
case "multiply":
fmt.Printf("Multiplication Result: %d\n", num1*num2)
case "divide":
if num2 != 0 {
fmt.Printf("Division Result: %d\n", num1/num2)
} else {
fmt.Println("Error: Division by zero")
}
default:
fmt.Println("Unknown operation")
}
}
Хороший код
package main
import "fmt"
// Этап 1: определяется интерфейс «Стратегии»
type OperationStrategy interface {
Execute(a, b int) int
}
// Этап 2: для каждой операции создаются конкретные стратегии
type AddStrategy struct{}
func (s AddStrategy) Execute(a, b int) int {
return a + b
}
type SubtractStrategy struct{}
func (s SubtractStrategy) Execute(a, b int) int {
return a - b
}
type MultiplyStrategy struct{}
func (s MultiplyStrategy) Execute(a, b int) int {
return a * b
}
type DivideStrategy struct{}
func (s DivideStrategy) Execute(a, b int) int {
if b != 0 {
return a / b
}
fmt.Println("Error: Division by zero")
return 0
}
// Этап 3: применяется класс контекста
type Calculator struct {
Strategy OperationStrategy
}
func (c *Calculator) SetStrategy(strategy OperationStrategy) {
c.Strategy = strategy
}
func (c Calculator) Calculate(a, b int) int {
return c.Strategy.Execute(a, b)
}
func main() {
calculator := Calculator{}
calculator.SetStrategy(AddStrategy{})
fmt.Printf("Addition Result: %d\n", calculator.Calculate(10, 5))
calculator.SetStrategy(SubtractStrategy{})
fmt.Printf("Subtraction Result: %d\n", calculator.Calculate(10, 5))
calculator.SetStrategy(MultiplyStrategy{})
fmt.Printf("Multiplication Result: %d\n", calculator.Calculate(10, 5))
calculator.SetStrategy(DivideStrategy{})
fmt.Printf("Division Result: %d\n", calculator.Calculate(10, 5))
}
Соотносим это с примером шаблона «Фабрика»:
package main
import (
"fmt"
"time"
)
// «Task» — это универсальная задача с общими атрибутами
type Task struct {
ID string
CreatedAt time.Time
Description string
}
// Поведение для выполняемых задач определяется в «TaskStrategy»
// Этап 1: определяется интерфейс «Стратегии»
type TaskStrategy interface {
Execute()
}
// Операции баз данных управляются в «DatabaseTask»
// Этап 2: создаются конкретные стратегии
type DatabaseTask struct {
Task
Query string
DBName string
Connected bool
}
func (dt *DatabaseTask) Execute() {
if !dt.Connected {
fmt.Printf("[DatabaseTask] Connecting to database: %s\n", dt.DBName)
dt.Connected = true
}
fmt.Printf("[DatabaseTask] Executing query: %s\n", dt.Query)
}
type APITask struct {
Task
URL string
Headers map[string]string
}
func (at *APITask) Execute() {
fmt.Printf("[APITask] Sending request to URL: %s with headers: %v\n", at.URL, at.Headers)
}
type FileProcessingTask struct {
Task
FilePath string
FileType string
}
func (fpt *FileProcessingTask) Execute() {
fmt.Printf("[FileProcessingTask] Processing file: %s of type: %s\n", fpt.FilePath, fpt.FileType)
}
// Выполнение стратегий управляется в «TaskContext»
// Этап 3: применяется класс контекста
type TaskContext struct {
Strategy TaskStrategy
}
func (tc *TaskContext) SetStrategy(strategy TaskStrategy) {
tc.Strategy = strategy
}
func (tc TaskContext) ExecuteTask() {
tc.Strategy.Execute()
}
func main() {
context := TaskContext{}
// Конфигурирование «DatabaseTask»
dbTask := &DatabaseTask{
Task: Task{
ID: "DB001",
CreatedAt: time.Now(),
Description: "Run a database query",
},
Query: "SELECT * FROM users",
DBName: "UserDB",
}
context.SetStrategy(dbTask) // «DatabaseTask» становится стратегией
context.ExecuteTask()
// Конфигурирование «APITask»
apiTask := &APITask{
Task: Task{
ID: "API001",
CreatedAt: time.Now(),
Description: "Send an API request",
},
URL: "https://api.example.com/data",
Headers: map[string]string{"Authorization": "Bearer token", "Content-Type": "application/json"},
}
context.SetStrategy(apiTask) // «APITask» становится стратегией
context.ExecuteTask()
// Конфигурирование «FileProcessingTask»
fileTask := &FileProcessingTask{
Task: Task{
ID: "FILE001",
CreatedAt: time.Now(),
Description: "Process a CSV file",
},
FilePath: "/data/files/report.csv",
FileType: "CSV",
}
context.SetStrategy(fileTask)
context.ExecuteTask()
}
Заключение
«Стратегия» — это поведенческий шаблон проектирования для динамического выбора алгоритмов или поведений во время выполнения с инкапсулированием их в отдельные классы. Особенно полезен он для упрощения кода, осложненного операторами if-else или switch, при повышении гибкости и соблюдении принципа открытости/закрытости.
Благодаря определению общего интерфейса для стратегий, созданию конкретных реализаций для каждого алгоритма и управлению их выполнением с помощью класса контекста, шаблоном «Стратегия» обеспечиваются четкое разделение обязанностей, повышенная тестопригодность и более легкая масштабируемость. Он активно применяется в таких сценариях, как обработка платежей, сортировка, сжатие и системы аутентификации. Более того, с этим шаблоном также используется отношение has-a.
Читайте также:
- Golang + htmx + Tailwind CSS = адаптивное веб-приложение
- Миграции баз данных с Golang
- Принципы SOLID на Go
Читайте нас в Telegram, VK и Дзен
Перевод статьи Shantanu Saini: Go Strategy Pattern





