写在之前

这周生活上出现了很多的不如意,从周一开始就觉得哪里出现了问题,然后就是各种烦躁的情绪,后来事情还真是如预感的那样发生了,很是心痛,但也无可奈何,希望大家都好好珍惜自己身边的人:友人,亲人,家人,愿一切和睦。

正文

一直以来都饱受公司APP客户端关于各种计费点的折磨。一段时间内,同一应用或不同应用间接入多家的计费模式,然后需要在不同的计费间来回的跳转,大大的增加了出错的几率,甚至有时候出现计费点错乱的现象,这就导致了工作效率的降低,而且做了大量的无用功,费时费力还没有成果。

基于这种困扰,一直以来都想封装一套统一实现计费策略的代码库,但是一直没有着手去做。最近有一套APP要实现微信,支付宝支付等计费,那么正好可以利用这个机会来实现一整套的计费点代码库。

而本篇博文的产生就是计费代码库的一个缩影,主要讲解的是如何使用策略模式设计这种可以使用多种算法来完成同样的目标。

需求分析

下面来简单的看下需求:

本款APP要求能实现微信、支付宝等支付方式进行购买虚拟币,购买完成后需上传购买数量,同时要更新用户所拥有的虚拟币。

ok,我们来分析下需求,主要可以分三步:

首先,购买虚拟币,这是一个动作,在这个动作中有可以分为多种形式,比如这里的用微信和支付宝购买;

其次,购买完成后,要上传服务端存储用户购买的虚拟币数量;

最后是更新UI,显示用户目前拥有的虚拟币。

由上面的分析,我们对需求有了很清晰的认识,这里的主要任务是设计一个简版的计费代码库,所以我们主要关心的也就是计费这一模块,其他的部分我们并不能考虑到计费库里,我相信大家都能明白,一个库,干好一件事即可。

说到计费,我们的需求中也就是购买虚拟币,而购买虚拟币这个行为可以是多种形式的,比如说使用微信,支付宝等,那么各种支付方式是不是都是一样的实现呢?

显然不是,它们都有自己的实现方式,而且每一家也都有自己的计费流程等,所以我们的库设计应该是可以符合多种实现的计费模式,并且能很好的实现扩展,而且各种实现可以互相替换,比如,购买虚拟币能使用微信支付也能使用支付宝支付,这就实现了互相替换。那么有没有这么一种实现方式呢?

显然答案是有的,这就是我们要讲的策略模式。

模式定义

那到底什么是策略模式呢?

策略模式是定义了一族算法,并分别将每组算法封装起来,使它们之间可以互相替换。算法的变化独立于使用算法的用户。

它主要使用在哪些场景呢?

①:同一种行为,多种实现。

②:安全地封装多种同一类型的操作。

③:同一抽象类有多个子类,而又需要选择具体子类操作。

由它的定义和使用场景,我们更加确信了我们的计费代码库使用这种模式是行得通的。

那么,我们到底该怎么使用策略模式进行设计我们的库呢?

详细设计

说道关于计费的问题,相信我们每个人都很清楚的了解,那就是需要有一个类似pay的方法专门用来进行支付,所以我们得有一个支付类,并且这个支付类带有一个pay的支付方法,如下设计:

Android 设计模式实战之关于封装计费代码库的策略模式详谈-LMLPHP

这里我并没有针对参数来进行详细的构建,毕竟只是一个针对简单的demo级别库,主要还是用来学习策略模式的,所以,我这里就只是简单的设计一个String类型的url参数,并让他返回一个String类型的结果,因为大多数网络请求都是返回的一个json串,便于解析。

可以看到我在这里设计了一个基类BasePay,并带有一个pay方法,接过相信支付功能的同学都应该知道,为了保证支付的安全性,很多支付渠道都会有一个预订单的接口,在预订单中返回一些重要的信息,并在支付接口中调用,例如微信支付就是这样模式。

所以为了满足这种需求,我们就需要有这么一个预订单的方法,那么我们在我们的基类中添加上这种方法:

Android 设计模式实战之关于封装计费代码库的策略模式详谈-LMLPHP

同样的没有做过多的参数考虑,只是让他传进一个String类型的URL地址。

ok,我们的基类已经设计出来了,那么各种各种的支付都可以继承此类来完成预订单和支付的能力,那么你觉得这样的设计怎么样呢?

假如有一种支付是不需要预订单呢?

这样也很简单,我就直接做一个空的实现。

没错,这种可以实现,那接入有100个支付,其中有80个没有预订单的实现呢?

难道你要做上80个空实现吗?

显然这是不可取的,那么我们该怎么做呢?

设计原则:保持共性,封装变化

假如有80个支付渠道不需要预订单,20个是需要预订单的,由此可以看出这个预订单的接口是变化的,而且剩下的20个需要预定单的每个实现也是不相同的,那么我们根据面向对象的设计原则:把需要变化的部分给独立的封装起来,只保留相同的部分,那么我们是不是可以把它给独立的封装起来以便应付随时变化的实现呢?

