Go 程序里 if else 分支太多?试着用策略模式治理一下吧

2023-01-03 16:25:28 浏览数 (1)

本周发布的设计模式文章,忘记标注原创,这里重新发布一次。 本次跟大家分享的是策略模式,是一个可以减少我们在程序流程里不停堆叠 if else 的一个好办法,大家一起学一下吧。

大家好,我是每周在这里陪你一起进步的网管。

上篇文章我给大家分享了设计模式中的模版模式,给大家讲了用模版模式在项目开发时提炼流程、减少重复开发的技巧。同时,在上一篇文章我也分享了我总结的一个暴论,那就是“模板、策略和职责链三个设计模式是解决业务系统流程复杂多变这个痛点的利器”。

今天我们继续接着一起学习一下策略模式,以及用 Go 代码怎么实现策略模式。

什么是策略模式

策略模式是一种行为设计模式,通过策略模式,可以在运行时修改一个对象的行为。很多资料里对它的定义是:

定义一类算法族,将每个算法分别封装起来,让他们可以互相替换,此模式让算法的变化独立于使用算法的客户端。

看完策略模式这个定义,你是不是也有一种看了等于没看的感觉,我一开始看的时候也是这样,下面我再用一些大白话给大家解释一下。

白话策略模式

策略模式这个定义乍一看起来,还是挺抽象、挺难懂的,这里说的算法并不是我们想找工作准备面试时每天要刷的那种算法;定义一类算法族中的算法族说的要完成的某项任务的归类,举个例子来说比如用户支付,就是个任务类。

算法族中的每个算法(即策略)则是说的完成这项任务的具体方式,结合我们的例子来说就是可以用支付宝也可以用微信支付这两种方式 (算法) ,来完成我们定义的用户支付这项任务 (算法族)。

策略模式主要用于允许我们的程序在运行时动态更改一个任务的处理逻辑,常见的应用场景有针对软件用户群体的不同策略切换(用一个烂大街的词儿表达就是千人千面)和业务流程兜底切换。

注意:这里是为了大家好理解举了支付这个例子,实际上运行时切换支付方式还是挺复杂的,实践的时候你可以先从运行时切换通知用户的任务练起。

策略模式要解决的问题是,让使用客户端跟具体执行任务的策略解耦,不管使用哪种策略完成任务,不需要更改客户端使用策略的方式。

上面说的这些使用策略模式完成任务的整个形态用 UML 图表示出来,会比较清晰,策略模式的 UML 图如下:

图中,主要有四类角色:

  • 客户端:这个客户端可以简单理解成是发起任务调用的代码。
  • 抽象策略:就是上面定义中的算法族,是所有具体策略的通用接口,声明了用于执行完成任务的方法。
  • 具体策略:实现了抽象策略,定义了具体应该怎么完成任务。
  • 上下文:作为客户端和具体策略的中间层,达到客户端与具体策略解耦的效果,它维护指向具体策略的引用,且仅通过抽象策略中定义的接口与具体策略进行交流。常用的实现方式是通过组合

上面类图里一个细节,上下文对象引用具体策略类的时候,使用的是组合的方式,让其私有属性指向策略接口的具体实现,这样就能完成在运行时修改执行任务的具体策略的效果(通过SetStrategy方法)。

光看上面的描述和UML图,还是有点单薄,为了更容易理解,下面咱们再举个更具体点的例子。

策略模式示例--实现支付策略

举例环节,接着用我们上面用的用户支付这个任务为例子。比如说在购物 App 上买东西后要付钱,客户端使用微信支付、或者是其他三方在线支付。如果使用策略模式进行解耦,客户端都可以使用同样的调用方式完成支付,甚至可以在微信支付不能使用时,让应用无痛地切换到三方支付,来完成支付。

注意这里的客户端是上面说的,调用上下文的代码,不是手机APP。

在用代码实现支付策略前,先用 UML 类图梳理一下整个实现的大体结构:

  • PayBehavior:抽象策略,对支付任务进行接口抽象
  • WxPay 和 ThirdPay :是具体的策略实现
  • PaxCtx:上下文对象在这里有两个作用,第一是协调自己持有的 PayBehavior 具体实现,完成支付的任务,第二是维护发起支付需要的支付参数--即图中的私有属性payParams

下面我们把这个 UML 图转化为代码实现,首先是定义PayBehavior 策略的接口

代码语言:javascript复制
type PayBehavior interface {
 OrderPay(px *PayCtx)
}

有了接口后,我们来定义两个策略的实现

代码语言:javascript复制
// 具体支付策略实现
// 微信支付
type WxPay struct {}
func(*WxPay) OrderPay(px *PayCtx) {
 fmt.Printf("Wx支付加工支付请求 %vn", px.payParams)
 fmt.Println("正在使用Wx支付进行支付")
}

// 三方支付
type ThirdPay struct {}
func(*ThirdPay) OrderPay(px *PayCtx) {
 fmt.Printf("三方支付加工支付请求 %vn", px.payParams)
 fmt.Println("正在使用三方支付进行支付")
}

有了策略的实现后,还得有个上下文来协调它们,以及持有完成这个任务所必需的那些入参payParams

代码语言:javascript复制

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,
 }
}


所有这些代码都准备好后,我们就可以试着运行程序调用它们了。

代码语言:javascript复制
func main() {
 wxPay := &WxPay{}
 px := NewPayCtx(wxPay)
 px.Pay()
 // 假设现在发现微信支付没钱,改用三方支付进行支付
 thPay := &ThirdPay{}
 px.setPayBehavior(thPay)
 px.Pay()
}

这个例子的实现还是比较简单的,相信大家都能看懂,我觉得最重要的是理解这个代码框架,后面自己结合实际在项目里实现策略模式的时候,可以支持拿来套用。

下面我们再来说说策略模式和上篇文章学习的模板模式的区别和关联使用。

策略模式和模板模式

策略模式和模版模式经常配合使用,策略模式是让完成某个任务的具体方式可以相互切换,而模版模式则是针对一个流程的共性梳理出固定的执行步骤,具体步骤的执行方式下放给子类来实现。两者解耦的维度不一样,策略模式在抽象方法的实现里,经常会用到模板模式。

还是拿我们上面的支付行为举例子。上面策略模式定义了一个算法族(支付),以及多个具体算法实现(微信、三方支付),让支付策略对客户端解耦。

上面咱们的示例代码还是比较简单的,通常完成支付时,还需要用参数生成签名、验证客户端传过来的签名、调用支付基础服务进行预下单、下单等操作,但是每种支付基础服务设计的接口和交互流程可能会有些小的差别,这个时候就可以用上签名学的模版模式,统一支付任务内部的流程步骤,策略模式、模版模式相结合使用能让我们写的程序更健壮、更容易维护。

好了今天的文章就到这里了,如果你也想让自己写出更好的代码,想用设计模式解决开发中的痛点,请持续关注后面这个系列的更新,觉得文章不错就动动手点个在看和赞吧,我们下期再见。

- END -

0 人点赞