《鸭子-策略模式Strategy.docx》由会员分享,可在线阅读,更多相关《鸭子-策略模式Strategy.docx(15页珍藏版)》请在第一文库网上搜索。
1、鸭子策略模式(Strategy)前百万事开头难,最近对这句话体会深刻!这篇文章是这个系列正式开始介绍设计模式的第一篇,所以肩负着确定这个系列风格的历史重任,它在我脑袋里默默地酝酿了好多天,却只搜刮出了一点儿不太清晰的轮廓,可是时间不等人,以后再多“迭代”几次吧!在前面的随笔里,我已经提到了,这个系列准备以HeadFirstDesignPatterns的结构为主线,所以每个模式的核心故事都是取材于此书,在此再次声明一下。不管怎样,宗旨是为了跟大家一起循序渐进地去认识设计模式。上一篇:模式和原则,得到很多朋友的支持和鼓励,这里再次深表感谢。这里我还是想呼吁一下,希望大家看过后多提宝贵意见,反对意见
2、更好,关键是我们在互动中可以共同进步,因为经验告诉我讨论(争论更甚)出来的火花,总是印象最深刻的。其实策略模式是一个很简单的模式,也是一个很常用的模式,可谓短小精悍。我在介绍这个模式的同时,为了加深大家对OO的理解,还会反复强调前面讲过的设计原则和GRASP模式。这个系列的文章前后多少会有一些关联的连续性,但是单独一篇文章针对单一模式也一定是独立的,所以不论大家想从前往后连续看也好,还是挑喜欢的跳着看,都没有问题。“罗嗦了这么多,太唐僧了吧,快点开始吧”(烂西红柿和臭鸡蛋从四面八方飞来)模拟鸭子Joe是一名00程序员,他为一家开发模拟鸭子池塘游戏的公司工作,该公司的主要产品是一种可以模拟展示多
3、种会游泳和呷呷叫的鸭子的游戏。这个游戏是使用标准的面向对象技术开发的,系统里所有鸭子都继承于Duck基类,系统的核心类图如下:模拟鸭子图1如图所示,在Duck基类里实现了公共的quack()和SWim()方法,而MaIIardDuck和RedheadD11Ck可以分别覆盖实现自己的disp1ay。方法,这样即重用了公共的部分,又支持不同子类的个性化扩展。从目前的情况看,这是一个很好的设计,哈!但是,商场如战场,不进则退。Joe的公司最近的日子不好过,盗版泛滥,再加上竞争对手的围追堵劫,已经拖欠好几个月工资了。因此,公司高层在一次集体“腐败”后,决定一定要给系统增加一些超玄的功能,以彻底击垮竞争
4、对手。经过董事会讨论,最终觉得如果能让鸭子飞起来,那么一定可以给对手致命一击。于是Joe的上司对董事们拍着胸脯说:“这没有问题,Joe是一个00程序员,这对他来说太简单了!我们保证一周内结束战斗。”接到任务的JOe丝亳不敢怠慢,研究了上级的指示以后,发现只要在Duck里增加一个f1y()方法就可以搞定了,这样所有继承DUCk的鸭子就都拥有了会飞的能力,哈!这回奖金有盼头啦!改进后的系统类图如下:模拟鸭子图2Joe的上司很高兴,带着新产品给董事们演示去了JOe的上司:“我正在给董事们演示你会飞的鸭子,但是怎么有很多橡皮鸭子也在四处乱飞呢?你在耍我吗?你还想不想混啦?!”(此处省略粗话100字)J
5、Oe被吓坏了,到手的奖金泡汤了!冷静下来的Joe发现,原来在DUCk类里增加的方法,也同样被继承于Duck的RubberDuck类继承了,所以就有了会飞的橡皮鸭子,这是严重违反该系统“真实模拟各种鸭子”的原则的!那么该怎么办呢?Joe很郁闷!他突然想到:如果在RubberDuck类里把f1y()方法重写一下会如何?在RubberDuck类的f1y()里让橡皮鸭子什么都不做,不就一切OK了吗!那以后再增加一个木头鸭子呢?它不会飞也不会叫,那不是要再重写quack()和f1y()方法,以后再增加其它特殊的鸭子都要这样,这不是太麻烦了,而且也很混乱。最终,Joe认识到使用继承不是办法,因为他的上司通
6、知他,董事会决定以后每6个月就会升级一次系统,以应对市场竞争,所以未来的变化会很频繁,而且还不可预知。如果以后靠逐个类去判断是否重写了quack()或f1y()方法来应对变化,显然混不下去!(Joe这时很迷惑,为什么屡试不爽的继承,在系统维护升级的时候,无法很好地支持重用呢?)那么使用接口怎么样?我可以把f1y()方法放在接口里,只有那些会飞的鸭子才需要实现这个接口,最好把quack()方法也拿出来放到一个接口里,因为有些鸭子是不会叫的。就像下面这样:模拟鸭子图3Joe的上司知道后怒了:“你这样做难道是希望所有需要quack()和f1y()方法的鸭子都去重复实现这两个方法的功能吗?就这么几个鸭
7、子还好说,但是我们有几十、上百个鸭子的时候你怎么办?如果某个方法要做一点修改,难道你要重复修改上百遍吗?你是不是疯啦?”呵呵!如果你是Joe,你该怎么办?我们知道,并不是所有的鸭子都会飞、会叫,所以维承不是正确的方法。但是虽然上面的使用FIyabIe接口的方法,可以解决部分问题(不再有会飞的橡皮鸭子),但是这个解决方案却彻底破坏了重用,它带来了另一个维护的噩梦!而且还有一个问题我们前面没有提到,难道所有的鸭子的飞行方式、叫声等行为都是一模一样的吗?不可能吧!说到这里,为了能帮助Joe摆脱困境,我们有必要先停下来,重新回顾一些面向对象设计原则。请您告诉我:“什么东西是在软件开发过程中是恒定不变的
8、?,您想到了吗?对,那就是变化本身,正所谓“计划没有变化快”,所以直面“变化这个事实”才是正道!Joe面对的问题是,鸭子的行为在子类里持续不断地改变,所以让所有的子类都拥有基类的行为是不适当的,而使用上面的接口的方式,又破坏了代码重用。现在就需要用到我们的第一个设计原则:Identifytheaspectsofyourapp1icationthatvaryandseparatethemfromwhatstaystheSame.(找到系统中变化的部分,将变化的部分同其它稳定的部分隔开。)换句话说就是:“找到变化并且把它封装起来,稍后你就可以在不影响其它部分的情况下修改或扩展被封装的变化部分。”尽
9、管这个概念很简单,但是它几乎是所有设计模式的基础,所有模式都提供了使系统里变化的部分独立于其它部分的方法。OK!现在我们已经有了一条设计原则,那么Joe的问题怎么办呢?就鸭子的问题来说,变化的部分就是子类里的行为。所以我们要把这部分行为封装起来,省得它们老惹麻烦!从目前的情况看,就是f1y()和quack()行为总是不老实,而SWim()行为是很稳定的,这个行为是可以使用继承来实现代码重用的,所以,我们需要做的就是把f1y()和quack()行为从DUCk基类里隔离出来。我们需要创建两组不同的行为,一组表示f1y()行为,一组表示quack()行为。为什么是两组而不是两个呢?因为对于不同的子类
10、来说,f1y()和quack()的表现形式都是不一样的,有的鸭子嘎嘎叫,有的却呷叫I叫。有了这两组行为,我们就可以组合出不同的鸭子,例如:我们可能想要实例化一个新的MaI1ardDUCk(野鸭)实例,并且给它初始化一个特殊类型的飞行行为(野鸭飞行能力比较强)。那么,如果我们可以这样,更进一步,为什么我们不可以动态地改变一个鸭子的行为呢?换句话说,我们将在DUCk类里包含行为设置方法,所以我们可以说在运行时改变Ma1IardDUCk的飞行行为,这听起来更酷更灵活了!那么我们到底要怎么做呢?回答这个问题,先要看一下我们的第二个设计原则:Programtoaninterface,notanimp1e
11、mentation.(面向接口编程,而不要面向实现编程。)嘿!对于这个原则,不论是耳朵还是眼睛,是不是都太熟悉了!“接口”这个词已经被赋予太多的含义,搞的大家一说点儿屁事就满嘴往外蹦“接口”。那么它到底是什么意思呢?我们这里说的接口是一个抽象的概念,不局限于语言层面的接口(例如C#里的interface)。一个接口也可以是一个抽象类,或者一个基类也可以看作是一种接口的表现形式,因为基类变量可以用来引用其子类。要点在于,我们在面向接口编程的时候,可以使用多态,那么实际运行的代码只依赖于具体的接口(interface,抽象类,基类),而不管这些接口提供的功能是如何实现的,也就是说,接口将系统的不同
12、部分隔离开来,同时又将它们连接在一起C我的神啊!接口真是太伟大了!(烂西红柿和臭鸡蛋从四面八方飞来)OK!这回该彻底解决Joe的问题了!根据面向接口编程的设计原则,我们应该用接口来隔离鸭子问题中变化的部分,也就是鸭子的不稳定的行为(f1y0、quack()。我们要用一个FIyBehaVior接口表示鸭子的飞行行为,这个接口可以有多种不同的实现方式,可以“横”着分,也可以“竖”着分,管它呢!这样做的好处就是我们将鸭子的行为实现在一组独立的类里,具体的鸭子是通过F1yBehavior这个接口来调用这个行为的,因为DUCk只依赖FIyBehaVior接口,所以不需要管FIyBehaVior是如何被实
13、现的。如下面的类图,FIyBehaVior和QUaCkBehaViOr接口都有不同的实现方式!模拟鸭子图4JOe已经晕了,“你说了这么多,全是大白话,来点代码行不行,我要C#的!o说到这里,我们也该开始彻底改造这个设计了,并会在最后附加部分代码来帮助大家理解。第一步:我们要给Duck类增加两个接口类型的实例变量,分别是f1yBehavior和quackBehavior,它们其实就是新的设计里的“飞行”和“叫唤”行为。每个鸭子对象都将会使用各种方式来设置这些变量,以引用它们期望的运行时的特殊行为类型(使用横着飞,吱吱叫,等等)。第二步:我们还要把f1y()和quack()方法从DUCk类里移除,
14、因为我们已经把这些行为移到F1yBehavior和QuackBehavior接口里了。我们将使用两个相似的PerfOrmFIy()和PerformQuack()方法来替换f1y0和qucak()方法,后面你会看到这两个新方法是如何起作用的。第三步:我们要考虑什么时候初始化f1yBehavior和QuackBehavior变量。最简单的办法就是在DUCk类初始化的时候同时初始化他们。但是我们这里还有更好的办法,就是提供两个可以动态设置变量值的方法SetFIyBehaVioro和SetQuackBehavior(),那么就可以在运行时动态改变鸭子的行为了。下面是修改后的Duck类图:DUCkf1y
15、Behavior:F1yBehaviorquackBehavior:QuackBehaviorswim()PerEormF1y0PerEormQuack()SetF1yBehavior()SetQuackBehavior()virtua1disp1ay。模拟鸭子图5我们再看看整个设计修改后的类图:模拟鸭子映最后大家再看看演示代码,因为代码比较多,就不贴出来了,大家可以下载后参考:下面是演示代码的执行结果:fi1G:/设计稹式/喊计模式随笔系列/DudbiBWg我亮一只野鸭!M我飞啦!北呷呷口我是一只红头鸭!我飞啦!#嘎嘎我是一个橡皮鸭子!功不会飞!林吱吱这就是策略模式前面说了那么多,现在终于到
16、了正式介绍我们今天的主角的时候啦!此刻心情真是好激动啊!其实我们在前面就是使用Strategy模式帮Joe度过了难过,真不知道他发了奖金后要怎么感谢我们啊。OK!下面先看看官方的定义:TheStrategyPatterndefinesafami1yofa1gorithms,encapsu1ateseachone,andmakestheminterchangeab1e.Strategy1etsthea1gorithmvaryindependent1yfromc1ientsthatuseit.(策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。)怎么样,有了前面Joe的经历,这个定义理解起来还不那么太费劲吧