Strategy — что это, паттерн стратегия в iOS и Android

Автор: IT Sectr Опубликовано: 2026-02-18 Время чтения: 9 мин

Strategy (Стратегия) — поведенческий паттерн проектирования, который определяет семейство взаимозаменяемых алгоритмов и помещает каждый из них в отдельный класс (Strategy). Паттерн позволяет выбирать алгоритм на лету: клиентский код работает через общий интерфейс Strategy, а конкретная реализация подставляется в рантайме. В iOS паттерн реализуется через Protocol + классы-стратегии, в Android — через Interface + имплементации. Strategy — один из 23 паттернов GoF, широко применяется для обработки платежей, валидации, сортировки и фильтрации данных. Подробнее — в оригинальном описании GoF.

Главное

  • Strategy — поведенческий паттерн GoF для семейства взаимозаменяемых алгоритмов
  • Инкапсуляция алгоритмов — каждый алгоритм изолирован в собственном классе
  • Интерфейс Strategy — общий контракт, который реализуют все конкретные стратегии
  • Композиция вместо наследования — контекст хранит ссылку на стратегию, а не наследует поведение
  • Open/Closed Principle — новые стратегии добавляются без изменения существующего кода

Что такое паттерн Strategy: суть и структура

Strategy — один из 23 паттернов GoF (Gang of Four), описанный в книге «Design Patterns: Elements of Reusable Object-Oriented Software» (1994). Паттерн решает задачу выбора алгоритма в рантайме. Вместо того чтобы писать один класс с множеством условных операторов (if-else, switch), Strategy предлагает выделить каждый алгоритм в отдельный класс с общим интерфейсом. Контекст (класс, использующий стратегию) хранит ссылку на интерфейс Strategy и делегирует выполнение конкретной стратегии.

Структура паттерна включает три элемента: Context (контекст) содержит ссылку на Strategy и вызывает её метод; Strategy (интерфейс) объявляет общий метод для всех алгоритмов; ConcreteStrategy (конкретная стратегия) реализует интерфейс и содержит конкретный алгоритм. Клиент создаёт нужную стратегию и передаёт её в контекст через конструктор, сеттер или параметр метода. Контекст не знает, какая именно стратегия выполняется — он работает только с интерфейсом.

КомпонентРольПример
ContextСодержит ссылку на StrategyPaymentProcessor, Sorter
StrategyОбщий интерфейс для алгоритмовProtocol PaymentStrategy
ConcreteStrategyКонкретная реализация алгоритмаCardPayment, PayPalPayment

Принцип Open/Closed — основное преимущество Strategy. Система открыта для расширения (можно добавить новую стратегию) и закрыта для изменения (не нужно менять код контекста). Без паттерна добавление нового алгоритма требует изменения существующего класса, что нарушает OCP и увеличивает риск регрессионных ошибок. Strategy также уменьшает размер классов: вместо 200-строчного класса с switch-case получается 6 классов по 20 строк каждый.

Strategy в iOS: реализация на Swift с Protocol

Strategy на Swift реализуется через Protocol (интерфейс стратегии) и классы или структуры-стратегии. Swift-протоколы поддерживают associated types и generic constraints, что даёт гибкость при проектировании стратегий. Контекст обычно — класс ViewModel или сервис, который принимает стратегию в init или через свойство. Паттерн широко используется в iOS-проектах для обработки событий, анимаций, форматирования данных и UI-стратегий.

swift
// 1. Protocol Strategy
protocol PaymentStrategy {
    func pay(amount: Decimal) async throws -> PaymentResult
}

// 2. Concrete Strategies
struct CardPaymentStrategy: PaymentStrategy {
    let cardNumber: String
    let cvv: String

    func pay(amount: Decimal) async throws -> PaymentResult {
        // Отправка запроса в банковский API
        return PaymentResult(status: .success, transactionId: "tx_\(UUID())")
    }
}

struct PayPalPaymentStrategy: PaymentStrategy {
    let email: String

    func pay(amount: Decimal) async throws -> PaymentResult {
        // Перенаправление на PayPal SDK
        return PaymentResult(status: .success, transactionId: "pp_\(UUID())")
    }
}

// 3. Context
class PaymentProcessor {
    private var strategy: PaymentStrategy

    init(strategy: PaymentStrategy) {
        self.strategy = strategy
    }

    func setStrategy(_: PaymentStrategy) {
        strategy = strategy
    }

    func processPayment(amount: Decimal) async throws -> PaymentResult {
        return try await strategy.pay(amount: amount)
    }
}

// Использование
let processor = PaymentProcessor(strategy: CardPaymentStrategy(cardNumber: "4111...", cvv: "123"))
let result = try await processor.processPayment(amount: 99.99)

