鸭子-策略模式Strategy.docx

上传人:李司机 文档编号:4383045 上传时间:2023-04-20 格式:DOCX 页数:15 大小:179.29KB
返回 下载 相关 举报
鸭子-策略模式Strategy.docx_第1页
第1页 / 共15页
鸭子-策略模式Strategy.docx_第2页
第2页 / 共15页
鸭子-策略模式Strategy.docx_第3页
第3页 / 共15页
鸭子-策略模式Strategy.docx_第4页
第4页 / 共15页
鸭子-策略模式Strategy.docx_第5页
第5页 / 共15页
点击查看更多>>
资源描述

《鸭子-策略模式Strategy.docx》由会员分享,可在线阅读,更多相关《鸭子-策略模式Strategy.docx(15页珍藏版)》请在三一办公上搜索。

1、鸭子策略模式(Strategy)前百万事开头难,最近对这句话体会深刻!这篇文章是这个系列正式开始介绍设计模式的第一 篇,所以肩负着确定这个系列风格的历史重任,它在我脑袋里默默地酝酿了好多天,却只搜刮出 了一点儿不太清晰的轮廓,可是时间不等人,以后再多“迭代”几次吧!在前面的随笔里,我已 经提到了,这个系列准备以Head First Design Patterns的结构为主线,所以每个模式的核 心故事都是取材于此书,在此再次声明一下。不管怎样,宗旨是为了跟大家一起循序渐进地去认 识设计模式。上一篇:模式和原则,得到很多朋友的支持和鼓励,这里再次深表感谢。这里我还是想呼 吁一下,希望大家看过后多提

2、宝贵意见,反对意见更好,关键是我们在互动中可以共同进步,因 为经验告诉我讨论(争论更甚)出来的火花,总是印象最深刻的。其实策略模式是一个很简单的模式,也是一个很常用的模式,可谓短小精悍。我在介绍这 个模式的同时,为了加深大家对OO的理解,还会反复强调前面讲过的设计原则和GRASP模式。 这个系列的文章前后多少会有一些关联的连续性,但是单独一篇文章针对单一模式也一定是独立 的,所以不论大家想从前往后连续看也好,还是挑喜欢的跳着看,都没有问题。“罗嗦了这么多,太唐僧了吧,快点开始吧”(烂西红柿和臭鸡蛋从四面八方飞来)模拟鸭子Joe是一名00程序员,他为一家开发模拟鸭子池塘游戏的公司工作,该公司的主

3、要产品是 一种可以模拟展示多种会游泳和呷呷叫的鸭子的游戏。这个游戏是使用标准的面向对象技术开发 的,系统里所有鸭子都继承于Duck基类,系统的核心类图如下:模拟鸭子图1如图所示,在Duck基类里实现了公共的quack()和SWim()方法,而MaIIardDuck和 RedheadD1.1.Ck可以分别覆盖实现自己的disp1.ay。方法,这样即重用了公共的部分,又支持 不同子类的个性化扩展。从目前的情况看,这是一个很好的设计,哈!但是,商场如战场,不进则退。Joe的公司最近的日子不好过,盗版泛滥,再加上竞争对手 的围追堵劫,已经拖欠好几个月工资了。因此,公司高层在一次集体“腐败”后,决定一定

4、要给 系统增加一些超玄的功能,以彻底击垮竞争对手。经过董事会讨论,最终觉得如果能让鸭子飞起 来,那么一定可以给对手致命一击。于是Joe的上司对董事们拍着胸脯说:“这没有问题,Joe 是一个00程序员,这对他来说太简单了!我们保证一周内结束战斗。”接到任务的JOe丝亳不敢怠慢,研究了上级的指示以后,发现只要在Duck里增加一个f1.y() 方法就可以搞定了,这样所有继承DUCk的鸭子就都拥有了会飞的能力,哈!这回奖金有盼头啦! 改进后的系统类图如下:模拟鸭子图2Joe的上司很高兴,带着新产品给董事们演示去了JOe的上司:“我正在给董事们演示你会飞的鸭子,但是怎么有很多橡皮鸭子也在四处乱飞 呢?你

5、在耍我吗?你还想不想混啦? ! ”(此处省略粗话100字)JOe被吓坏了,到手的奖金泡汤了!冷静下来的Joe发现,原来在DUCk类里增加的方法, 也同样被继承于Duck的RubberDuck类继承了,所以就有了会飞的橡皮鸭子,这是严重违反该系 统“真实模拟各种鸭子”的原则的!那么该怎么办呢? Joe很郁闷!他突然想到:如果在RubberDuck 类里把f1.y ()方法重写一下会如何?在RubberDuck类的f1.y()里让橡皮鸭子什么都不做,不就一 切OK 了吗!那以后再增加一个木头鸭子呢?它不会飞也不会叫,那不是要再重写quack()和f1.y() 方法,以后再增加其它特殊的鸭子都要这样

