Strategy(策略)— 一种行为设计模式,定义了一系列可互换的算法,并将每个算法放入单独的类(Strategy)中。该模式允许即时选择算法:客户端代码通过公共的 Strategy 接口工作,具体的实现在运行时被插入。在 iOS 中,该模式通过 Protocol + 策略类实现,在 Android 中 — 通过 Interface + 实现。Strategy 是 23 种 GoF 模式之一,广泛应用于支付处理、验证、排序和数据过滤。更多信息 — 在 GoF 原始描述中。
要点
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 行的类。
Swift 中的 Strategy 通过 Protocol(策略接口)和类或结构体策略来实现。Swift 协议支持关联类型和泛型约束,这为策略设计提供了灵活性。上下文通常是 ViewModel 类或服务,在 init 或通过属性接收策略。该模式广泛用于 iOS 项目中的事件处理、动画、数据格式化和 UI 策略。
// 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。
Kotlin 中的 Strategy 在语言层面使用 Interface 和函数式接口(SAM)来简化。Kotlin 支持 lambda,允许将算法作为函数传递,而无需声明单独的策略类。在 Android 中,该模式应用于 ViewModel 和 Use Cases,用于隔离数据加载、缓存和错误处理算法。使用 Clean Architecture 的 Android 项目使用 Strategy 根据标志(mock, real, cache)注入不同的仓库实现。
// 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 vs State — 结构上两种模式相同:都使用带有接口和具体类的组合。区别在于目的:Strategy 选择独立的算法,State 根据对象的状态管理其行为。在 State 中,上下文在状态变化时自己更改策略;在 Strategy 中,上下文不控制切换 — 客户端显式指定算法。策略彼此不知道,状态可以相互转换。
Strategy vs Command — Command 将单个操作封装为对象,Strategy 封装一组可互换的算法。Command — «做什么»(一次 execute 调用),Strategy — «如何做»(多步算法)。Command 用于队列、延迟执行、撤销/重做。Strategy — 用于在运行时选择任务执行方式。命令可以用策略参数化,结合两种模式。
| 特性 | Strategy | State | Command | Template Method |
|---|---|---|---|---|
| 目的 | 可互换的算法 | 依赖状态的行为 | 封装请求 | 算法骨架 |
| 切换 | 由客户端显式 | 由上下文自动 | 由客户端或队列 | 通过继承 |
| 层级 | 对象(组合) | 对象(组合) | 对象 | 类(继承) |
Strategy vs Template Method — 两种模式都定义了算法,但方式不同。Template Method 使用继承:基类定义算法的骨架(模板方法),子类覆写单独的步骤。Strategy 使用组合:算法完全移到单独的类中。Template Method 对于具有固定算法结构的情况更简单,Strategy — 当算法完全不同且可以动态变化时。
支付处理 — Strategy 的经典示例。网上商店的购物车包含产品列表,支付方式由用户选择。每种方式(卡、PayPal、Apple Pay、Google Pay、加密货币)— 一个独立的策略,具有共同的签名 pay(amount)。PaymentProcessor 上下文不知道支付具体如何执行 — 它调用公共方法。添加新的支付方式不需要修改购物车代码。
数据验证 — Strategy 应用于同一字段的不同验证规则。EmailValidatorStrategy、PhoneValidatorStrategy、AgeValidatorStrategy 实现带有 validate(input) 方法的公共接口 ValidationStrategy。注册表单使用一组策略来检查每个字段。验证策略可以链接(责任链模式)或在循环中同时应用。这用多态验证器的集合取代了长长的 if-else 检查。
// 排序策略
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 的基础。
常见问题
当您有 3 个以上可能变化或扩展的算法时,Strategy 是合理的。如果只有 2 个稳定的算法 — 简单的 if-else 成本更低。当算法在应用程序的不同部分使用、需要在运行时切换算法或每个算法需要自己的依赖项和测试时,请使用 Strategy。
不,它们是结构相似但不同的模式。Strategy — 客户端显式选择算法,策略彼此独立。State — 对象在内部状态改变时自己改变其行为,状态可以相互转换。在 State 中上下文管理状态变化,在 Strategy 中 — 客户端代码。
可以,在 Swift 和 Kotlin 中,策略可以作为闭包或 lambda 传递。Swift: typealias PaymentHandler = (Decimal) async throws -> PaymentResult。Kotlin: typealias PaymentFun = suspend (BigDecimal) -> PaymentResult。这简化了简单情况的代码,但会丢失命名和文档。对于 1-2 个算法 — 闭包就够了,对于 4 个以上 — 单独的类更好。
每个策略都使用模拟依赖项进行独立的单元测试。上下文使用模拟策略进行测试 — 验证上下文是否调用策略方法并传递正确的参数。在 Swift 中使用 XCTest + 协议进行模拟,在 Kotlin 中使用 MockK 或 Mockito。主要优势:每个策略都可以在没有复杂配置的情况下隔离测试。
是的,Strategy(策略) — 在《Design Patterns: Elements of Reusable Object-Oriented Software》(Gamma, Helm, Johnson, Vlissides, 1994)一书中描述的 23 种模式之一。属于行为模式组(Behavioral Patterns)。别名:Policy(政策)。原始的 Smalltalk-80 示例代码可在 GoF 原版中找到。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。