Strategy в SwiftUI — паттерн естественно интегрируется с MVVM. ViewModel содержит свойство стратегии и вызывает её метод при действии пользователя. SwiftUI View получает данные через @Published или @State — стратегия скрывает детали реализации от View. Например, стратегия валидации текста (emailValidator, phoneValidator) подменяется в зависимости от типа поля ввода. Комбинирование Strategy с SwiftUI даёт гибкость без наследования от UIKit.

Strategy в Android: реализация на Kotlin с Interface

Strategy на Kotlin использует Interface на уровне языка и функциональные интерфейсы (SAM) для упрощения. Kotlin поддерживает лямбды, что позволяет передавать алгоритмы как функции без объявления отдельного класса стратегии. В Android паттерн применяется в ViewModel и Use Cases для изоляции алгоритмов загрузки данных, кэширования и обработки ошибок. Android-проекты с Clean Architecture используют Strategy для инжекции разных реализаций репозитория в зависимости от флагов (mock, real, cache).

kotlin
// 1. Interface Strategy
interface PaymentStrategy {
    suspend fun pay(amount: BigDecimal): PaymentResult
}

// 2. Concrete Strategies
class CardPaymentStrategy(
    private val cardNumber: String,
    private val cvv: String
) : PaymentStrategy {
    override suspend fun pay(amount: BigDecimal): PaymentResult {
        // Банковский API через Retrofit
        return PaymentResult(success = true, transactionId = "tx_${UUID.randomUUID()}")
    }
}

class PayPalPaymentStrategy(
    private val email: String
) : PaymentStrategy {
    override suspend fun pay(amount: BigDecimal): PaymentResult {
        // PayPal SDK integration
        return PaymentResult(success = true, transactionId = "pp_${UUID.randomUUID()}")
    }
}

// 3. Context
class PaymentProcessor(
    private val strategy: PaymentStrategy
) {
    fun setStrategy(strategy: PaymentStrategy): PaymentProcessor {
        return PaymentProcessor(strategy)
    }

    suspend fun processPayment(amount: BigDecimal): PaymentResult {
        return strategy.pay(amount)
    }
}

// Использование в ViewModel
class CheckoutViewModel : ViewModel() {
    private var processor = PaymentProcessor(CardPaymentStrategy("4111...", "123"))

    fun payWithCard() {
        viewModelScope.launch {
            val result = processor.processPayment(BigDecimal("99.99"))
            // Обработка результата
        }
    }
}

Strategy с Hilt/Dagger — в Android-проектах стратегии часто инжектятся через DI. Hilt предоставляет конкретную реализацию PaymentStrategy через @Binds или @Provides. Это позволяет менять стратегию без изменения кода контекста — достаточно изменить модуль DI для другой сборки (debug/release). Например, для отладки инжектится MockPaymentStrategy, для прода — реальная банковская стратегия. Комбинация Strategy + DI даёт максимальную гибкость.

Сравнение Strategy с State, Command и Template Method

Strategy vs State — структурно паттерны идентичны: оба используют композицию с интерфейсом и конкретными классами. Разница в назначении: Strategy выбирает независимый алгоритм, State управляет поведением объекта в зависимости от его состояния. В State контекст сам меняет стратегию при изменении состояния, в Strategy контекст не контролирует переключение — клиент явно задаёт алгоритм. Стратегии не знают друг о друге, состояния — могут переходить между собой.

Strategy vs Command — Command инкапсулирует одно действие как объект, Strategy инкапсулирует набор взаимозаменяемых алгоритмов. Command — «что сделать» (один вызов execute), Strategy — «как сделать» (алгоритм из нескольких шагов). Command используется для очередей, отложенного выполнения, undo/redo. Strategy — для выбора способа выполнения задачи в рантайме. Команды можно параметризовать стратегиями, комбинируя оба паттерна.

ХарактеристикаStrategyStateCommandTemplate Method
НазначениеВзаимозаменяемые алгоритмыПоведение от состоянияИнкапсуляция запросаСкелет алгоритма
СменаКлиентом явноКонтекстом автоматическиКлиентом или очередьюНаследованием
УровеньОбъектный (композиция)Объектный (композиция)ОбъектныйКлассовый (наследование)

Strategy vs Template Method — оба паттерна определяют алгоритмы, но разными способами. Template Method использует наследование: базовый класс определяет скелет алгоритма (шаблонный метод), подклассы переопределяют отдельные шаги. Strategy использует композицию: алгоритм полностью вынесен в отдельный класс. Template Method проще для случаев с фиксированной структурой алгоритма, Strategy — когда алгоритмы полностью разные и могут меняться динамически.

