行为模式--策略模式

 

 
 
下面将会先用Java设计这个电影票打折方案课题并用策略模式更改设计,然后用Objective-C设计这个电影票打折方案课题并用策略模式更改设计。
 
为了实现上述电影票打折功能,Sunny 软件公司开发人员设计了一个电影票类 MovieTicket,其核心代码(Java)片段如下所示:
 

通过 MovieTicket 类实现了电影票的折后价计算,该方案解决了电影票打折问题,每一种打折方式都可以称为一种打折算法,更换打折方式只需修改客户端代码中的参数,无须修改已有源代码,但该方案并不是一个完美的解决方案,它至少存在如下三个问题:

(1) MovieTicket 类的 calculate() 方法非常庞大,它包含各种打折算法的实现代码,在代码中出现了较长的 if…else… 语句,不利于测试和维护。

(2) 增加新的打折算法或者对原有打折算法进行修改时必须修改 MovieTicket 类的源代码,违反了“开闭原则”,系统的灵活性和可扩展性较差。

(3) 算法的复用性差,如果在另一个系统(如商场销售管理系统)中需要重用某些打折算法,只能通过对源代码进行复制粘贴来重用,无法单独重用其中的某个或某些算法(重用较为麻烦)。

如何解决这三个问题?导致产生这些问题的主要原因在于 MovieTicket 类职责过重,它将各种打折算法都定义在一个类中,这既不便于算法的重用,也不便于算法的扩展。因此我们需要对 MovieTicket 类进行重构,将原本庞大的 MovieTicket 类的职责进行分解,将算法的定义和使用分离,这就是策略模式所要解决的问题,下面将进入策略模式的应用。

算法的封装与切换——策略模式

完整解决方案

为了实现打折算法的复用,并能够灵活地向系统中增加新的打折方式,Sunny 软件公司开发人员使用策略模式对电影院打折方案进行重构,重构后基本结构如图所示:

 
 
 为了提高系统的灵活性和可扩展性,我们将具体策略类的类名存储在配置文件中,并通过工具类 XMLUtil 来读取配置文件并反射生成对象,XMLUtil 类的代码如下所示:
 如果需要增加新的打折方式,原有代码均无须修改,只要增加一个新的折扣类作为抽象折扣类的子类,实现在抽象折扣类中声明的打折方法,然后修改配置文件,将原有具体折扣类类名改为新增折扣类类名即可,完全符合“开闭原则”。
 
 工程已经上传至GitHub官网中,可自行下载:MoiveTicketTest  (java版本)点击可直接下载。
 
前面提供的是java的小实例项目,接下来提供Objective-C的小实例项目
 
首先讲一下自己最开始的思路是有点不确切的,最开始拿到这个课题,我最先是用OC编写,当时看到这个三个并列的要求:
 
我处理方式是:需要三个参数:
  (BOOL)hasStudent表示学生证,(int)useAge表示年龄小于10,(BOOL)isVIP表示VIP用户。 (Objective-C语言)
  另外我还增加了设置积分的方法,以满足课题第三个要求:积分累计到一定额度可换取电影院赠送的奖品
 
这样我在设计代码的时候,参数不统一,后来自己对比这篇官方文档的时候,官方作者采用的是:
  (String)"Student"表示学生用户,(String)"Children"表示儿童用户,(String)"VIP"VIP表示VIP用户。(Java语言)
 这个参数统一的设计细节可以将这三个属性值放在一个方法中处理。这样比较好。
 
 接下来我就展示我的最开始的(Objective-C语言)代码
 因为这部分代码确实写的不好,就不提供源代码下载链接了,相信读者的你也不愿意使用这部分代码的吧。
接着进行改进上面这个不好的Objective-C的源代码:
  这样这个简单的小项目是不是一目了然了呢?但是从设计模式原则来看,如果是一个大的项目,这个设计方式将很多并列的业务逻辑都归于一个类中,如果以后以后继续增加业务逻辑,甚至增加更加复杂的业务逻辑,这样代码记忆显示的越来越臃肿,可读性不高。另外假设在一个大的项目中,提供的服务除了上面这个买电影票的三种优惠情况,如果这个项目还提供了订餐业务,订餐业务如果也和买电影票的三种优惠情况类似,那么这些优惠情况,如果按照上面的设计方式,就需要再增加一个订餐类中又要重新写类似的业务逻辑。
  按照策略模式的优点来思考:如果能够把MovieTicket这个类中的业务逻辑抽离出来,设计成面向接口编程,然后具体实现算法。这样这些算法,还可以被订餐主类共享,也就是可以通过组合的方式,直接引用,就不用重新写过业务逻辑了。
 
下面就是应用策略模式来处理这个MovieTicket的类:
<MovieTicket.h MovieTicket.m Discount.h Discount.m main.m>
 
 <StudentDiscount.h StudentDiscount.m ChildDiscount.h ChildDiscount.m VIPDiscount.h VIPDiscount.m>
 运行结果结果是:
 项目源代码下载网址: github网址
(2017年2月9日补充:事实上,在OC中也有Class类,通过Class类创建类对象,也可以仿照Java的方式根据类的字符串名字创建对象)

介绍 策略模式 的两个典型应用

策略模式实用性强、扩展性好,在软件开发中得以广泛使用,是使用频率较高的设计模式之一。下面将介绍策略模式的两个典型应用实例,一个来源于Java SE,一个来源于微软公司推出的演示项目 PetShop。  