6、,这不是太麻烦了,而且也很混乱。最终,Joe认识到使用继承不是办法,因为他的上司通知他,董事会决定以后每6个月就会 升级一次系统,以应对市场竞争,所以未来的变化会很频繁,而且还不可预知。如果以后靠逐个 类去判断是否重写了 quack ()或f1.y ()方法来应对变化,显然混不下去!(Joe这时很迷惑,为什么屡试不爽的继承,在系统维护升级的时候,无法很好地支持重用 呢?)那么使用接口怎么样?我可以把f1.y()方法放在接口里,只有那些会飞的鸭子才需要实现这 个接口,最好把quack()方法也拿出来放到一个接口里,因为有些鸭子是不会叫的。就像下面这 样:模拟鸭子图3Joe的上司知道后怒了: “你

7、这样做难道是希望所有需要quack()和f1.y()方法的鸭子都去重 复实现这两个方法的功能吗?就这么几个鸭子还好说,但是我们有几十、上百个鸭子的时候 你怎么办?如果某个方法要做一点修改,难道你要重复修改上百遍吗?你是不是疯啦? ”呵呵!如果你是Joe,你该怎么办?我们知道,并不是所有的鸭子都会飞、会叫,所以维承不是正确的方法。但是虽然上面的 使用FIyabIe接口的方法,可以解决部分问题(不再有会飞的橡皮鸭子),但是这个解决方案却彻 底破坏了重用,它带来了另一个维护的噩梦!而且还有一个问题我们前面没有提到,难道所有的 鸭子的飞行方式、叫声等行为都是一模一样的吗?不可能吧!说到这里,为了能帮助

8、Joe摆脱困境,我们有必要先停下来,重新回顾一些面向对象设计 原则。请您告诉我:“什么东西是在软件开发过程中是恒定不变的? ,您想到了吗?对,那就 是变化本身,正所谓“计划没有变化快”,所以直面“变化这个事实”才是正道! Joe面对的问 题是,鸭子的行为在子类里持续不断地改变,所以让所有的子类都拥有基类的行为是不适当的, 而使用上面的接口的方式,又破坏了代码重用。现在就需要用到我们的第一个设计原则:Identify the aspects of your app1.ication that vary and separate them from what stays the Same.(找到系

9、统中变化的部分,将变化的部分同其它稳定的部分隔 开。)换句话说就是:“找到变化并且把它封装起来,稍后你就可以在不影响其它部分的情况下 修改或扩展被封装的变化部分。”尽管这个概念很简单,但是它几乎是所有设计模式的基础, 所有模式都提供了使系统里变化的部分独立于其它部分的方法。OK!现在我们已经有了一条设计原则,那么Joe的问题怎么办呢?就鸭子的问题来说,变 化的部分就是子类里的行为。所以我们要把这部分行为封装起来,省得它们老惹麻烦!从目前的 情况看,就是f1.y()和quack()行为总是不老实,而SWim()行为是很稳定的,这个行为是可以使 用继承来实现代码重用的,所以,我们需要做的就是把f1

10、.y()和quack()行为从DUCk基类里隔离 出来。我们需要创建两组不同的行为,一组表示f1.y ()行为,一组表示quack()行为。为什么是 两组而不是两个呢?因为对于不同的子类来说,f1.y()和quack()的表现形式都是不一样的,有 的鸭子嘎嘎叫,有的却呷叫I叫。有了这两组行为,我们就可以组合出不同的鸭子,例如:我们可 能想要实例化一个新的MaI1.ardDUCk(野鸭)实例,并且给它初始化一个特殊类型的飞行行为(野 鸭飞行能力比较强)。那么,如果我们可以这样,更进一步,为什么我们不可以动态地改变一个 鸭子的行为呢?换句话说,我们将在DUCk类里包含行为设置方法,所以我们可以说在