Реальные примеры использования паттерна Strategy

Обработка платежей — классический пример Strategy. Корзина интернет-магазина содержит список товаров, а способ оплаты выбирается пользователем. Каждый способ (карта, PayPal, Apple Pay, Google Pay, криптовалюта) — отдельная стратегия с общей сигнатурой pay(amount). Контекст PaymentProcessor не знает, как именно проводится платёж — он вызывает общий метод. Добавление нового способа оплаты не требует изменения кода корзины.

Валидация данных — Strategy применяется для разных правил валидации одного поля. EmailValidatorStrategy, PhoneValidatorStrategy, AgeValidatorStrategy реализуют общий интерфейс ValidationStrategy с методом validate(input). Форма регистрации использует набор стратегий для проверки каждого поля. Стратегии валидации можно комбинировать цепочкой (Chain of Responsibility) или применять все сразу в цикле. Это заменяет длинные if-else проверки на коллекцию полиморфных валидаторов.

swift
// Стратегия сортировки
protocol SortingStrategy {
    func sort<T>(_ items: [T]) -> [T] where T: Comparable
}

struct QuickSortStrategy: SortingStrategy {
    func sort<T>(_ items: [T]) -> [T] { /* quicksort */ items }
}

struct MergeSortStrategy: SortingStrategy {
    func sort<T>(_ items: [T]) -> [T] { /* mergesort */ items }
}

class SortedDataSource<T> {
    private var strategy: SortingStrategy
    func display(_ items: [T]) { let sorted = strategy.sort(items) }
}

Аутентификация — в мобильных приложениях стратегии аутентификации переключаются в зависимости от провайдера. AuthStrategy с методами login(), logout(), getToken() реализуется для EmailPasswordAuth, GoogleAuth, AppleAuth, BiometricAuth. Контекст AuthManager принимает стратегию через DI или фабрику. Это позволяет добавлять новых провайдеров аутентификации без изменения экрана логина. Паттерн Strategy — основа для многих OAuth-библиотек и Firebase Authentication.

Часто задаваемые вопросы

Когда использовать Strategy вместо if-else?

Strategy оправдан, когда у вас 3+ алгоритма, которые могут меняться или расширяться. Если алгоритмов 2 и они стабильны — простой if-else менее затратен. Используйте Strategy, когда алгоритмы используются в разных частях приложения, когда нужно подменять алгоритмы в рантайме или когда каждый алгоритм требует собственных зависимостей и тестов.

Strategy — это то же самое, что State?

Нет, это разные паттерны с похожей структурой. Strategy — клиент явно выбирает алгоритм, и стратегии независимы. State — объект сам меняет своё поведение при изменении внутреннего состояния, и состояния могут переходить друг в друга. В State контекст управляет сменой состояния, в Strategy — клиентский код.

Можно ли использовать Strategy без классов — через closures?

Да, в Swift и Kotlin стратегию можно передать как замыкание или лямбду. Swift: typealias PaymentHandler = (Decimal) async throws -> PaymentResult. Kotlin: typealias PaymentFun = suspend (BigDecimal) -> PaymentResult. Это упрощает код для простых случаев, но теряется именование и документация. Для 1-2 алгоритмов — closure достаточно, для 4+ — лучше отдельные классы.

Как тестировать паттерн Strategy?

Каждая стратегия тестируется отдельным unit-тестом с mock-зависимостями. Context тестируется с mock-стратегией — проверяется, что контекст вызывает метод стратегии и передаёт правильные параметры. В Swift используйте XCTest + протоколы для mock, в Kotlin — MockK или Mockito. Основное преимущество: каждая стратегия тестируется изолированно без сложной настройки.

Strategy — это паттерн GoF?

Да, Strategy (Стратегия) — один из 23 паттернов, описанных в книге «Design Patterns: Elements of Reusable Object-Oriented Software» (Gamma, Helm, Johnson, Vlissides, 1994). Относится к группе поведенческих паттернов (Behavioral Patterns). Псевдонимы: Policy (Политика). Исходный код примера на Smalltalk-80 доступен в оригинальном издании GoF.

Итоги

  • Strategy — поведенческий паттерн для взаимозаменяемых алгоритмов с общим интерфейсом
  • Инкапсуляция — каждый алгоритм изолирован в отдельном классе-стратегии
  • iOS Swift — реализация через Protocol, структуры и замыкания
  • Android Kotlin — реализация через Interface, лямбды и DI с Hilt
  • Open/Closed Principle — новые стратегии добавляются без изменения контекста
  • Применение — платежи, валидация, сортировка, аутентификация, форматирование

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также