同样的道理支付接口的实现也是不尽相同的,是否也应该封装起来呢?

由此想到了另外的一个设计原则。

设计原则:针对接口编程,而不是针对实现编程

针对接口编程,不针对具体的实现进行编程,这样我们就可以区分不同的实现独立的完成,同时每个支付的业务只面对接口,不用关心具体的实现是怎么完成的:

Android 设计模式实战之关于封装计费代码库的策略模式详谈-LMLPHP

由上图所示,分别的定义了两个接口PreOrderInterface和PayInterface,并分别包含一个方法,并且我们分别对这两个接口做了两个实现,分别是针对微信和支付宝的。

这样一来的话,我们就把原来的BasePay里面的两个变化的方法分别的抽取出来封装成接口,并根据不同的业务渠道进行实现。

那我们是不是可以直接用BasePay基类来实现这个两个接口呢?

如果直接用BasePay来实现接口,那和不抽取变化封装有什么区别呢,所以我们不能直接用BasePay来或者是它的子类来直接的实现这两个接口,那么到底该怎么运用这两个接口呢?

由此我们想到了另外的一种设计原则:

设计原则:少用继承,多用组合

可以使用组合的形式来添加对接口的运用,有了接口的引用,不仅可以包含了算法的封装,同时也可以在运行时动态的改变行为。

Android 设计模式实战之关于封装计费代码库的策略模式详谈-LMLPHP

这样我们就有了对这两个接口的引用,那么我们在定义两个对接口调用的方法:

Android 设计模式实战之关于封装计费代码库的策略模式详谈-LMLPHP

同时为了便于扩展,为了能在运行时改变我们的行为方式,我们可以用设置接口的方式来实现:

Android 设计模式实战之关于封装计费代码库的策略模式详谈-LMLPHP

ok,这样将会更加利于我们的扩展。下面来看看完整的BasePay类吧:

Android 设计模式实战之关于封装计费代码库的策略模式详谈-LMLPHP

这样的话,当我们需要预订单的时候直接的调用performPreOrder方法,在支付的时候调用performPay方法,可以完全的不用考虑它的直接实现到底是怎么做的,我们只需要关心的就是调用的接口而已,从而降低了耦合性。

当然这只是我们的基类,我们还需要它的具体实现类:

Android 设计模式实战之关于封装计费代码库的策略模式详谈-LMLPHP

如果具体类有相同的功能,那么可以在父类中定义一个抽象方法,同时改变父类为抽象类,那么这个相同的功能就可以直接的在子类中实现,比如说要打印一个输出支付结果的log日志,那么就可以分别在子类中完成。

ok,有了子类具体实现,那么到底该怎么使用呢:

其实也很简单,既然父类已经有了对接口的引用,那么我们可以在子类中对接口实例化:

Android 设计模式实战之关于封装计费代码库的策略模式详谈-LMLPHP

Android 设计模式实战之关于封装计费代码库的策略模式详谈-LMLPHP

这里可以看出,当我们需要什么的时候就进行什么初始化,如果不需要,可以直接的不用管它。

那么在来看看我们是怎么使用的:

Android 设计模式实战之关于封装计费代码库的策略模式详谈-LMLPHP

当我们确定要使用哪种支付方式时,可以利用面向对象多态的特性指定具体的子类,然后在子类中实例化父类对接口的具体引用实现,当使用支付接口时,可以直接的调用父类提供的支付方法完成支付。

父类只需要关心引用的接口,而不需要关心具体的接口实现,实现解耦;

子类只需要关系对父类引用接口的实例化,和所共有的特性实现,也不需要具体的实现;

对具体的实现进行了封装,每种具体的实现类都实现接口,它只关注具体的实现,这样就把整个变化封装在特有的实现类中,通过接口获取到对它的引用,那么整个结构就显得非常的清晰,各个模块都只需要关注自己的领域,而且模块间耦合性也降到最低。

把变化进行封装,保持共性,通过接口制定具体的算法,这就是我们的策略模式。

最后在来看下整个类图:

Android 设计模式实战之关于封装计费代码库的策略模式详谈-LMLPHP

ok,这样我们就利用策略模式实现了一个简单的计费代码库,每当有新的计费加入时,我们只需要添加具体的实现类和具体的支付类,并不需要修改原来的代码,并且可以多方面复用。

到这里基本的策略模式就讲完了,计费代码库demo级也就出来了,当然它并不完善,只是关注策略模式的这一部分,其他的像网络请求,一些帮助类等都是需要封装的,也不是这个主题所关注的,所以这就需要大家根据自己的需求来实现了。

好了,今天就讲到这里吧。

各位如果还有哪里不明白的,或是我这里讲的还不够透彻,亦或是讲错了的地方请留言指正,让我们共同进步,谢谢

同时,请大家扫一扫关注我的微信公众号,虽然写的不是很勤,但是每一篇都有质量保证,让您学习到真正的知识。

Android 设计模式实战之关于封装计费代码库的策略模式详谈-LMLPHP

05-02 06:56