(1) Java SE 的容器布局管理就是策略模式的一个经典应用实例,其基本结构示意图如图所示:

在 Java SE 开发中,用户需要对容器对象 Container 中的成员对象如按钮、文本框等 GUI 控件进行布局(Layout),在程序运行期间由客户端动态决定一个 Container 对象如何布局,Java 语言在 JDK 中提供了几种不同的布局方式,封装在不同的类中,如 BorderLayout、FlowLayout、GridLayout、GridBagLayout 和 CardLayout 等。在图中,Container 类充当环境角色Context,而 LayoutManager 作为所有布局类的公共父类扮演了抽象策略角色,它给出所有具体布局类所需的接口,而具体策略类是 LayoutManager 的子类,也就是各种具体的布局类,它们封装了不同的布局方式。

任何人都可以设计并实现自己的布局类,只需要将自己设计的布局类作为 LayoutManager 的子类就可以,比如传奇的 Borland 公司曾在 JBuilder 中提供了一种新的布局方式——XYLayout,作为对JDK提供的Layout类的补充。对于客户端而言,只需要使用 Container 类提供的 setLayout() 方法就可设置任何具体布局方式,无须关心该布局的具体实现。在 JDK 中,Container 类的代码片段如下:  

 1 public class Container extends Component {  
 2     ……  
 3     LayoutManager layoutMgr;  
 4     ……  
 5     public void setLayout(LayoutManager mgr) {  
 6     layoutMgr = mgr;  
 7     ……  
 8     }  
 9     ……  
10 }  

从上述代码可以看出,Container 作为环境类,针对抽象策略类 LayoutManager 进行编程,用户在使用时,根据“里氏代换原则”,只需要在 setLayout() 方法中传入一个具体布局对象即可,无须关心它的具体实现。

(2) 除了基于Java语言的应用外,在使用其他面向对象技术开发的软件中,策略模式也得到了广泛的应用。

在微软公司提供的演示项目 PetShop 4.0 中就使用策略模式来处理同步订单和异步订单的问题。在 PetShop 4.0 的 BLL(Business Logic Layer,业务逻辑层)子项目中有一个 OrderAsynchronous 类和一个 OrderSynchronous 类,它们都继承自 IOrderStrategy 接口,如图所示:  

在图中,OrderSynchronous 以一种同步的方式处理订单,而 OrderAsynchronous 先将订单存放在一个队列中,然后再对队列里的订单进行处理,以一种异步方式对订单进行处理。BLL 的 Order 类通过反射机制从配置文件中读取策略配置的信息,以决定到底是使用哪种订单处理方式。配置文件 web.config 中代码片段如下所示:

……
<add key="OrderStrategyClass" value="PetShop.BLL.OrderSynchronous"/>
……

用户只需要修改配置文件即可更改订单处理方式,提高了系统的灵活性。

策略模式总结

策略模式用于算法的自由切换和扩展,它是应用较为广泛的设计模式之一。策略模式对应于解决某一问题的一个算法族,允许用户从该算法族中任选一个算法来解决某一问题,同时可以方便地更换算法或者增加新的算法。只要涉及到算法的封装、复用和切换都可以考虑使用策略模式。

主要优点

策略模式的主要优点如下:

(1) 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为

(2) 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族,恰当使用继承可以把公共的代码移到抽象策略类中,从而避免重复的代码。

(3) 策略模式提供了一种可以替换继承关系的办法。如果不使用策略模式,那么使用算法的环境类就可能会有一些 子类,每一个子类提供一种不同的算法。但是,这样一来算法的使用就和算法本身混在一起,不符合“单一职责原则”,决定使用哪一种算法的逻辑和该算法本身混 合在一起,从而不可能再独立演化;而且使用继承无法实现算法或行为在程序运行时的动态切换。

(4) 使用策略模式可以避免多重条件选择语句。多重条件选择语句不易维护,它把采取哪一种算法或行为的逻辑与算法或行为本身的实现逻辑混合在一起,将它们全部硬编码(Hard Coding)在一个庞大的多重条件选择语句中,比直接继承环境类的办法还要原始和落后。

(5) 策略模式提供了一种算法的复用机制,由于将算法单独提取出来封装在策略类中,因此不同的环境类可以方便地复用这些策略类。

主要缺点

策略模式的主要缺点如下:

(1) 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。

(2) 策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类。

(3) 无法同时在客户端使用多个策略类,也就是说,在使用策略模式时,客户端每次只能使用一个策略类,不支持使用一个策略类完成部分功能后再使用另一个策略类来完成剩余功能的情况。

适用场景

在以下情况下可以考虑使用策略模式:

(1) 一个系统需要动态地在几种算法中选择一种,那么可以将这些算法封装到一个个的具体算法类中,而这些具体算法类都是一个抽象算法类的子类。换言之,这些具体 算法类均有统一的接口,根据“里氏代换原则”和面向对象的多态性,客户端可以选择使用任何一个具体算法类,并只需要维持一个数据类型是抽象算法类的对象。

(2) 一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重条件选择语句来实现。此时,使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句。

(3) 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法与相关的数据结构,可以提高算法的保密性与安全性。

 

 
 
 
 
 学习来自《极客学院》《幕客网》《设计模式其实很简单》
 
 

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
posted @ 2015-09-11 04:52  何杨  阅读(397)  评论(0编辑  收藏  举报