策略模式(Strategy Pattern)
策略模式(Strategy Pattern)
一、模式定义
策略模式将一组算法封装成独立的策略对象,并使它们可以互换。调用者持有策略接口的引用,在运行时可以替换为不同的策略实现,而不需要修改调用者的代码。
一句话理解:把"做什么"和"怎么做"分开。 调用者只管"我要排序",不关心用的是快排还是归并排——那是策略对象的事。
二、解决什么问题
- 消除大量 if-else/switch:当同一个操作有多种实现方式时,如果用条件分支写,每加一种方式就要改调用方代码
- 算法切换的灵活性:运行时需要根据条件动态切换算法,而不是编译期写死
- 算法的独立演化:每种算法可以独立修改、测试,互不影响
三、核心角色
| 角色 | 职责 |
|---|---|
| Strategy(策略接口) | 声明算法方法,所有具体策略实现此接口 |
| ConcreteStrategy | 实现具体的算法逻辑 |
| Context(上下文) | 持有策略引用,将算法调用委托给策略对象 |
四、结构图
┌────────────────────┐ ┌──────────────────────┐
│ Context │ │ Strategy │
├────────────────────┤ ├──────────────────────┤
│ - strategy Strategy│─────>│ + Execute(data) Data │
│ + SetStrategy(s) │ └──────────┬───────────┘
│ + DoWork(data) │ │ implements
└────────────────────┘ ┌──────────┴──┬──────────┐
│ │ │
┌───────┴──┐ ┌───────┴──┐ ┌────┴──────┐
│StrategyA │ │StrategyB │ │StrategyC │
│+Execute()│ │+Execute() │ │+Execute() │
└──────────┘ └──────────┘ └───────────┘
五、Go 实现要点
Go 的策略模式比 Java 更简洁,因为接口是隐式实现的:
- 策略接口只需一个方法:Go 倡导小接口,策略接口通常只有一个方法(如
Execute、Calculate、Send) - 函数也是一种策略:当策略不需要维护状态时,直接用
func类型作为策略,无需定义结构体 - 策略注入方式:可以通过构造函数注入,也可以通过 Setter 方法在运行时动态切换
六、完整练习代码
以电商运费计算为场景:不同物流公司有不同的运费计算策略。
package main
import "fmt"
// ============================================================
// 策略接口
// ============================================================
// ShippingStrategy 运费计算策略接口
// 参数:重量(kg)、距离(km) 返回值:运费(元)
type ShippingStrategy interface {
CalculateCost(weight float64, distance float64) float64
Name() string
}
// ============================================================
// 具体策略实现
// ============================================================
// 1. 顺丰:起步价 + 重量计费,速度快但贵
type SFExpress struct{}
func (s *SFExpress) CalculateCost(weight float64, distance float64) float64 {
base := 18.0 // 起步价
perKg := 5.0 // 每公斤加价
// 顺丰不区分距离远近,统一计价
return base + weight*perKg
}
func (s *SFExpress) Name() string {
return "顺丰速运"
}
// 2. 中通:按重量和距离分段计费
type ZTOExpress struct{}
func (z *ZTOExpress) CalculateCost(weight float64, distance float64) float64 {
base := 8.0
perKgPerKm := 0.05 // 每公斤每公里费用
cost := base + weight*distance*perKgPerKm
// 超过500km加收远程费
if distance > 500 {
cost += 5.0
}
return cost
}
func (z *ZTOExpress) Name() string {
return "中通快递"
}
// 3. 京东物流:大件折扣策略
type JDLogistics struct{}
func (j *JDLogistics) CalculateCost(weight float64, distance float64) float64 {
perKg := 3.0
// 距离分三档计费
var distanceRate float64
switch {
case distance <= 100:
distanceRate = 1.0
case distance <= 500:
distanceRate = 1.2
default:
distanceRate = 1.5
}
cost := weight * perKg * distanceRate
// 大件(>20kg)打八折
if weight > 20 {
cost *= 0.8
}
return cost
}
func (j *JDLogistics) Name() string {
return "京东物流"
}
// 4. 函数式策略:免邮策略
func FreeShipping(weight float64, distance float64) float64 {
return 0
}
// 函数适配器:将普通函数包装为策略接口
type FuncStrategy struct {
name string
fn func(weight, distance float64) float64
}
func (fs *FuncStrategy) CalculateCost(weight float64, distance float64) float64 {
return fs.fn(weight, distance)
}
func (fs *FuncStrategy) Name() string {
return fs.name
}
// ============================================================
// 上下文:订单
// ============================================================
type Order struct {
OrderID string
Weight float64 // kg
Distance float64 // km
strategy ShippingStrategy
}
func NewOrder(orderID string, weight, distance float64) *Order {
return &Order{
OrderID: orderID,
Weight: weight,
Distance: distance,
}
}
// SetStrategy 切换运费策略
func (o *Order) SetStrategy(s ShippingStrategy) {
o.strategy = s
}
// ShippingCost 计算运费
func (o *Order) ShippingCost() float64 {
if o.strategy == nil {
panic("运费策略未设置")
}
cost := o.strategy.CalculateCost(o.Weight, o.Distance)
fmt.Printf(" 订单 %s | %s | %.1fkg × %.0fkm = %.2f元\n",
o.OrderID, o.strategy.Name(), o.Weight, o.Distance, cost)
return cost
}
// ============================================================
// 辅助:对比所有策略
// ============================================================
func CompareStrategies(order *Order, strategies []ShippingStrategy) {
fmt.Printf("\n--- 订单 %s 运费对比(%.1fkg, %.0fkm)---\n",
order.OrderID, order.Weight, order.Distance)
for _, s := range strategies {
order.SetStrategy(s)
order.ShippingCost()
}
}
// ============================================================
// 主函数
// ============================================================
func main() {
// 所有可用策略
strategies := []ShippingStrategy{
&SFExpress{},
&ZTOExpress{},
&JDLogistics{},
&FuncStrategy{name: "免邮活动", fn: FreeShipping},
}
// 场景1:轻小件短途
order1 := NewOrder("ORD-001", 2.0, 80)
CompareStrategies(order1, strategies)
// 场景2:重件长途
order2 := NewOrder("ORD-002", 25.0, 600)
CompareStrategies(order2, strategies)
// 场景3:运行时动态选择最优策略
fmt.Println("\n--- 动态选择最优运费 ---")
order3 := NewOrder("ORD-003", 10.0, 300)
var bestStrategy ShippingStrategy
var minCost float64 = 1e9
for _, s := range strategies {
order3.SetStrategy(s)
cost := order3.ShippingCost()
if cost < minCost {
minCost = cost
bestStrategy = s
}
}
fmt.Printf("\n>>> 最优选择: %s, 运费: %.2f元\n", bestStrategy.Name(), minCost)
order3.SetStrategy(bestStrategy)
}
七、运行结果预期
--- 订单 ORD-001 运费对比(2.0kg, 80km)---
订单 ORD-001 | 顺丰速运 | 2.0kg × 80km = 28.00元
订单 ORD-001 | 中通快递 | 2.0kg × 80km = 16.00元
订单 ORD-001 | 京东物流 | 2.0kg × 80km = 6.00元
订单 ORD-001 | 免邮活动 | 2.0kg × 80km = 0.00元
--- 订单 ORD-002 运费对比(25.0kg, 600km)---
订单 ORD-002 | 顺丰速运 | 25.0kg × 600km = 143.00元
订单 ORD-002 | 中通快递 | 25.0kg × 600km = 763.00元
订单 ORD-002 | 京东物流 | 25.0kg × 600km = 90.00元
订单 ORD-002 | 免邮活动 | 25.0kg × 600km = 0.00元
--- 动态选择最优运费 ---
订单 ORD-003 | 顺丰速运 | 10.0kg × 300km = 68.00元
订单 ORD-003 | 中通快递 | 10.0kg × 300km = 158.00元
订单 ORD-003 | 京东物流 | 10.0kg × 300km = 36.00元
订单 ORD-003 | 免邮活动 | 10.0kg × 300km = 0.00元
>>> 最优选择: 免邮活动, 运费: 0.00元
八、策略模式 vs 状态模式辨析
这两个模式结构几乎一模一样(都是接口 + 多个实现 + Context 持有引用),但意图完全不同:
| 维度 | 策略模式 | 状态模式 |
|---|---|---|
| 核心目的 | 算法选择 | 状态驱动的行为变化 |
| 谁决定切换 | 外部调用者主动选择 | 对象内部根据状态自动切换 |
| 切换频率 | 通常设置一次或偶尔切换 | 频繁切换,每次操作都可能变 |
| 策略间是否知道彼此 | 互不感知 | 状态类知道下一个状态是什么 |
| 类比 | 遥控器选频道 | 红绿灯自动切换 |
九、函数式策略的取舍
Go 中 func 是一等公民,当策略不需要维护状态时,用函数类型更简洁:
// 函数式策略(适合简单场景)
type ShippingFunc func(weight, distance float64) float64
// 结构体策略(需要状态或多个方法时)
type ShippingStrategy interface {
CalculateCost(weight, distance float64) float64
Name() string
}
选择原则:策略只需一个方法且无状态 → 用函数;策略需要多个方法或携带配置 → 用接口+结构体。
十、实际项目身影
sort.Slice(data, less func):less 函数就是策略,传不同的比较函数得到不同的排序结果http.Handler:每个 handler 是一个策略,路由器根据 URL 选择不同的处理策略io.Writer:写入策略——可以是文件、网络、内存缓冲区,调用者不关心底层实现- 支付系统:微信/支付宝/银行卡支付就是不同的支付策略,订单只管调用
Pay()接口
十一、小结
策略模式的本质是将变化点(算法/规则)抽离为独立对象,通过接口注入实现解耦。Go 的隐式接口让策略模式的实现比传统 OOP 语言更轻量——不需要继承层级,只需实现一个方法就够了。当你发现代码中出现了大量针对同一操作的 if-else 分支时,就是策略模式该上场的信号。

浙公网安备 33010602011771号