日常设计过程中我们常常会遇到这样的需求,在得到一个数据结果的过程中,有时会选择这样或那样不同的处理过程,也就是说目的相同,算法却不同,并且这些算法之间还要频繁的替换,如果是在小型的程序中,还可以使用方法重载的技术实现这种需求,但是面对大型系统,这样基于函数级别的封装就显得不便于维护,每次增加新的算法就要使得原来已有的算法重新编译一边,代价较高,因此策略模型的引入就显得非常重要。
以下学习笔记内容由java语言编写。
商场开业,需要在每台收银机上安装一个收银系统,要求每次录入商品的名称,单价和购买数量,点击确定按钮后就可以将当前商品的收费信息显示在收银界面,再次录入下一条商品信息点击确定后,将收费信息追加在收银界面,总价递增;商品录入结束后点击清零按钮刷新界面,清空录入列表和总价。
面向过程风格
以下代码中包含java图形化编程相关接口调用,要读懂部分函数参数和功能需要一定图形化编程经验。
1 | import java.awt.Font; |
显而易见,这样的程序轻而易举的就能指出很多缺点:
展示界面的swing代码和用于计算的业务逻辑代码杂糅在一起,难以分别
需要修改原有业务逻辑时需要整个重新编译源程序
无法轻便高效的拓展原系统功能
…
以上代码虽然实现了收银系统的功能,但是面向过程的编程风格使得代码的可读性、可维护性、可扩展性都不强,面对小小的需求改变都难以适应。例如,商场开业为了酬宾推出部分商品折扣活动,有五折、八折、九折三种折扣分别适用于部分商品,在生成商品收费清单时,不仅要用所选的优惠方式进行计算,还要将折扣信息输出在收银界面上。
这是我们就应该考虑将业务逻辑封装起来,适应需求的变化。
业务逻辑封装
- 业务逻辑封装类
1 | /** |
- 界面类
以下代码省略相同部分。
1 | …… |
这个版本的代码相比第一次有了很大的改善,代码的重构使得展示界面的“前端”代码和真正用于处理数据的业务逻辑代码分开,降低了耦合度。在这个版本中,当我们要对系统的界面进行优化或者业务逻辑的需求出现变化时,我们可以很轻易在不同的类中做修改,并且完全不影响到另一个方面。
但是这样的代码就是好的设计吗,显示不是,我们依然可以从中挑出不足:
业务逻辑的代码全部放在一个类中,从这个角度耦合度并没有降下来
每一次进行业务逻辑的计算实际上只用到了类中的一个计算方法,但是其他计算方法也无可避免的参与了系统的编译和实例化
如果要加入新的业务功能,要么单独写类增加界面类的判断逻辑,提高界面和业务的耦合,要么融入到已有的业务类中,使得业务逻辑类的内部愈加复杂
……
基于上述的种种问题,我们想到在上一个章节中介绍了简单工厂模式,显然就可以运用到这个案例中来,业务逻辑实际上可以抽象出计算实际收费功能的父类和具体不同算法实现收费的子类,这样不同的计算方式就可以分开来,降低业务逻辑的耦合度。
再看来需求,商场为了回馈快开业期间消费者的支持特地推出了更加有力的优惠活动,满减活动,但是不与原有的折扣活动共享,消费者自主选择划算的优惠方式。
简单工厂模式实现
- 收费工厂类
1 | public class CashFactory { |
- 优惠父类
1 | public class CashSuper { |
- 正常收费子类
1 | public class CashNormal extends CashSuper{ |
- 折扣子类
1 | public class CashDiscount extends CashSuper{ |
- 满减子类
1 | public class CashReturn extends CashSuper{ |
- 界面类
1 | …… |
到了这个版本,在业务逻辑方面的耦合度已经降得很低了,现在面对已有子类的新需求,我们只需要在工厂类中添加不同的参数名,返回相应的实例即可;而面对新的计算方法需求,添加计算方法类之后继承父类,并修改业务工厂中的调用即可。
到目前为止,虽然简单工厂模式也解决了收费算法的问题,但是这个模式只是解决了对象的创建问题,而且由于工厂本身包括了所有的收费方式,每次维护和扩展都要改动这个工厂,以致代码需要重新编译部署,所以这不是最好的办法。这里我们就要引出今天的策略模式了。
策略模式
策略模式(strategy)定义了算法家族,分别封装起来,让其之间可以互相替换,使算法的变化不会影响到使用算法的用户
对于这个收银系统来说,不论是打折还是满减,甚至返利等算法,都是策略,用户真正想要得到的是优惠后所要付出的费用,而至于算法的实现过程并不关心。因此,我们真正所应该封装的,是不同的策略,而非类,这就是简单工厂模式所不能解决的问题,工厂只是解决了对象的创建,而用户所需要的只是类中的计算方法,而策略模式正好可以把对象也封装起来。在系统中,优惠父类即策略类,各种实现子类即具体策略类。
- 策略上下文
1 | public class CashContext { |
- 界面类
1 | …… |
实际上,这个版本的代码是策略模式和简单工厂模式的结合,将工厂类和策略模式的上下文结合了起来,使得代码更加轻便。对比策略模式和简单工厂模式的界面类代码,我们可以发现,简单工厂模式中我们的客户端需要使用工厂类和优惠父类两个类才可以完成策略的使用,而策略模式中只需要在客户端实例化策略上下文一个类就可以完成策略的使用,耦合度变得更低,连优惠父类也封装起来了。
总结
- 策略模式定义了一系列算法的方法,这些算法所做的工作相同,只是实现不同。策略模式以相同的方法调用不同的算法,减少了算法类和使用算法类之间的耦合
- 策略模式简化了单元测试,每个算法都有自己的类,可通过自己的接口单独测试
- 实践中策略模式可以用来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考略使用策略模式处理这种变化的可能性