Strategy — 什么是策略模式,iOS 和 Android 中的策略模式

作者: IT Sectr 发布日期: 2026-02-18 阅读时间: 9 分钟

Strategy(策略)— 一种行为设计模式,定义了一系列可互换的算法,并将每个算法放入单独的类(Strategy)中。该模式允许即时选择算法:客户端代码通过公共的 Strategy 接口工作,具体的实现在运行时被插入。在 iOS 中,该模式通过 Protocol + 策略类实现,在 Android 中 — 通过 Interface + 实现。Strategy 是 23 种 GoF 模式之一,广泛应用于支付处理、验证、排序和数据过滤。更多信息 — 在 GoF 原始描述中。

要点

  • Strategy — 用于可互换算法系列的 GoF 行为模式
  • 算法的封装 — 每个算法都隔离在自己的类中
  • Strategy 接口 — 所有具体策略实现的公共契约
  • 组合优于继承 — 上下文持有对策略的引用,而不是继承行为
  • 开闭原则 — 新策略可以在不修改现有代码的情况下添加

什么是 Strategy 模式:本质和结构

Strategy — 23 种 GoF(四人帮)模式之一,在《Design Patterns: Elements of Reusable Object-Oriented Software》(1994)一书中描述。该模式解决了运行时算法选择的问题。与其编写一个包含许多条件运算符(if-else, switch)的类,Strategy 建议将每个算法分离到带有公共接口的单独类中。上下文(使用策略的类)持有对 Strategy 接口的引用,并将执行委托给具体的策略。

模式的结构包括三个元素:Context(上下文)包含对 Strategy 的引用并调用其方法;Strategy(接口)为所有算法声明一个公共方法;ConcreteStrategy(具体策略)实现接口并包含具体的算法。客户端创建所需的策略,并通过构造函数、setter 或方法参数将其传递给上下文。上下文不知道哪个策略正在执行 — 它只与接口交互。

组件角色示例
Context包含对 Strategy 的引用PaymentProcessor, Sorter
Strategy算法的公共接口Protocol PaymentStrategy
ConcreteStrategy算法的具体实现CardPayment, PayPalPayment

开闭原则 — Strategy 的主要优势。系统对扩展开放(可以添加新策略)对修改关闭(无需更改上下文代码)。如果没有该模式,添加新算法需要修改现有类,这违反了 OCP 并增加了回归错误的风险。Strategy 还减小了类的大小:不需要一个 200 行的 switch-case 类,而是得到 6 个各 20 行的类。

iOS 中的 Strategy:在 Swift 中使用 Protocol 实现

Swift 中的 Strategy 通过 Protocol(策略接口)和类或结构体策略来实现。Swift 协议支持关联类型和泛型约束,这为策略设计提供了灵活性。上下文通常是 ViewModel 类或服务,在 init 或通过属性接收策略。该模式广泛用于 iOS 项目中的事件处理、动画、数据格式化和 UI 策略。

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

// 2. 具体策略
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)

SwiftUI 中的 Strategy — 该模式自然地与 MVVM 集成。ViewModel 包含一个策略属性,并在用户操作时调用其方法。SwiftUI View 通过 @Published 或 @State 接收数据 — 策略对 View 隐藏了实现细节。例如,文本验证策略(emailValidator, phoneValidator)根据输入字段类型进行切换。Strategy 与 SwiftUI 的结合提供了灵活性,而无需继承 UIKit。

Android 中的 Strategy:在 Kotlin 中使用 Interface 实现

Kotlin 中的 Strategy 在语言层面使用 Interface 和函数式接口(SAM)来简化。Kotlin 支持 lambda,允许将算法作为函数传递,而无需声明单独的策略类。在 Android 中,该模式应用于 ViewModel 和 Use Cases,用于隔离数据加载、缓存和错误处理算法。使用 Clean Architecture 的 Android 项目使用 Strategy 根据标志(mock, real, cache)注入不同的仓库实现。

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

