简要介绍

过去出于学习目的写项目时,由于是“单兵作战”,所以完全不用考虑后期维护以及扩展成本,除了为了实践设计模式而刻意引入的架构外,大部分代码都是以快速实现为导向,怎么方便怎么写。

然鹅进入实际生产环境后我才发现,这业务迭代是真勾巴快,扩展性真的非常非常重要。

比如设想下面这个典型场景:我们刚刚完成了场景 A 和场景 B 的逻辑,结果没过多久 PM(产品经理)又提过来一个需求增加一个新场景 C。场景 A、B、C 是同级别的,如果使用普通的硬编码分支,为了兼容新场景,我们必须侵入式地修改主函数,增加一个 else if 语句或者 case 语句。这样随着场景不断增多,代码会变得很脆弱,每次新增一个场景都要修改这个巨大的 if-else/switch-case,这显然违背了开闭原则(对扩展开放,对修改关闭)。

简单来说,策略模式是一种行为型设计模式。它的核心思想是:定义一系列算法(或业务逻辑),把它们一个个单独封装起来,并且使它们可以互相替换。换句人话讲,就是把本来堆积在一个巨大 if-else 或 switch-case 里的各个业务分支,抽离成一个个独立的结构体。主流程不再关心具体的执行细节,只负责“根据当前上下文挑一个匹配的策略去执行”。

这样一来,主干逻辑就和具体的业务分支彻底解耦了。下次 PM 再提新场景,我们只需要默默建一个新文件实现接口即可,原有主干代码一行都不用动,这才是真正意义上的开闭原则。

Gemini_Generated

接下来我以一个电商中非常常见的场景“根据用户会员等级进行订单折扣计算”为例,通过对比策略模式重构前和重构后的代码,优不优雅一眼便知。

毋庸置疑,这种写法非常直观,所有逻辑都堆死在一个函数里,每种会员类型走什么逻辑一眼就能看出来。

虽说可以将每个具体逻辑作为一个函数解耦出去方便复用,但是每次增加会员类型时都要回来修改这个选择分支逻辑主函数,说不定一个手抖还把其他逻辑改崩了 😂。

代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import "fmt"

// Order 订单结构体
type Order struct {
Price float64
UserType string // Normal, VIP, Coupon
}

// CalculatePrice 充满 switch-case
func CalculatePrice(order Order) float64 {
switch order.UserType {
case "Normal":
return order.Price // 原价
case "VIP":
return order.Price * 0.8 // 8折
case "Coupon":
return order.Price - 10 // 满减10元
default:
return order.Price
}
}

func main() {
order := Order{Price: 100, UserType: "VIP"}
fmt.Printf("最终价格: %.2f\n", CalculatePrice(order))
}

重构后虽然复杂性稍微有所上涨,但是可扩展性大大提高,核心函数 CalculatePriceOptimized 只有区区几行,即使未来 PM 要求增加“双十一大促”、“生日特价”等几十种新场景,核心函数也完全不需要改 😏。

代码示例
1
2
3
4
// DiscountStrategy 定义折扣算法接口
type DiscountStrategy interface {
GetFinalPrice(price float64) float64
}
1
2
3
4
5
6
7
8
9
10
11
// NormalStrategy 普通用户策略
type NormalStrategy struct{}
func (s *NormalStrategy) GetFinalPrice(price float64) float64 { return price }

// VIPStrategy VIP用户策略
type VIPStrategy struct{}
func (s *VIPStrategy) GetFinalPrice(price float64) float64 { return price * 0.8 }

