Go 程序里 if else 分支太多?试着用策略模式治理一下吧
本周发布的设计模式文章,忘记标注原创,这里重新发布一次。
本次跟大家分享的是策略模式,是一个可以减少我们在程序流程里不停堆叠 if else 的一个好办法,大家一起学一下吧。
大家好,我是每周在这里陪你一起进步的网管。
上篇文章我给大家分享了设计模式中的模版模式,给大家讲了用模版模式在项目开发时提炼流程、减少重复开发的技巧。同时,在上一篇文章我也分享了我总结的一个暴论,那就是“模板、策略和职责链三个设计模式是解决业务系统流程复杂多变这个痛点的利器”。
今天我们继续接着一起学习一下策略模式,以及用 Go 代码怎么实现策略模式。
什么是策略模式
策略模式是一种行为设计模式,通过策略模式,可以在运行时修改一个对象的行为。很多资料里对它的定义是:
定义一类算法族,将每个算法分别封装起来,让他们可以互相替换,此模式让算法的变化独立于使用算法的客户端。
看完策略模式这个定义,你是不是也有一种看了等于没看的感觉,我一开始看的时候也是这样,下面我再用一些大白话给大家解释一下。
白话策略模式
策略模式这个定义乍一看起来,还是挺抽象、挺难懂的,这里说的算法并不是我们想找工作准备面试时每天要刷的那种算法;定义一类算法族中的算法族说的要完成的某项任务的归类,举个例子来说比如用户支付,就是个任务类。
算法族中的每个算法(即策略)则是说的完成这项任务的具体方式,结合我们的例子来说就是可以用支付宝也可以用微信支付这两种方式 (算法) ,来完成我们定义的用户支付这项任务 (算法族)。
策略模式主要用于允许我们的程序在运行时动态更改一个任务的处理逻辑,常见的应用场景有针对软件用户群体的不同策略切换(用一个烂大街的词儿表达就是千人千面)和业务流程兜底切换。
注意:这里是为了大家好理解举了支付这个例子,实际上运行时切换支付方式还是挺复杂的,实践的时候你可以先从运行时切换通知用户的任务练起。
策略模式要解决的问题是,让使用客户端跟具体执行任务的策略解耦,不管使用哪种策略完成任务,不需要更改客户端使用策略的方式。
上面说的这些使用策略模式完成任务的整个形态用 UML 图表示出来,会比较清晰,策略模式的 UML 图如下:
图中,主要有四类角色:
- 客户端:这个客户端可以简单理解成是发起任务调用的代码。
- 抽象策略:就是上面定义中的算法族,是所有具体策略的通用接口,声明了用于执行完成任务的方法。
- 具体策略:实现了抽象策略,定义了具体应该怎么完成任务。
- 上下文:作为客户端和具体策略的中间层,达到客户端与具体策略解耦的效果,它维护指向具体策略的引用,且仅通过抽象策略中定义的接口与具体策略进行交流。常用的实现方式是通过组合
上面类图里一个细节,上下文对象引用具体策略类的时候,使用的是组合的方式,让其私有属性指向策略接口的具体实现,这样就能完成在运行时修改执行任务的具体策略的效果(通过SetStrategy
方法)。
光看上面的描述和UML图,还是有点单薄,为了更容易理解,下面咱们再举个更具体点的例子。
策略模式示例--实现支付策略
举例环节,接着用我们上面用的用户支付这个任务为例子。比如说在购物 App 上买东西后要付钱,客户端使用微信支付、或者是其他三方在线支付。如果使用策略模式进行解耦,客户端都可以使用同样的调用方式完成支付,甚至可以在微信支付不能使用时,让应用无痛地切换到三方支付,来完成支付。
注意这里的客户端是上面说的,调用上下文的代码,不是手机APP。
在用代码实现支付策略前,先用 UML 类图梳理一下整个实现的大体结构:
- PayBehavior:抽象策略,对支付任务进行接口抽象
- WxPay 和 ThirdPay :是具体的策略实现
- PaxCtx:上下文对象在这里有两个作用,第一是协调自己持有的 PayBehavior 具体实现,完成支付的任务,第二是维护发起支付需要的支付参数--即图中的私有属性
payParams
。
下面我们把这个 UML 图转化为代码实现,首先是定义PayBehavior
策略的接口
type PayBehavior interface {
OrderPay(px *PayCtx)
}
有了接口后,我们来定义两个策略的实现
// 具体支付策略实现
// 微信支付
"本文使用的完整可运行源码
去公众号「网管叨bi叨」发送【设计模式】即可领取"
type WxPay struct {}
func(*WxPay) OrderPay(px *PayCtx) {
fmt.Printf("Wx支付加工支付请求 %v\n", px.payParams)
fmt.Println("正在使用Wx支付进行支付")
}
// 三方支付
type ThirdPay struct {}
func(*ThirdPay) OrderPay(px *PayCtx) {
fmt.Printf("三方支付加工支付请求 %v\n", px.payParams)
fmt.Println("正在使用三方支付进行支付")
}
有了策略的实现后,还得有个上下文来协调它们,以及持有完成这个任务所必需的那些入参payParams
"本文使用的完整可运行源码
去公众号「网管叨bi叨」发送【设计模式】即可领取"
type PayCtx struct {
// 提供支付能力的接口实现
payBehavior PayBehavior
// 支付参数
payParams map[string]interface{}
}
func (px *PayCtx) setPayBehavior(p PayBehavior) {
px.payBehavior = p
}
func (px *PayCtx) Pay() {
px.payBehavior.OrderPay(px)
}
func NewPayCtx(p PayBehavior) *PayCtx {
// 支付参数,Mock数据
params := map[string]interface{} {
"appId": "234fdfdngj4",
"mchId": 123456,
}
return &PayCtx{
payBehavior: p,
payParams: params,
}
}
所有这些代码都准备好后,我们就可以试着运行程序调用它们了。
func main() {
wxPay := &WxPay{}
px := NewPayCtx(wxPay)
px.Pay()
// 假设现在发现微信支付没钱,改用三方支付进行支付
thPay := &ThirdPay{}
px.setPayBehavior(thPay)
px.Pay()
}
这个例子的实现还是比较简单的,相信大家都能看懂,我觉得最重要的是理解这个代码框架,后面自己结合实际在项目里实现策略模式的时候,可以支持拿来套用。
本文的完整源码,已经同步收录到我整理的电子教程里啦,可向我的公众号「网管叨bi叨」发送关键字【设计模式】领取。