11、运行时改 变Ma1.IardDUCk的飞行行为,这听起来更酷更灵活了!那么我们到底要怎么做呢?回答这个问题, 先要看一下我们的第二个设计原则:Program to an interface, not an imp1.ementation.(面向接口编程,而不要 面向实现编程。)嘿!对于这个原则,不论是耳朵还是眼睛,是不是都太熟悉了! “接口”这个词已经被赋 予太多的含义,搞的大家一说点儿屁事就满嘴往外蹦“接口”。那么它到底是什么意思呢?我们 这里说的接口是一个抽象的概念,不局限于语言层面的接口(例如C#里的interface)。一个接口 也可以是一个抽象类,或者一个基类也可以看作是一种接口的表

12、现形式,因为基类变量可以用来 引用其子类。要点在于,我们在面向接口编程的时候,可以使用多态,那么实际运行的代码只依 赖于具体的接口(interface,抽象类,基类),而不管这些接口提供的功能是如何实现的,也就是 说,接口将系统的不同部分隔离开来,同时又将它们连接在一起C我的神啊!接口真是太伟大了! (烂西红柿和臭鸡蛋从四面八方飞来)OK!这回该彻底解决Joe的问题了!根据面向接口编程的设计原则,我们应该用接口来隔离鸭子问题中变化的部分,也就是鸭 子的不稳定的行为(f1.y0、quack()。我们要用一个FIyBehaVior接口表示鸭子的飞行行为,这 个接口可以有多种不同的实现方式,可以“横

13、”着分,也可以“竖”着分,管它呢!这样做的好 处就是我们将鸭子的行为实现在一组独立的类里,具体的鸭子是通过F1.yBehavior这个接口来调 用这个行为的,因为DUCk只依赖FIyBehaVior接口,所以不需要管FIyBehaVior是如何被实现 的。如下面的类图,FIyBehaVior和QUaCkBehaViOr接口都有不同的实现方式!QuackBehavior8ack ()灯A女模拟鸭子图4JOe已经晕了,“你说了这么多,全是大白话,来点代码行不行,我要C#的! o说到这 里,我们也该开始彻底改造这个设计了,并会在最后附加部分代码来帮助大家理解。第一步:我们要给Duck类增加两个接口类

14、型的实例变量,分别是f1.yBehavior和 quackBehavior,它们其实就是新的设计里的“飞行”和“叫唤”行为。每个鸭子对象都将会使 用各种方式来设置这些变量,以引用它们期望的运行时的特殊行为类型(使用横着飞,吱吱叫, 等等)。第二步:我们还要把f1.y ()和quack()方法从DUCk类里移除,因为我们已经把这些行为移 到F1.yBehavior和QuackBehavior接口里了。我们将使用两个相似的PerfOrmFIy ()和 PerformQuack ()方法来替换f 1 y 0和qucak ()方法,后面你会看到这两个新方法是如何起作用的。第三步:我们要考虑什么时候初始

15、化f1.yBehavior和QuackBehavior变量。最简单的办法 就是在DUCk类初始化的时候同时初始化他们。但是我们这里还有更好的办法,就是提供两个可 以动态设置变量值的方法SetFIyBehaVioro和SetQuackBehavior (),那么就可以在运行时动态 改变鸭子的行为了。下面是修改后的Duck类图:DUCkf1.yBehavior : F1.yBehaviorquackBehavi or : QuackBehaviorswim ()PerEormF1.y0Per EormQuack ()SetF1.yBehavi or ()SetQuackBehavi or ()vi

16、rtua1.disp1.ay。模拟鸭子图5我们再看看整个设计修改后的类图:模拟鸭子映最后大家再看看演示代码,因为代码比较多,就不贴出来了,大家可以下载后参考:下面是演示代码的执行结果:fi1.G:/设计稹式/喊计模式随笔系列/DudbiBWg我亮一只野鸭! M我飞啦!北呷呷口我是一只红头鸭! 我飞啦! #嘎嘎我是一个橡皮鸭子!功不会飞!林吱吱这就是策略模式前面说了那么多,现在终于到了正式介绍我们今天的主角的时候啦!此刻心情真是好激动 啊!其实我们在前面就是使用Strategy模式帮Joe度过了难过,真不知道他发了奖金后要怎么 感谢我们啊。OK!下面先看看官方的定义:The Strategy P

17、attern defines a fami1.y of a1.gorithms, encapsu1.ates each one, and makes them interchangeab1.e. Strategy 1.ets the a1.gorithm vary independent1.y from c1.ients that use it.(策略模式定义了一系列的算法,并将每 一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变 化。)怎么样,有了前面Joe的经历,这个定义理解起来还不那么太费劲吧?我想凡是认真看到 这里的人,应该都能理解的。那么下面再画蛇

18、添足地罗嗦几句,给那些还不太理解的朋友一个机 会吧。St 图COnteXt(应用场景): 需要使用ConcreteStrategy提供的算法。 内部维护一个Strategy的实例。 负责动态设置运行时Strategy具体的实现算法。 负责跟Strategy之间的交互和数据传递。Strategy (抽象策略类): 定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,COnteXt使用这个接口调用不同的算法,一般使用接口或抽象类实现。ConcreteStrategy (具体策略类): 实现了 Stratogy定义的接口,提供具体的算法实现。还不理解? !我的神啊!那再看看下面的顺序图吧,这

19、是最后的机会啦!:C1.ient:CCInCreteStrateRvA1 :初始化 Stratmgy2:初始化具件实现为策略(算法)A3:这里也可以指定甚他具体普现资略(算法)4:5:6:调用策略(算法)n7 :通过Straty调用具体实现8:调用到具体笑财9:10:Strategy顺序图应用场景和优缺点上面我们已经看过了 Strategy模式的详细介绍,下面我们再来简单说说这个模式的优缺点 吧!怎么说呢,人无完人,设计模式也不是万能的,每一个模式都有它的使命,也就是说只有在 特定的场景下才能发挥其功效。我们要使用好模式,就必须熟知各个模式的应用场景。对于Strat2gy模式来说,主要有这些应

20、用场景:1、多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要 执行的行为。(例如 F1.yBehavior 和 QuackBehavior)2、需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。(例如F1.yBehavior和QuackBehavior的具体实现可任意变化或扩充)3、对客户(DUCk)隐藏具体策略(算法)的实现细节,彼此完全独立。对于Strategy模式来说,主要有如下优点:1、提供了一种替代继承的方法,而且既保持了继承的优点(代码重用)还比继承更灵活 (算法独立,可以任意扩展)。2、避免程序中使用多重条件转移语句,使

21、系统更灵活,并易于扩展。3、遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。对于Strategy模式来说,主要有如下缺点:1、因为每个具体策略类都会产生一个新类,所以会增加系统需要维护的类的数量。备注:关于场景和优缺点,上面肯定说得不够全面,欢迎大家来补充。.NET框架里的应用Strategy模式的应用非常广泛,也许大家有意无意之间一直都在使用。这里举一个.NET框 架里使用Strategy模式的例子,象这样的例子其实还有很多,只要大家细心体会就一定会发现 的。如果写过程序,那么Array1.ist类肯定都会用过吧,那么它的Sort方法想必大家也一定不 陌生了。SOrt方法的定义如下:

22、pub1.ic virtua1. void Sort (!Comparer comparer)可以看到Sort方法接收一个ICOmParer类型的参数,那么这个IComParer接口是做什么 用的呢?下面我们看一段程序,下面的代码示例演示如何使用默认比较器和一个反转排序顺序的 自定义比较器,对Array1.iSt中的值进行排序。(完全引自MSDN:ms-he1.p:/MS. MSDNQTR. v80. chsMS. MSDN. v80MS. NETDEVFX. v20. chs/cpref2/htm1./M_System_C o1.1.ections_Array1.ist_Sort_1._a2

23、d90598. htm)1 using System;2 using System.Co1.1.ections;4 pub1.ic c1.ass SampIesArray1.ist 56E pub1.ic c1.ass myReverserC1.ass : !Comparer 78 I / Ca1.1.s Case1.nsensitiveComparer-Compare with the parameters reversed.9 int IComparer.Compare( Object x, Object y) 10 I return( (new Case1.nsensitiveCompa

24、rer().Compare( y, x );111)12 I13 b )14 I15 pub1.ic static void Main() 16 I17 I / Creates and initia1.izes a new Array1.ist.18 i Array1.ist myA1. = new Array1.ist();19 ImyA1.Add( The,);20 ImyA1.Add( quick);21 ImyA1.Add( brown);22 ImyA1.Add( fox);23 ImyA1.Add( jumps);24 ImyA1.Add( over);25 ImyA1.Add(

25、the);26 ImyA1.Add( 1.azy);27 ImyA1.Add( dog,);28 I29 I / Disp1.ays the va1.ues of the Array1.ist.30 I Conso1.e.Write1.ine( The Array1.ist initia1.1.y contains the fo1.1.owing va1.ues:);31 I Print1.ndexAndVa1.ues( myA1.);32 I33 I / Sorts the va1.ues of the Array1.ist using the defau1.t comparer.34 Im

26、yA1.Sort();35 I Conso1.e.Write1.ine( After sorting with the defau1.t comparer:);36 IPrint1.ndexAndVa1.ues( myA1. );37 I38 I / Sorts the va1.ues of the Array1.ist using the reverse case-insensitive comparer.39 I !Comparer myComparer = new myReverserCIassO;40 I myA1.Sort( myComparer);41 I Conso1.e.Wri

27、te1.ine( After sorting with the reverse case-insensitive comparer:);42 I Print1.ndexAndVa1.ues( myA1.);43 I44 I- 45 I46广. pub1.ic static void Print1.ndexAndVa1.ues( !Enumerab1.e my 1.ist) 47 I inti = 0;48 I foreach ( Object obj in my1.ist)49 I Conso1.e.Write1.ine( t0t1, i+1 obj);50 IConso1.e.Write1.

28、ine();51卜52 I54 5556EE*57 I This code produces the fo1.1.owing output.58 I The Array1.ist initia1.1.y contains the fo1.1.owing va1.ues:59 I0:The60 I1:quick61 I2:brown62 I3:fox63 I4:jumps64 I5:over65 I6:the66 I7:1.azy67 I8:dog68 I69 I After sorting with the defau1.t comparer:70 I0:brown71 I1:dog72 I2

29、:fox73 I3:jumps74 I4:1.azy75 I5:over76 I6:quick77 I7:the78 I8:The79 I80 I After sorting with the reverse case-insensitive comparer:81 I0:the82 I1:The83 I2:quick84 I3:over85 I4:1.azy86 I5: jumps87 I6:fox88 I7:dog89 I8: brown90 1.*/怎么样,大家看出来了吧,其实在这段代码里,Array1.iSt相当于Stra1.egy模式中的 ConteXt (应用场景)部分,而ICOm

30、Parer相当于Strategy (抽象策略类)部分,myReverserC1.ass 相当于COnCreteStrategy (具体策略类)部分。我们这里抛开HiyReverserC1.ass类的Compare方法 如何具体实现不谈,我们只要知道这是一个具体策略类,它提供了应用场景需要的具体算法,它 实现了抽象策略类接口,而应用场景通过抽象策略类动态调用到了具体策略类中的算法。哈!所 以这是一个十分典型的Strategy模式的应用。基于这个符合Strategy模式的结构,我们还可以提供很多种自定义的具体策略类的实现, 只要这些类实现了 ICon1.Parer接口,就可以在运行时动态设置给Ar

31、ray1.iSt类的SOrt方法,在 Sort方法中会根据具体策略类实现的比较算法规则来对Array1.iSt中的数据进行排序。最后一个设计原则关于Strategy模式的故事讲到这里,应该基本OK啦!下面我们再聊些更高层次的东西。什么是更高层次的东西?嘿!当然是设计原则了!在前面总结Strategy模式的优点的时候我们 提到过,Strategy模式不仅保留了继承的优点,而且还提供了更灵活的扩展能力。为什么会这 样呢? Strategy模式是怎么做到这一点的呢?哈!这是因为它“上面有人”啊!谁啊?它就是 我们下面要介绍的重量级设计原则:Favor composition over inherit

32、ance.(优先使用对象组合,而非类继承)关于组合和继承,我们只要这样来理解即可:组合是一种“HAS-A”关系,而继承是一种“IS-A” 关系。很明显HAS-A”要比“IS-A”更灵活一些。也就是说在创建系统的时候,我们应该优先 使用对象组合,因为它不仅可以给你提供更多灵活性和扩展性,而且还使你可以在运行时改变行 为(组合不同的对象),这简直是酷毙了 !但是也不是说继承就是不能用,只是说应该把继承应用 在相对更稳定,几乎没有变化的地方,例如前面的DUCk类里的SWim()方法,因为可以肯定所有 鸭子一定都会游泳,所以就没有必要给这个行为提供基于Strategy模式的实现方式,因为那样 做除了是

33、程序更复杂以外,没有什么意义。BU1.1.ET POINTS Knowing the 00 basics does not make you a good 00 designer. Good 00 designs are reusab1.ezextensib1.e and maintainab1.e. Patterns show you how to bui1.d systems with good 00 design qua1.ities. Patterns are proven object oriented experience. Patterns don,t give you code

34、zthey give you genera1. so1.utions to design prob1.ems.You app1.y them to your specific app1.ication. Patterns arent inventedzthey are discovered. Most patterns and princip1.es address issues of change in software. Most patterns a1.1.ow some part of a system to vary independent1.y of a1.1. other parts. We often try to take what varies in a system andencapsu1.ate it.Patterns provide a shared 1.anguage that can maximize the va1.ue of your communication with other deve1.opers.

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 生活休闲 > 在线阅读


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号