// CouponStrategy 优惠券策略
type CouponStrategy struct{}
func (s *CouponStrategy) GetFinalPrice(price float64) float64 { return price - 10 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

// 策略工厂
var discountStrategies = map[string]DiscountStrategy{
"Normal": &NormalStrategy{},
"VIP": &VIPStrategy{},
"Coupon": &CouponStrategy{},
}

func CalculatePriceOptimized(price float64, userType string) float64 {
strategy, ok := discountStrategies[userType]
if !ok {
return price // 默认策略
}
return strategy.GetFinalPrice(price)
}

func main() {
finalPrice := CalculatePriceOptimized(100, "VIP")
fmt.Printf("重构后的最终价格: %.2f\n", finalPrice)
}

动手实现

下面我们对上述策略模式的例子进行扩展,编写一个更加完整的例子,包括从请求校验到数据落库的完整流程。可以跟着敲一下,印象更加深刻。

首先介绍一下完整的代码结构和每个文件的作用。

image-20260405174759452

首先我们来定义通用的请求和响应结构体。编写 strategy/context.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package strategy

// Request 表示一次策略执行所需的统一上下文。
type Request struct {
BizType string
UserTag string
EntityID int64
Operator string
Meta map[string]any
}

// Result 表示策略执行的结果。
type Result struct {
StrategyName string
Success bool
Message string
Payload map[string]any
}

然后我们来定义接口,这是该策略模式的核心抽象,接口定义了每一个具体策略需要实现的方法,后续所有策略都要实现此接口,编写 strategy/interface.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package strategy

import "context"

// Strategy 定义了策略模式的通用能力,包括:策略判断、校验、初始化、构建实体、持久化。
type Strategy interface {
// 返回策略名称
Name() string
// 返回策略优先级
Priority() int
// 判断该策略是否支持当前请求中的业务场景
Supports(ctx context.Context, req *Request) bool
// 校验请求参数
Validate(ctx context.Context, req *Request) error
// 对请求进行初始化
Init(ctx context.Context, req *Request) error
// 策略满足的情况下,判断是否可以执行,比如幂等校验等
CanExecute(ctx context.Context, req *Request) (bool, error)
// 构建持久化实体
BuildEntity(ctx context.Context, req *Request) (*Result, error)
// 持久化
Persist(ctx context.Context, req *Request, result *Result) error
}

接下来我们添加优先级的默认实现,后续所有策略都可以继承该结构体,实现默认的优先级机制。继续编写 strategy/interface.go,在底部添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// BaseStrategy 提供优先级的默认实现。
type BaseStrategy struct {
priority int
}

// NewBaseStrategy 创建基础策略。
func NewBaseStrategy(priority int) BaseStrategy {
return BaseStrategy{priority: priority}
}

// Priority 返回策略的优先级。
func (s BaseStrategy) Priority() int {
return s.priority
}

接下来编写注册策略、以及执行第一个匹配到的策略的逻辑。后续所有的策略只需要在 init 中调用 Register 函数将自己注册到 registry 策略库中,然后 ExecuteFirst 函数就能根据请求参数选择第一个匹配到的策略并执行策略的具体逻辑。编写 strategy/registry.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package strategy

import (
"context"
"fmt"
"sort"
)

var registry []Strategy

// Register 注册策略,按优先级从高到低排序。
func Register(s Strategy) {
registry = append(registry, s)
sort.SliceStable(registry, func(i, j int) bool {
return registry[i].Priority() > registry[j].Priority()
})
}

// ExecuteFirst 执行第一个匹配的策略。
func ExecuteFirst(ctx context.Context, req *Request) (*Result, error) {
for _, s := range registry {
if !s.Supports(ctx, req) {
continue
}

if err := s.Validate(ctx, req); err != nil {
return nil, fmt.Errorf("strategy %s validate failed: %w", s.Name(), err)
}

if err := s.Init(ctx, req); err != nil {
return nil, fmt.Errorf("strategy %s init failed: %w", s.Name(), err)
}

ok, err := s.CanExecute(ctx, req)
if err != nil {
return nil, fmt.Errorf("strategy %s pre-check failed: %w", s.Name(), err)
}
if !ok {
return &Result{
StrategyName: s.Name(),
Success: false,
Message: "策略已命中,但当前条件不允许执行",
}, nil
}

result, err := s.BuildEntity(ctx, req)
if err != nil {
return nil, fmt.Errorf("strategy %s build entity failed: %w", s.Name(), err)
}
if result == nil {
return nil, fmt.Errorf("strategy %s return nil result", s.Name())
}

if err := s.Persist(ctx, req, result); err != nil {
return nil, fmt.Errorf("strategy %s persist failed: %w", s.Name(), err)
}

result.StrategyName = s.Name()
result.Success = true
return result, nil
}

return nil, fmt.Errorf("no strategy matched for bizType=%s userTag=%s", req.BizType, req.UserTag)
}

通用框架编写完了,接下来需要编写具体的策略类实现具体的逻辑。每个策略类只需要定义一个结构体并实现 Strategy 接口定义的所有方法,编写 New 函数创建该结构体的对象,在 init 方法中调用刚刚写好的 strategy.Register 函数将 New 创建的对象注册到策略库中等待被执行即可。编写 default.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package strategies

import (
"context"
"fmt"
"strategy_demo/strategy"
)

func init() {
strategy.Register(NewDefaultOrderStrategy())
}

// DefaultOrderStrategy 默认策略。
type DefaultOrderStrategy struct {
strategy.BaseStrategy
}

// NewDefaultOrderStrategy 创建默认策略。
func NewDefaultOrderStrategy() *DefaultOrderStrategy {
return &DefaultOrderStrategy{
BaseStrategy: strategy.NewBaseStrategy(100),
}
}

// Name 返回策略名称。
func (s *DefaultOrderStrategy) Name() string {
return "default_order_strategy"
}

// Supports 判断该策略是否支持当前请求。
func (s *DefaultOrderStrategy) Supports(ctx context.Context, req *strategy.Request) bool {
return req != nil && req.BizType == "order"
}

// Validate 验证请求参数。
func (s *DefaultOrderStrategy) Validate(ctx context.Context, req *strategy.Request) error {
if req == nil {
return fmt.Errorf("req is nil")
}
if req.EntityID <= 0 {
return fmt.Errorf("entityID must be greater than 0")
}
if req.Operator == "" {
return fmt.Errorf("operator is empty")
}
return nil
}

// Init 初始化上下文。
func (s *DefaultOrderStrategy) Init(ctx context.Context, req *strategy.Request) error {
if req.Meta == nil {
req.Meta = map[string]any{}
}

req.Meta["channel"] = "standard"
return nil
}

// CanExecute 判断当前请求是否允许继续执行。
func (s *DefaultOrderStrategy) CanExecute(ctx context.Context, req *strategy.Request) (bool, error) {
return true, nil
}

// BuildEntity 构建结果。
func (s *DefaultOrderStrategy) BuildEntity(ctx context.Context, req *strategy.Request) (*strategy.Result, error) {
return &strategy.Result{
Message: "命中通用订单策略",
Payload: map[string]any{
"entity_id": req.EntityID,
"channel": req.Meta["channel"],
},
}, nil
}

// Persist 模拟持久化动作。
func (s *DefaultOrderStrategy) Persist(ctx context.Context, req *strategy.Request, result *strategy.Result) error {
if result.Payload == nil {
result.Payload = map[string]any{}
}
result.Payload["persisted"] = true
return nil
}

编写 vip.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package strategies

import (
"context"
"fmt"
"strategy_demo/strategy"
)

func init() {
strategy.Register(NewVIPOrderStrategy())
}

// VIPOrderStrategy 表示高优先级特例策略。
type VIPOrderStrategy struct {
strategy.BaseStrategy
}

// NewVIPOrderStrategy 创建 VIP 策略。
func NewVIPOrderStrategy() *VIPOrderStrategy {
return &VIPOrderStrategy{
BaseStrategy: strategy.NewBaseStrategy(200),
}
}

// Name 返回策略名称。
func (s *VIPOrderStrategy) Name() string {
return "vip_order_strategy"
}

// Supports 判断是否支持 VIP 请求。
func (s *VIPOrderStrategy) Supports(ctx context.Context, req *strategy.Request) bool {
return req != nil && req.BizType == "order" && req.UserTag == "vip"
}

// Validate 校验 VIP 请求。
func (s *VIPOrderStrategy) Validate(ctx context.Context, req *strategy.Request) error {
if req == nil {
return fmt.Errorf("req is nil")
}
if req.EntityID <= 0 {
return fmt.Errorf("entityID must be greater than 0")
}
if req.Operator == "" {
return fmt.Errorf("operator is empty")
}
return nil
}

// Init 初始化 VIP 上下文。
func (s *VIPOrderStrategy) Init(ctx context.Context, req *strategy.Request) error {
if req.Meta == nil {
req.Meta = map[string]any{}
}
// 这里模拟高优先级策略对执行上下文的增强。
req.Meta["channel"] = "vip"
req.Meta["discount"] = "20%"
return nil
}

// CanExecute 判断是否允许执行。
func (s *VIPOrderStrategy) CanExecute(ctx context.Context, req *strategy.Request) (bool, error) {
return true, nil
}

// BuildEntity 构建 VIP 执行结果。
func (s *VIPOrderStrategy) BuildEntity(ctx context.Context, req *strategy.Request) (*strategy.Result, error) {
return &strategy.Result{
Message: "命中 VIP 订单策略",
Payload: map[string]any{
"entity_id": req.EntityID,
"channel": req.Meta["channel"],
"discount": req.Meta["discount"],
},
}, nil
}

// Persist 模拟持久化动作。
func (s *VIPOrderStrategy) Persist(ctx context.Context, req *strategy.Request, result *strategy.Result) error {
if result.Payload == nil {
result.Payload = map[string]any{}
}
result.Payload["persisted"] = true
result.Payload["audit_log"] = "vip flow recorded"
return nil
}

编写 vip_v2.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package strategies

import (
"context"
"fmt"
"strategy_demo/strategy"
)

func init() {
strategy.Register(NewVIPOrderStrategyV2())
}

// VIPOrderStrategy 表示高优先级特例策略。
type VIPOrderStrategyV2 struct {
strategy.BaseStrategy
}

// NewVIPOrderStrategy 创建 VIP 策略。
func NewVIPOrderStrategyV2() *VIPOrderStrategyV2 {
return &VIPOrderStrategyV2{
BaseStrategy: strategy.NewBaseStrategy(201),
}
}

// Name 返回策略名称。
func (s *VIPOrderStrategyV2) Name() string {
return "vip_order_strategy_v2"
}

// Supports 判断是否支持 VIP 请求。
func (s *VIPOrderStrategyV2) Supports(ctx context.Context, req *strategy.Request) bool {
return req != nil && req.BizType == "order" && req.UserTag == "vip"
}

// Validate 校验 VIP 请求。
func (s *VIPOrderStrategyV2) Validate(ctx context.Context, req *strategy.Request) error {
if req == nil {
return fmt.Errorf("req is nil")
}
if req.EntityID <= 0 {
return fmt.Errorf("entityID must be greater than 0")
}
if req.Operator == "" {
return fmt.Errorf("operator is empty")
}
return nil
}

// Init 初始化 VIP 上下文。
func (s *VIPOrderStrategyV2) Init(ctx context.Context, req *strategy.Request) error {
if req.Meta == nil {
req.Meta = map[string]any{}
}
// 这里模拟高优先级策略对执行上下文的增强。
req.Meta["channel"] = "vip"
req.Meta["discount"] = "20%"
return nil
}

// CanExecute 判断是否允许执行。
func (s *VIPOrderStrategyV2) CanExecute(ctx context.Context, req *strategy.Request) (bool, error) {
return true, nil
}

// BuildEntity 构建 VIP 执行结果。
func (s *VIPOrderStrategyV2) BuildEntity(ctx context.Context, req *strategy.Request) (*strategy.Result, error) {
return &strategy.Result{
Message: "命中 VIP 订单策略",
Payload: map[string]any{
"entity_id": req.EntityID,
"channel": req.Meta["channel"],
"discount": req.Meta["discount"],
},
}, nil
}

// Persist 模拟持久化动作。
func (s *VIPOrderStrategyV2) Persist(ctx context.Context, req *strategy.Request, result *strategy.Result) error {
if result.Payload == nil {
result.Payload = map[string]any{}
}
result.Payload["persisted"] = true
result.Payload["audit_log"] = "vip flow recorded"
return nil
}

大功告成,接下来只需要编写入口函数,并模拟打过来的请求即可。有一个需要注意的点是需要导入 registries 策略包,这样策略类的 init 函数就会自动执行注册,非常方便,编写 main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
"context"
"fmt"
_ "strategy_demo/strategies"
"strategy_demo/strategy"
)

