24.4 策略模式的两个典型应用
策略模式实用性强、扩展性好,在软件开发中得以广泛使用,是使用频率较高的设计模式之一。下面将介绍策略模式的两个典型应用实例,一个来源于Java SE,一个来源于微软公司推出的演示项目PetShop。
(1) Java SE的容器布局管理就是策略模式的一个经典应用实例,其基本结构示意图如图24-3所示:
【每次看到这个LayoutManager2接口,我都在想当时Sun公司开发人员是怎么想的!微笑】
在Java SE开发中,用户需要对容器对象Container中的成员对象如按钮、文本框等GUI控件进行布局(Layout),在程序运行期间由客户端动态决定一个Container对象如何布局,Java语言在JDK中提供了几种不同的布局方式,封装在不同的类中,如BorderLayout、FlowLayout、GridLayout、GridBagLayout和CardLayout等。在图24-3中,Container类充当环境角色Context,而LayoutManager作为所有布局类的公共父类扮演了抽象策略角色,它给出所有具体布局类所需的接口,而具体策略类是LayoutManager的子类,也就是各种具体的布局类,它们封装了不同的布局方式。
任何人都可以设计并实现自己的布局类,只需要将自己设计的布局类作为LayoutManager的子类就可以,比如传奇的Borland公司(现在已是传说)曾在JBuilder中提供了一种新的布局方式——XYLayout,作为对JDK提供的Layout类的补充。对于客户端而言,只需要使用Container类提供的setLayout()方法就可设置任何具体布局方式,无须关心该布局的具体实现。在JDK中,Container类的代码片段如下:
public class Container extends Component {
……
LayoutManager layoutMgr;
……
public void setLayout(LayoutManager mgr) {
layoutMgr = mgr;
……
}
……
}
从上述代码可以看出,Container作为环境类,针对抽象策略类LayoutManager进行编程,用户在使用时,根据“里氏代换原则”,只需要在setLayout()方法中传入一个具体布局对象即可,无须关心它的具体实现。
(2) 除了基于Java语言的应用外,在使用其他面向对象技术开发的软件中,策略模式也得到了广泛的应用。
在微软公司提供的演示项目PetShop 4.0中就使用策略模式来处理同步订单和异步订单的问题。在PetShop 4.0的BLL(Business Logic Layer,业务逻辑层)子项目中有一个OrderAsynchronous类和一个OrderSynchronous类,它们都继承自IOrderStrategy接口,如图24-4所示:
在图24-4中,OrderSynchronous以一种同步的方式处理订单,而OrderAsynchronous先将订单存放在一个队列中,然后再对队列里的订单进行处理,以一种异步方式对订单进行处理。BLL的Order类通过反射机制从配置文件中读取策略配置的信息,以决定到底是使用哪种订单处理方式。配置文件web.config中代码片段如下所示:
……
<add key="OrderStrategyClass" value="PetShop.BLL.OrderSynchronous"/>
……
用户只需要修改配置文件即可更改订单处理方式,提高了系统的灵活性。
24.5 策略模式总结
策略模式用于算法的自由切换和扩展,它是应用较为广泛的设计模式之一。策略模式对应于解决某一问题的一个算法族,允许用户从该算法族中任选一个算法来解决某一问题,同时可以方便地更换算法或者增加新的算法。只要涉及到算法的封装、复用和切换都可以考虑使用策略模式。
策略模式的主要优点如下:
(1) 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
(2) 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族,恰当使用继承可以把公共的代码移到抽象策略类中,从而避免重复的代码。
(3) 策略模式提供了一种可以替换继承关系的办法。如果不使用策略模式,那么使用算法的环境类就可能会有一些子类,每一个子类提供一种不同的算法。但是,这样一来算法的使用就和算法本身混在一起,不符合“单一职责原则”,决定使用哪一种算法的逻辑和该算法本身混合在一起,从而不可能再独立演化;而且使用继承无法实现算法或行为在程序运行时的动态切换。
(4) 使用策略模式可以避免多重条件选择语句。多重条件选择语句不易维护,它把采取哪一种算法或行为的逻辑与算法或行为本身的实现逻辑混合在一起,将它们全部硬编码(Hard Coding)在一个庞大的多重条件选择语句中,比直接继承环境类的办法还要原始和落后。
(5) 策略模式提供了一种算法的复用机制,由于将算法单独提取出来封装在策略类中,因此不同的环境类可以方便地复用这些策略类。
策略模式的主要缺点如下:
(1) 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
(2) 策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类。
(3) 无法同时在客户端使用多个策略类,也就是说,在使用策略模式时,客户端每次只能使用一个策略类,不支持使用一个策略类完成部分功能后再使用另一个策略类来完成剩余功能的情况。
在以下情况下可以考虑使用策略模式:
(1) 一个系统需要动态地在几种算法中选择一种,那么可以将这些算法封装到一个个的具体算法类中,而这些具体算法类都是一个抽象算法类的子类。换言之,这些具体算法类均有统一的接口,根据“里氏代换原则”和面向对象的多态性,客户端可以选择使用任何一个具体算法类,并只需要维持一个数据类型是抽象算法类的对象。
(2) 一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重条件选择语句来实现。此时,使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句。
(3) 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法与相关的数据结构,可以提高算法的保密性与安全性。
练习
Sunny软件公司欲开发一款飞机模拟系统,该系统主要模拟不同种类飞机的飞行特征与起飞特征,需要模拟的飞机种类及其特征如表24-1所示:
表24-1 飞机种类及特征一览表
飞机种类 | 起飞特征 | 飞行特征 |
---|---|---|
直升机(Helicopter) | 垂直起飞(VerticalTakeOff) | 亚音速飞行(SubSonicFly) |
客机(AirPlane) | 长距离起飞(LongDistanceTakeOff) | 亚音速飞行(SubSonicFly) |
歼击机(Fighter) | 长距离起飞(LongDistanceTakeOff) | 超音速飞行(SuperSonicFly) |
鹞式战斗机(Harrier) | 垂直起飞(VerticalTakeOff) | 超音速飞行(SuperSonicFly) |
为将来能够模拟更多种类的飞机,试采用策略模式设计该飞机模拟系统。