// 2. 具体策略
class CardPaymentStrategy(
    private val cardNumber: String,
    private val cvv: String
) : PaymentStrategy {
    override suspend fun pay(amount: BigDecimal): PaymentResult {
        // 通过 Retrofit 调用银行 API
        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 通过 @Binds 或 @Provides 提供 PaymentStrategy 的具体实现。这允许在不修改上下文代码的情况下更改策略 — 只需为不同的构建(debug/release)更改 DI 模块。例如,调试时注入 MockPaymentStrategy,生产时注入真实的银行策略。Strategy + DI 的组合提供了最大的灵活性。

Strategy 与 State、Command 和 Template Method 的比较

Strategy vs State — 结构上两种模式相同:都使用带有接口和具体类的组合。区别在于目的:Strategy 选择独立的算法,State 根据对象的状态管理其行为。在 State 中,上下文在状态变化时自己更改策略;在 Strategy 中,上下文不控制切换 — 客户端显式指定算法。策略彼此不知道,状态可以相互转换。

Strategy vs Command — Command 将单个操作封装为对象,Strategy 封装一组可互换的算法。Command — «做什么»(一次 execute 调用),Strategy — «如何做»(多步算法)。Command 用于队列、延迟执行、撤销/重做。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 实现带有 validate(input) 方法的公共接口 ValidationStrategy。注册表单使用一组策略来检查每个字段。验证策略可以链接(责任链模式)或在循环中同时应用。这用多态验证器的集合取代了长长的 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) }
}

身份验证 — 在移动应用中,身份验证策略根据提供商进行切换。具有 login()、logout()、getToken() 方法的 AuthStrategy 为 EmailPasswordAuth、GoogleAuth、AppleAuth、BiometricAuth 实现。AuthManager 上下文通过 DI 或工厂接收策略。这允许在不修改登录屏幕的情况下添加新的身份验证提供商。Strategy 模式是许多 OAuth 库和 Firebase Authentication 的基础。

常见问题

什么时候使用 Strategy 而不是 if-else?

当您有 3 个以上可能变化或扩展的算法时,Strategy 是合理的。如果只有 2 个稳定的算法 — 简单的 if-else 成本更低。当算法在应用程序的不同部分使用、需要在运行时切换算法或每个算法需要自己的依赖项和测试时,请使用 Strategy。

Strategy 和 State 是同一个东西吗?

不,它们是结构相似但不同的模式。Strategy — 客户端显式选择算法,策略彼此独立。State — 对象在内部状态改变时自己改变其行为,状态可以相互转换。在 State 中上下文管理状态变化,在 Strategy 中 — 客户端代码。

可以在没有类的情况下通过闭包使用 Strategy 吗?

可以,在 Swift 和 Kotlin 中,策略可以作为闭包或 lambda 传递。Swift: typealias PaymentHandler = (Decimal) async throws -> PaymentResult。Kotlin: typealias PaymentFun = suspend (BigDecimal) -> PaymentResult。这简化了简单情况的代码,但会丢失命名和文档。对于 1-2 个算法 — 闭包就够了,对于 4 个以上 — 单独的类更好。

如何测试 Strategy 模式?

每个策略都使用模拟依赖项进行独立的单元测试。上下文使用模拟策略进行测试 — 验证上下文是否调用策略方法并传递正确的参数。在 Swift 中使用 XCTest + 协议进行模拟,在 Kotlin 中使用 MockK 或 Mockito。主要优势:每个策略都可以在没有复杂配置的情况下隔离测试。

Strategy 是 GoF 模式吗?

是的,Strategy(策略) — 在《Design Patterns: Elements of Reusable Object-Oriented Software》(Gamma, Helm, Johnson, Vlissides, 1994)一书中描述的 23 种模式之一。属于行为模式组(Behavioral Patterns)。别名:Policy(政策)。原始的 Smalltalk-80 示例代码可在 GoF 原版中找到。

总结

  • Strategy — 用于具有公共接口的可互换算法的行为模式
  • 封装 — 每个算法都隔离在单独的策略类中
  • iOS Swift — 通过 Protocol、结构体和闭包实现
  • Android Kotlin — 通过 Interface、lambda 和 Hilt DI 实现
  • 开闭原则 — 新策略可以在不修改上下文的情况下添加
  • 应用 — 支付、验证、排序、身份验证、格式化

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读