func main() {
ctx := context.Background()

requests := []*strategy.Request{
{
BizType: "order",
UserTag: "vip",
EntityID: 1001,
Operator: "alice",
},
{
BizType: "order",
UserTag: "normal",
EntityID: 1002,
Operator: "bob",
},
{
BizType: "refund",
UserTag: "normal",
EntityID: 1003,
Operator: "charlie",
},
}

for _, req := range requests {
result, err := strategy.ExecuteFirst(ctx, req)
if err != nil {
fmt.Printf("请求 %+v 执行失败: %v\n", *req, err)
continue
}
fmt.Printf("请求 bizType=%s, userTag=%s 命中策略=%s, message=%s, payload=%v\n",
req.BizType, req.UserTag, result.StrategyName, result.Message, result.Payload)
}
}

执行试一下

image-20260405182646923

非常完美!

分析

优点

一、入口层完全不关心业务类型

很明显业务入口只负责准备上下文,然后把选择权交给策略框架。类似上述编写的 main 函数,在正常 rpc 服务也是这样,主函数只负责解析请求,利用请求参数构造 strategy.Request 然后调用 strategy.ExecuteFirst 即可。入口层再也不用到处写 if bizType == xxx { if userTag == yyy { ... } else if userTag == zzz { ... } ... } else if ... 这种强耦合代码。

