当你的代码里出现一长串 if-else 或 switch-case 来处理不同算法分支时,你是否想过有一种更优雅的方式来组织它们?在十三Tech 的代码评审中,策略模式(Strategy Pattern)是我们推荐用来消除这类"条件爆炸"的经典方案。本文将带你从原理到实践,掌握这一行为型设计模式的精髓。
什么是策略模式
策略模式是一种行为型设计模式,它定义一系列算法,将每种算法封装到独立的类中,并使它们可以相互替换。这样,算法的变化不会影响到使用算法的客户端代码。
核心角色
- 策略接口(Strategy):声明所有具体策略的通用操作方法
- 具体策略(ConcreteStrategy):实现策略接口,封装具体的算法逻辑
- 上下文(Context):维护一个策略对象的引用,负责与策略对象交互,将具体执行委托给策略
- 客户端(Client):创建具体策略并注入到上下文中,也可在运行时动态切换策略
代码实现
下面用 Go 实现一个经典的策略模式示例:根据不同的策略执行不同的业务逻辑。
package main
import "fmt"
// IStrategy 策略接口,声明所有策略必须实现的方法
type IStrategy interface {
Execute()
}
// strategyA 具体策略 A
type strategyA struct{}
func (a strategyA) Execute() {
fmt.Println("执行策略 A")
}
// strategyB 具体策略 B
type strategyB struct{}
func (b strategyB) Execute() {
fmt.Println("执行策略 B")
}
// NewStrategyA 创建策略 A 的实例
func NewStrategyA() IStrategy {
return &strategyA{}
}
// NewStrategyB 创建策略 B 的实例
func NewStrategyB() IStrategy {
return &strategyB{}
}
上下文实现
// Context 上下文,负责调用具体的策略
type Context struct {
strategy IStrategy
}
// SetStrategy 动态设置策略
func (c *Context) SetStrategy(strategy IStrategy) {
c.strategy = strategy
}
// Execute 执行当前策略
func (c *Context) Execute() {
c.strategy.Execute()
}
// NewContext 创建上下文实例
func NewContext() *Context {
return &Context{}
}
客户端调用
func main() {
// 创建上下文
context := NewContext()
// 设置策略 B 并执行
context.SetStrategy(NewStrategyB())
context.Execute() // 输出:执行策略 B
// 运行时动态切换为策略 A
context.SetStrategy(NewStrategyA())
context.Execute() // 输出:执行策略 A
}
适用场景
策略模式在以下场景中能够大放异彩:
- 支付路由系统:根据渠道可用性、费率、优先级动态选择支付通道
- 日志处理:不同环境(开发、测试、生产)使用不同的日志输出策略
- 数据压缩:根据数据特征选择 gzip、snappy、zstd 等压缩算法
- 排序算法:根据数据规模动态选择快速排序、归并排序或插入排序
- 营销规则引擎:不同类型的促销活动(满减、折扣、返现)对应不同计算策略
优缺点分析
优点
- 消除条件语句:将复杂的
if-else/switch替换为策略对象的组合 - 符合开闭原则:新增算法只需添加新的策略类,无需修改上下文代码
- 算法可复用:各个策略独立封装,可在不同上下文中复用
- 运行时动态切换:客户端可以在程序运行期间灵活更换策略
缺点
- 如果算法极少变化,引入策略模式会增加不必要的复杂度
- 策略数量过多时,会产生较多的类文件,增加维护成本
总结
策略模式的核心价值在于将"变"与"不变"分离:算法的调用方式是不变的(由上下文统一封装),而具体的算法实现是可变的(由各个策略独立维护)。在十三Tech 的实际项目中,我们在处理多支付方式路由、动态配置切换等场景时大量应用此模式,有效避免了条件分支的无限膨胀,让代码结构更加清晰、易于扩展。
如果你正在为一长串 if-else 感到头疼,策略模式可能就是你要找的解药。