你是否也遇到过每种支付方式都让 if-else 不断增加的代码?
当排序选项、折扣计算和过滤逻辑开始纠缠在一个巨大的 switch 语句中时,代码会越来越不敢修改。
这时就该用上 Swift 的策略模式了。
今天我们从实战角度讲解如何通过协议和闭包,像更换零件一样替换算法。
先来看核心要点。
策略模式将“做什么”和“怎么做”分离,使算法可以从外部替换。
在 Swift 中,可以通过协议(较重量级)或闭包(较轻量)两种方式实现。
下面逐一介绍。
什么是策略模式?先用 3 行总结
先搭好骨架,再深入复杂理论。
- 将需要替换的算法统一到一个公共规范(协议)中
- 将实际行为分别实现为策略对象或闭包
- 使用方只需了解规范,不必知道具体内容
可以把它理解为给游戏角色更换武器。
角色只需要知道“攻击”,无论是剑、弓还是魔法,都由装备的武器自行处理。
不修改角色代码,只更换武器。
这个“替换”基本就是策略模式的全部。
使用协议实现(经典方式)
先来看最教科书式的协议实现方式。
以折扣计算为例:普通会员、VIP 会员和使用优惠券等情况都有不同的计算方式。
下面的代码定义了所有策略必须遵守的公共规范,以及一个具体策略。
// 所有折扣策略都必须遵守的公共规范
protocol DiscountStrategy {
func discount(for price: Int) -> Int
}
// VIP 策略: 20% 折扣
struct VIPDiscount: DiscountStrategy {
func discount(for price: Int) -> Int { price * 20 / 100 }
}
接下来看看持有并使用该策略的一方。
使用方对象不关心传入的是哪种策略。
struct Checkout {
var strategy: DiscountStrategy // 可以替换策略的位置
func finalPrice(_ price: Int) -> Int {
price - strategy.discount(for: price)
}
}
// 在支付时只替换策略
let cart = Checkout(strategy: VIPDiscount())
即使新增折扣政策,也不需要修改 Checkout。
只需新建一个遵循 DiscountStrategy 的结构体即可。
不修改现有代码就能扩展功能,这就是实战中最明显的优势。
使用闭包实现(Swift 风格)
不过,策略明明很简单,却每次都创建结构体,确实有些麻烦。
连简单逻辑都用协议封装,反而容易让代码变得冗长。
这时在 Swift 中,闭包会更轻量、更自然。
struct Checkout {
// 将策略本身作为函数接收
var discount: (Int) -> Int
func finalPrice(_ price: Int) -> Int {
price - discount(price)
}
}
// 在当前位置直接定义策略
let cart = Checkout(discount: { $0 * 20 / 100 })
无需声明额外类型,在传入时就可以直接写入逻辑。
对于排序条件、过滤条件和简单转换等轻量算法的替换,这种方式更加简洁。
实际上,标准库中的 sorted(by:) 正是这种基于闭包的策略模式。
排序的骨架保持不变,只通过闭包替换比较条件。
也就是说,我们早就在每天使用它了。
协议和闭包,什么时候该用哪个?
我把实战中的选择标准整理成了表格。
| 分类 | 协议方式 | 闭包方式 |
|---|---|---|
| 适用场景 | 策略复杂或持有状态 | 策略简短且简单 |
| 复用性 | 容易在多个地方复用 | 适合一次性使用 |
| 代码量 | 需要声明类型,代码较多 | 就地定义,代码较少 |
| 测试 | 易于作为独立类型验证 | 简单验证逻辑即可 |
| 可读性 | 通过名称表达意图 | 较短时清晰,较长时难读 |
我的判断标准是这样的。
如果策略拥有内部状态、会在多个页面复用,或需要通过名称清晰表达意图,就适合使用协议。
反之,如果只是临时替换一两行逻辑,闭包就是正确选择。
混合使用两者完全没有问题。我会将大型策略交给协议,将细节选项交给闭包。
常见问题(Q&A)
Q. 策略模式和继承(重写)有什么区别?
继承绑定在父类上,因此行为在编译时就被固定。
而策略模式可以在运行时自由替换策略,也不受类层次结构限制。
Q. 和使用 enum 的 switch 语句处理有什么区别?
使用 switch 时,每增加一个新分支,都必须打开并修改现有代码。
策略模式只需“添加”新策略,就能减少修改现有代码的情况。
归根结底,策略模式的核心只有一个:把变化的部分移到外部,让它更容易替换。
根据场景灵活使用今天学到的协议和闭包,你一定能摆脱令人厌烦的 if-else 地狱。先从一小段代码开始重构吧!