二、模版化、流程规范化

我们把每一种业务策略都定义为明确相同的流程:

  • Validate
  • Init
  • CanExecute
  • BuildEntity
  • Persist

这使得主流程非常稳定,每一部分的职责也更加清晰,后续的可维护性以及可扩展性都大大提高。

三、自动注册 + 优先级,让扩展变成新增文件而不用改老代码

每个具体策略实现都在 init() 里自动注册,注册方式统一是 strategy.Register(NewXXX),这使得新增一种业务类型或者将一种老类型升级为 v2 版本时,只要新增一个策略文件,主流程完全不用改。完美实践开闭原则。

四、用 Supports 做路由,用请求对象承载上下文

每个策略都标准化地实现 Supports,按业务类型或用户标识判断自己是否适配。比起分支语句,这样写也使得选择条件更加灵活,每个策略甚至能按需使用 context 请求上下文来判断自己是否符合,这对于灰度放量乃至环境测试都非常有用。

同时,这套代码把策略执行需要的参数集中塞进 strategy.Request 对象里,也使得流程之间更加连贯。

局限性

再看一下我们的标题,“使用策略模式重构复杂业务分支”,复杂这两个字非常重要。如果我们的业务分支只有那么几个并且非常稳定,后续也几乎不会发生迭代,那我觉得使用 if-else 反而是一种心智负担更小的开发方式。

总结

总结一下,设计模式从来都不是银弹,更不是为了炫技。我们在写代码时,最忌讳的就是“拿着锤子找钉子”——业务模型极度简单时还强行套用模式,这只会让原本直白的逻辑变得支离破碎,徒增心智负担和系统的维护成本。

说到底,代码不仅是写给机器跑的,更是写给几个月后的自己、以及未来的协作者看的。一套优秀的代码架构,往往不是一开始就设计得多么宏大完美,而是在开发效率、系统复杂度和后期可维护性这三者之间,随着业务发展不断寻找那个最优雅的平衡点。