最近在给公司的商城做第三方支付的对接,看了一下以前的微信支付,感觉结合了一下之前看的设计模式,想试试能不能在上面用上。一番研究后,感觉也是可以,就是可能有点牛刀小试。
模式定义
- 策略模式的定义
策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。 - 模板方法模式定义
模板方法模式在一个方法中定义一个算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
ROUND 1:多支付方式
1、 场景描述
微信支付有预下单、退款、订单查询等等接口,看了下第三方支付,同样也是类似的三板斧。很容易,我们会想到以下这个类图:
图中可以看到WxPay和SandPay都继承了Pay,他们有同样的行为:下单、退款、订单查询,但是,我们可以知道他们的实现肯定是不一样的,这样我们都必须覆盖父类的方法,来处理不一样支付的细节,这样的时候,代码就会显得冗余,每个子类都需要覆盖父类的实现,如何让代码更加统一呢?
2、 问题分析
这个时候,我们需要的是将行为独立出来,让其封装在特定的行为类里,这样,我们就能“指定”行为到支付的实例。比如说,我们想要产生一个新的第三方支付实例,我们可以动态的让其实现微信支付的下单操作(当然这是不合理)。
这时,我们用到了一个很重要的设计原则:
针对接口编程,而不是针对实现编程
我们用接口代表每个行为,比如说,OrderBehavior和RefundBehavior,行为的每个实现都讲实现其中一个接口。支付类不会实现下单和退款的接口,而是有其他类专门实现。我们称这种类叫“行为”类。和以前做法不一样的地方在于,以前的做法是:行为由超类或者子类继承某个接口,自行实现。这两种做法都依赖与“实现”,这样我们很容易被实现绑死,很难在后来改变行为(除非写更多的代码)。新的设计里,支付类将使用由接口所表示的行为,所以实际的“实现”并不会绑死在支付类中,这样,支付类就不用在了解行为实现的细节。
来看一下新的类图:
以及,看看Pay抽象类中统一后的方法代码:
orderBehavior->orderPay();
}
public function refund()
{
return $this->refundBehavior->refund();
}
}
然后在实现的时候,子类就能这样动态实现
orderBehavior = new WxOrder();
//实现了微信支付的下单
$pay->orderPay();
//第三方支付同理
$pay = new SandPay();
$pay->orderBehavior = new SandOrder();
$pay->orderPay();
可能这时候有人会问,我明明可以直接就在具体类里实现这个方法,也不用多写这么多类与接口,正常来说,支付也不会改变其实现方式。
是的,一般来说,支付是不会修改的,但是如果突然说现在不用第三方支付,全部都用微信支付,那么我们代码的修改可能会很多了,但是用这种实现我们只需要改动一个很小地方:
orderBehavior = new WxOrder();
//我们由于不用管实际的细节,也不用做太多的代码改动,而且由微信支付本来的稳定实现,我们也能很放心的说,代码不会出现bug
$pay->orderPay();
这一种实现方式,也符合了一个设计原则:
多用组合,少用继承
这里支付对下单甚至退款的操作,实际的实现都不是通过继承得到的,而是通过将其他类的结合,不仅可以将算法族封装成类,更可以“在运行时动态改变行为”,只要行为对象符合正确的接口标准即可。
这个情况,你觉得是用了哪种设计模式?
ROUND 2:同样的实现结构
1、 场景描述
在处理完多种支付的场景后,我们在开始加入了第三方支付的代码,在写的过程发现,下单操作和微信支付类似,我们需要在请求
之前,组装请求必要参数、签名,得到返回结果后,对数据验签,然后进行自己的业务逻辑,最后返回一个应答。
同理,退款操作也是一样:组装请求必要参数、签名等等,和下单操作几乎处理逻辑的顺序或者说结构很相似,如果我们继续这么
编写,会发现很多重复代码,这个时候要怎么处理呢?
2、 问题分析
在解决问题之前,先说一下一个重要的设计原则:
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
通俗来说,就是要善于发现现实中的变与不变,抽离不变的地方,使其能复用,然后让变化的部分自行解决处理。
在这个场景中,很特殊的,我们发现不管是微信的下单、退款,还是第三方的下单、退款,他们业务的逻辑几乎是一个流水线上出
来的,就是他们不变的地方,而变化的是什么呢?很明显就是具体的实现业务不同,这部分应该由业务自己实现。
这样,我们将该描述用类图表示如下:
而代码具体如下:
generateRequestData();
$this->generateSign();
$this->request();
$this->verifiedSign();
$this->handleResponse();
$this->returnMsg();
}
abstract protected function generateRequestData();
abstract protected function generateSign();
abstract protected function request();
abstract protected function handleResponse();
abstract protected function verifiedSign();
abstract protected function returnMsg();
}
class WxOrder extends PayAction{
public function orderPay()
{
$this->doAction();
}
}
只针对这一系列操作,我们可以写成这个样子,这样所有的行为都一样了,只需要实现自身不一样的地方即可。
ROUND 3:结合的效果
1、如果把第一种情况的结构和第二种情况的结构,结合起来,最后是什么样子呢,我们可以看一下:
这样,整个结构就很清晰了,客户端调用实现了行为的下单或者退款类,不需要知道具体的细节,并且可以动态的改变行为的对象;而行为的操作的重复部分又被抽出来,只需要各自实现变化的部分,不变的部分都由抽象类PayAction先定义好,再由不一样的支付类实现公共的部分:签名与验签,甚至最后固定的返回正确和错误时的格式都是同一个支付里,相同的部分;最后不一样的只有不同请求时候,不一样的请求参数,以及获得返回参数后的不一样的处理了。
类和接口的确比一开始的设计要多了不少,但是结构非常清晰,而且修改的时候,可以很有针对性的修改,对于客户端来说,调用是透明的,对于提供服务的我们来说,不管是新增一个支付方式,还是多了一个支付操作,我们都可以很好的增加代码,不用修改现有代码,而这个也是一个经常说到的设计原则:
开闭原则:对修改关闭,对扩展开放
ROUND 4:解答与对比
第一和第二个场景,它们分别对应的设计模式是什么?
第一种设计模式是:策略模式,而第二种是:模板方法模式
第一种我们可以清楚的看到,我们对于如何运行一个行为的时候,我们是将其封装在一个类中处理,并且,它们可以相互替换。算法的改变由客户来决定,可以动态的改变。
第二种我们可以看到的是,整个算法的结构已经被定义好,跟着预定好的模板来编写我们的算法,就可以实现相似的功能有条不紊的编写下去,不会出现多余的部分,并且可以专心的处理自身特别的业务逻辑。
所以,以后如果代码的相关结构很相似,可以选用模板方法模式来编写;如果业务中的某些行为可以被抽象并且有需要动态改变的时候,可以考虑策略模式来编写。