惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

披萨盒的赛博日志

像 systemd 一样管理 MacOS 后台常驻任务 以ORM看封装的边界 Git Merge VS Git Rebase: 如何优雅地合并分支? 修改Linux内核模块以支持WG OpenLDAP折腾日记 非特权模式容器 ssh 登录问题 在 Linux 开发环境中使用网络代理 白嫖 Aseprite 像素绘图软件 MongoDB 增删改查 Python数据分析工具包-Numpy 解决 CLion 中文乱码问题 搭建 RLCraft 服务器 SpringBoot读取配置文件 Centos 配置 LNMP 环境 部署项目时遇到的坑 浅谈 xhr 请求跨域问题 JavaScript 学习笔记 Eclipse配置Web开发环境 Vue2 基本知识 Ribbon 简单使用 Nacos 简单使用 Spring Cloud Alibaba 环境搭建 什么是RSS?什么是Feed?它们有什么关系? Docker基本使用 TensorFlow启用GPU加速 如何进行内网穿透 Hello World! Git基本使用 Butterfly常用标签外挂
使用策略模式重构复杂业务分支
2026-04-05 · via 披萨盒的赛博日志

简要介绍

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

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

比如设想下面这个典型场景:我们刚刚完成了场景 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"


type Order struct {
Price float64
UserType string
}


func CalculatePrice(order Order) float64 {
switch order.UserType {
case "Normal":
return order.Price
case "VIP":
return order.Price * 0.8
case "Coupon":
return order.Price - 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

type DiscountStrategy interface {
GetFinalPrice(price float64) float64
}
1
2
3
4
5
6
7
8
9
10
11

type NormalStrategy struct{}
func (s *NormalStrategy) GetFinalPrice(price float64) float64 { return price }


type VIPStrategy struct{}
func (s *VIPStrategy) GetFinalPrice(price float64) float64 { return price * 0.8 }


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


type Request struct {
BizType string
UserTag string
EntityID int64
Operator string
Meta map[string]any
}


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"


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

type BaseStrategy struct {
priority int
}


func NewBaseStrategy(priority int) BaseStrategy {
return BaseStrategy{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


func Register(s Strategy) {
registry = append(registry, s)
sort.SliceStable(registry, func(i, j int) bool {
return registry[i].Priority() > registry[j].Priority()
})
}


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())
}


type DefaultOrderStrategy struct {
strategy.BaseStrategy
}


func NewDefaultOrderStrategy() *DefaultOrderStrategy {
return &DefaultOrderStrategy{
BaseStrategy: strategy.NewBaseStrategy(100),
}
}


func (s *DefaultOrderStrategy) Name() string {
return "default_order_strategy"
}


func (s *DefaultOrderStrategy) Supports(ctx context.Context, req *strategy.Request) bool {
return req != nil && req.BizType == "order"
}


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
}


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
}


func (s *DefaultOrderStrategy) CanExecute(ctx context.Context, req *strategy.Request) (bool, error) {
return true, nil
}


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
}


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())
}


type VIPOrderStrategy struct {
strategy.BaseStrategy
}


func NewVIPOrderStrategy() *VIPOrderStrategy {
return &VIPOrderStrategy{
BaseStrategy: strategy.NewBaseStrategy(200),
}
}


func (s *VIPOrderStrategy) Name() string {
return "vip_order_strategy"
}


func (s *VIPOrderStrategy) Supports(ctx context.Context, req *strategy.Request) bool {
return req != nil && req.BizType == "order" && req.UserTag == "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
}


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
}


func (s *VIPOrderStrategy) CanExecute(ctx context.Context, req *strategy.Request) (bool, error) {
return true, nil
}


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
}


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())
}


type VIPOrderStrategyV2 struct {
strategy.BaseStrategy
}


func NewVIPOrderStrategyV2() *VIPOrderStrategyV2 {
return &VIPOrderStrategyV2{
BaseStrategy: strategy.NewBaseStrategy(201),
}
}


func (s *VIPOrderStrategyV2) Name() string {
return "vip_order_strategy_v2"
}


func (s *VIPOrderStrategyV2) Supports(ctx context.Context, req *strategy.Request) bool {
return req != nil && req.BizType == "order" && req.UserTag == "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
}


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
}


func (s *VIPOrderStrategyV2) CanExecute(ctx context.Context, req *strategy.Request) (bool, error) {
return true, nil
}


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
}


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 反而是一种心智负担更小的开发方式。

总结

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

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