Justin's Tech Blog

ASP.NET,JavaScript,Ajax,CSS,Debugging,Performance
随笔 - 111, 文章 - 0, 评论 - 2140, 引用 - 55
数据加载中……

设计模式随笔系列:鸭子-策略模式(Strategy)[原]

鸭子-策略模式(Strategy

前言

万事开头难,最近对这句话体会深刻!这篇文章是这个系列正式开始介绍设计模式的第一篇,所以肩负着确定这个系列风格的历史重任,它在我脑袋里默默地酝酿了好多天,却只搜刮出了一点儿不太清晰的轮廓,可是时间不等人,以后再多“迭代”几次吧!在前面的随笔里,我已经提到了,这个系列准备以《Head First Design Patterns》的结构为主线,所以每个模式的核心故事都是取材于此书,在此再次声明一下。不管怎样,宗旨是为了跟大家一起循序渐进地去认识设计模式。

上一篇:模式和原则,得到很多朋友的支持和鼓励,这里再次深表感谢。这里我还是想呼吁一下,希望大家看过后多提宝贵意见,反对意见更好,关键是我们在互动中可以共同进步,因为经验告诉我讨论(争论更甚)出来的火花,总是印象最深刻的。

其实策略模式是一个很简单的模式,也是一个很常用的模式,可谓短小精悍。我在介绍这个模式的同时,为了加深大家对OO的理解,还会反复强调前面讲过的设计原则和GRASP模式。这个系列的文章前后多少会有一些关联的连续性,但是单独一篇文章针对单一模式也一定是独立的,所以不论大家想从前往后连续看也好,还是挑喜欢的跳着看,都没有问题。

“罗嗦了这么多,太唐僧了吧,快点开始吧(烂西红柿和臭鸡蛋从四面八方飞来)

模拟鸭子

Joe是一名OO程序员,他为一家开发模拟鸭子池塘游戏的公司工作,该公司的主要产品是一种可以模拟展示多种会游泳和呷呷叫的鸭子的游戏。这个游戏是使用标准的面向对象技术开发的,系统里所有鸭子都继承于Duck基类,系统的核心类图如下:

如图所示,在Duck基类里实现了公共的quack()swim()方法,而MallardDuckRedheadDuck可以分别覆盖实现自己的display()方法,这样即重用了公共的部分,又支持不同子类的个性化扩展。从目前的情况看,这是一个很好的设计,哈!  

但是,商场如战场,不进则退。Joe的公司最近的日子不好过,盗版泛滥,再加上竞争对手的围追堵劫,已经拖欠好几个月工资了。因此,公司高层在一次集体“腐败”后,决定一定要给系统增加一些超玄的功能,以彻底击垮竞争对手。经过董事会讨论,最终觉得如果能让鸭子飞起来,那么一定可以给对手致命一击。于是Joe的上司对董事们拍着胸脯说:“这没有问题,Joe是一个OO程序员,这对他来说太简单了!我们保证一周内结束战斗。”

接到任务的Joe丝毫不敢怠慢,研究了上级的指示以后,发现只要在Duck里增加一个fly()方法就可以搞定了,这样所有继承Duck的鸭子就都拥有了会飞的能力,哈!这回奖金有盼头啦!改进后的系统类图如下:


    Joe的上司很高兴,带着新产品给董事们演示去了……  

……

Joe的上司:“我正在给董事们演示你会飞的鸭子,但是怎么有很多橡皮鸭子也在四处乱飞呢?你在耍我吗?你还想不想混啦?!”(此处省略粗话100)

Joe被吓坏了,到手的奖金泡汤了!冷静下来的Joe发现,原来在Duck类里增加的方法,也同样被继承于DuckRubberDuck类继承了,所以就有了会飞的橡皮鸭子,这是严重违反该系统“真实模拟各种鸭子”的原则的!那么该怎么办呢?Joe很郁闷!他突然想到:如果在RubberDuck类里把fly()方法重写一下会如何?在RubberDuck类的fly()里让橡皮鸭子什么都不做,不就一切OK了吗!那以后再增加一个木头鸭子呢?它不会飞也不会叫,那不是要再重写quack()fly()方法,以后再增加其它特殊的鸭子都要这样,这不是太麻烦了,而且也很混乱。

最终,Joe认识到使用继承不是办法,因为他的上司通知他,董事会决定以后每6个月就会升级一次系统,以应对市场竞争,所以未来的变化会很频繁,而且还不可预知。如果以后靠逐个类去判断是否重写了quack()fly()方法来应对变化,显然混不下去!

Joe这时很迷惑,为什么屡试不爽的继承,在系统维护升级的时候,无法很好地支持重用呢?)

那么使用接口怎么样?我可以把fly()方法放在接口里,只有那些会飞的鸭子才需要实现这个接口,最好把quack()方法也拿出来放到一个接口里,因为有些鸭子是不会叫的。就像下面这样:



Joe的上司知道后怒了:“你这样做难道是希望所有需要quack()fly()方法的鸭子都去重复实现这两个方法的功能吗?就这么几个鸭子还好说,但是我们有几十、上百个鸭子的时候你怎么办?如果某个方法要做一点修改,难道你要重复修改上百遍吗?你是不是疯啦?”

呵呵!如果你是Joe,你该怎么办?

我们知道,并不是所有的鸭子都会飞、会叫,所以继承不是正确的方法。但是虽然上面的使用Flyable接口的方法,可以解决部分问题(不再有会飞的橡皮鸭子),但是这个解决方案却彻底破坏了重用,它带来了另一个维护的噩梦!而且还有一个问题我们前面没有提到,难道所有的鸭子的飞行方式、叫声等行为都是一模一样的吗?不可能吧!

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

Identify the aspects of your application that vary and separate them from what stays the same.(找到系统中变化的部分,将变化的部分同其它稳定的部分隔开。)

换句话说就是:“找到变化并且把它封装起来,稍后你就可以在不影响其它部分的情况下修改或扩展被封装的变化部分。” 尽管这个概念很简单,但是它几乎是所有设计模式的基础,所有模式都提供了使系统里变化的部分独立于其它部分的方法。

OK!现在我们已经有了一条设计原则,那么Joe的问题怎么办呢?就鸭子的问题来说,变化的部分就是子类里的行为。所以我们要把这部分行为封装起来,省得它们老惹麻烦!从目前的情况看,就是fly()quack()行为总是不老实,而swim()行为是很稳定的,这个行为是可以使用继承来实现代码重用的,所以,我们需要做的就是把fly()quack()行为从Duck基类里隔离出来。我们需要创建两组不同的行为,一组表示fly()行为,一组表示quack()行为。为什么是两组而不是两个呢?因为对于不同的子类来说,fly()quack()的表现形式都是不一样的,有的鸭子嘎嘎叫,有的却呷呷叫。有了这两组行为,我们就可以组合出不同的鸭子,例如:我们可能想要实例化一个新的MallardDuck(野鸭)实例,并且给它初始化一个特殊类型的飞行行为(野鸭飞行能力比较强)。那么,如果我们可以这样,更进一步,为什么我们不可以动态地改变一个鸭子的行为呢?换句话说,我们将在Duck类里包含行为设置方法,所以我们可以说在运行时改变MallardDuck的飞行行为,这听起来更酷更灵活了!那么我们到底要怎么做呢?回答这个问题,先要看一下我们的第二个设计原则:

Program to an interface, not an implementation.(面向接口编程,而不要面向实现编程。)

嘿!对于这个原则,不论是耳朵还是眼睛,是不是都太熟悉了!“接口”这个词已经被赋予太多的含义,搞的大家一说点儿屁事就满嘴往外蹦“接口”。那么它到底是什么意思呢?我们这里说的接口是一个抽象的概念,不局限于语言层面的接口(例如C#里的interface)。一个接口也可以是一个抽象类,或者一个基类也可以看作是一种接口的表现形式,因为基类变量可以用来引用其子类。要点在于,我们在面向接口编程的时候,可以使用多态,那么实际运行的代码只依赖于具体的接口(interface,抽象类,基类),而不管这些接口提供的功能是如何实现的,也就是说,接口将系统的不同部分隔离开来,同时又将它们连接在一起。我的神啊!接口真是太伟大了!(烂西红柿和臭鸡蛋从四面八方飞来)

OK!这回该彻底解决Joe的问题了!

根据面向接口编程的设计原则,我们应该用接口来隔离鸭子问题中变化的部分,也就是鸭子的不稳定的行为(fly()quack())。我们要用一个FlyBehavior接口表示鸭子的飞行行为,这个接口可以有多种不同的实现方式,可以“横”着分,也可以“竖”着分,管它呢!这样做的好处就是我们将鸭子的行为实现在一组独立的类里,具体的鸭子是通过FlyBehavior这个接口来调用这个行为的,因为Duck只依赖FlyBehavior接口,所以不需要管FlyBehavior是如何被实现的。如下面的类图,FlyBehaviorQuackBehavior接口都有不同的实现方式!

Joe已经晕了,“你说了这么多,全是大白话,来点代码行不行,我要C#的!”。说到这里,我们也该开始彻底改造这个设计了,并会在最后附加部分代码来帮助大家理解。  

第一步:我们要给Duck类增加两个接口类型的实例变量,分别是flyBehaviorquackBehavior,它们其实就是新的设计里的“飞行”和“叫唤”行为。每个鸭子对象都将会使用各种方式来设置这些变量,以引用它们期望的运行时的特殊行为类型(使用横着飞,吱吱叫,等等)

第二步:我们还要把fly()quack()方法从Duck类里移除,因为我们已经把这些行为移到FlyBehaviorQuackBehavior接口里了。我们将使用两个相似的PerformFly()PerformQuack()方法来替换fly()qucak()方法,后面你会看到这两个新方法是如何起作用的。

第三步:我们要考虑什么时候初始化flyBehaviorquackBehavior变量。最简单的办法就是在Duck类初始化的时候同时初始化他们。但是我们这里还有更好的办法,就是提供两个可以动态设置变量值的方法SetFlyBehavior()SetQuackBehavior(),那么就可以在运行时动态改变鸭子的行为了。

下面是修改后的Duck类图:

我们再看看整个设计修改后的类图:


最后大家再看看演示代码,因为代码比较多,就不贴出来了,大家可以下载后参考:。下面是演示代码的执行结果:


这就是策略模式

前面说了那么多,现在终于到了正式介绍我们今天的主角的时候啦!此刻心情真是好激动啊!其实我们在前面就是使用Strategy模式帮Joe度过了难过,真不知道他发了奖金后要怎么感谢我们啊。OK!下面先看看官方的定义:

The Strategy Pattern defines a family of algorithms,encapsulates each one,and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.(策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。)

怎么样,有了前面Joe的经历,这个定义理解起来还不那么太费劲吧?我想凡是认真看到这里的人,应该都能理解的。那么下面再画蛇添足地罗嗦几句,给那些还不太理解的朋友一个机会吧。J

Context(应用场景):

l         需要使用ConcreteStrategy提供的算法。

l         内部维护一个Strategy的实例。

l         负责动态设置运行时Strategy具体的实现算法。

l         负责跟Strategy之间的交互和数据传递。

Strategy(抽象策略类)

l         定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,Context使用这个接口调用不同的算法,一般使用接口或抽象类实现。

ConcreteStrategy(具体策略类)

l         实现了Strategy定义的接口,提供具体的算法实现。

 

还不理解?!我的神啊!那再看看下面的顺序图吧,这是最后的机会啦!


应用场景和优缺点

上面我们已经看过了Strategy模式的详细介绍,下面我们再来简单说说这个模式的优缺点吧!怎么说呢,人无完人,设计模式也不是万能的,每一个模式都有它的使命,也就是说只有在特定的场景下才能发挥其功效。我们要使用好模式,就必须熟知各个模式的应用场景。

对于Strategy模式来说,主要有这些应用场景:

1、  多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。(例如FlyBehaviorQuackBehavior)

2、  需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。(例如FlyBehaviorQuackBehavior的具体实现可任意变化或扩充)

3、  对客户(Duck)隐藏具体策略(算法)的实现细节,彼此完全独立。

 

对于Strategy模式来说,主要有如下优点:

1、  提供了一种替代继承的方法,而且既保持了继承的优点(代码重用)还比继承更灵活(算法独立,可以任意扩展)

2、  避免程序中使用多重条件转移语句,使系统更灵活,并易于扩展。

3、  遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。

对于Strategy模式来说,主要有如下缺点:

1、  因为每个具体策略类都会产生一个新类,所以会增加系统需要维护的类的数量。

 

    备注:关于场景和优缺点,上面肯定说得不够全面,欢迎大家来补充。

.NET框架里的应用

Strategy模式的应用非常广泛,也许大家有意无意之间一直都在使用。这里举一个.NET框架里使用Strategy模式的例子,象这样的例子其实还有很多,只要大家细心体会就一定会发现的。

如果写过程序,那么ArrayList类肯定都会用过吧,那么它的Sort方法想必大家也一定不陌生了。Sort方法的定义如下:

public virtual void Sort (IComparer comparer)

可以看到Sort方法接收一个IComparer类型的参数,那么这个IComparer接口是做什么用的呢?下面我们看一段程序,下面的代码示例演示如何使用默认比较器和一个反转排序顺序的自定义比较器,对 ArrayList 中的值进行排序。(完全引自MSDNms-help://MS.MSDNQTR.v80.chs/MS.MSDN.v80/MS.NETDEVFX.v20.chs/cpref2/html/M_System_Collections_ArrayList_Sort_1_a2d90598.htm)

 

 1using System;
 2using System.Collections;
 3
 4public class SamplesArrayList  {
 5 
 6   public class myReverserClass : IComparer  {
 7
 8      // Calls CaseInsensitiveComparer.Compare with the parameters reversed.
 9      int IComparer.Compare( Object x, Object y )  {
10          return( (new CaseInsensitiveComparer()).Compare( y, x ) );
11      }

12
13   }

14
15   public static void Main()  {
16 
17      // Creates and initializes a new ArrayList.
18      ArrayList myAL = new ArrayList();
19      myAL.Add( "The" );
20      myAL.Add( "quick" );
21      myAL.Add( "brown" );
22      myAL.Add( "fox" );
23      myAL.Add( "jumps" );
24      myAL.Add( "over" );
25      myAL.Add( "the" );
26      myAL.Add( "lazy" );
27      myAL.Add( "dog" );
28 
29      // Displays the values of the ArrayList.
30      Console.WriteLine( "The ArrayList initially contains the following values:" );
31      PrintIndexAndValues( myAL );
32 
33      // Sorts the values of the ArrayList using the default comparer.
34      myAL.Sort();
35      Console.WriteLine( "After sorting with the default comparer:" );
36      PrintIndexAndValues( myAL );
37
38      // Sorts the values of the ArrayList using the reverse case-insensitive comparer.
39      IComparer myComparer = new myReverserClass();
40      myAL.Sort( myComparer );
41      Console.WriteLine( "After sorting with the reverse case-insensitive comparer:" );
42      PrintIndexAndValues( myAL );
43
44   }

45 
46   public static void PrintIndexAndValues( IEnumerable myList )  {
47      int i = 0;
48      foreach ( Object obj in myList )
49         Console.WriteLine( "\t[{0}]:\t{1}", i++, obj );
50      Console.WriteLine();
51   }

52
53}

54
55
56/* 
57This code produces the following output.
58The ArrayList initially contains the following values:
59        [0]:    The
60        [1]:    quick
61        [2]:    brown
62        [3]:    fox
63        [4]:    jumps
64        [5]:    over
65        [6]:    the
66        [7]:    lazy
67        [8]:    dog
68
69After sorting with the default comparer:
70        [0]:    brown
71        [1]:    dog
72        [2]:    fox
73        [3]:    jumps
74        [4]:    lazy
75        [5]:    over
76        [6]:    quick
77        [7]:    the
78        [8]:    The
79
80After sorting with the reverse case-insensitive comparer:
81        [0]:    the
82        [1]:    The
83        [2]:    quick
84        [3]:    over
85        [4]:    lazy
86        [5]:    jumps
87        [6]:    fox
88        [7]:    dog
89        [8]:    brown 
90*/

 

怎么样,大家看出来了吧,其实在这段代码里,ArrayList相当于Strategy模式中的Context(应用场景)部分,而IComparer相当于Strategy(抽象策略类)部分,myReverserClass相当于ConcreteStrategy(具体策略类)部分。我们这里抛开myReverserClass类的Compare方法如何具体实现不谈,我们只要知道这是一个具体策略类,它提供了应用场景需要的具体算法,它实现了抽象策略类接口,而应用场景通过抽象策略类动态调用到了具体策略类中的算法。哈!所以这是一个十分典型的Strategy模式的应用。

基于这个符合Strategy模式的结构,我们还可以提供很多种自定义的具体策略类的实现,只要这些类实现了IComparer接口,就可以在运行时动态设置给ArrayList类的Sort方法,在Sort方法中会根据具体策略类实现的比较算法规则来对ArrayList中的数据进行排序。

最后一个设计原则

关于Strategy模式的故事讲到这里,应该基本OK啦!下面我们再聊些更高层次的东西。什么是更高层次的东西?嘿!当然是设计原则了!在前面总结Strategy模式的优点的时候我们提到过,Strategy模式不仅保留了继承的优点,而且还提供了更灵活的扩展能力。为什么会这样呢?Strategy模式是怎么做到这一点的呢?哈!这是因为它“上面有人”啊!谁啊?它就是我们下面要介绍的重量级设计原则:

Favor composition over inheritance.(优先使用对象组合,而非类继承)

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

BULLET POINTS

l        Knowing the OO basics does not make you a good OO designer.

l        Good OO designs are reusable,extensible and maintainable.

l        Patterns show you how to build systems with good OO design qualities.

l        Patterns are proven object oriented experience.

l        Patterns don’t give you code,they give you general solutions to design problems.You apply them to your specific application.

l        Patterns aren’t invented,they are discovered.

l        Most patterns and principles address issues of change in software.

l        Most patterns allow some part of a system to vary independently of all other parts.

l        We often try to take what varies in a system and encapsulate it.

l        Patterns provide a shared language that can maximize the value of your communication with other developers.

作者

王晓亮/Justin

MSN:xiaoliang203@hotmail.com

Mail:xiaoliang.justin@gmail.com

参考资料

UML和模式应用》

《敏捷软件开发—原则、模式与实践》

Head First Design Patterns

李建忠老师的《C#面向对象设计模式纵横谈系列课程》

作者:Justin
出处:http://justinw.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
(快捷留言:点击下面链接后会直接提交留言信息。)
快捷留言(注:点击链接后会直接提交相应留言信息!) 我要推荐!  我要顶!  太棒了!  期待下一篇  路过 
1
0
(请您对文章做出评价)
« 上一篇:几则经典的故事[转]
» 下一篇:设计模式随笔系列:气象站的故事-观察者模式(Observer)[原]

posted on 2007-02-06 00:42 Justin 阅读(26195) 评论(119)  编辑 收藏 网摘 所属分类: OOAD&UML, Design Patterns

评论

#18楼   回复  引用    

这是我见到过的最好的诠释。
2007-02-07 09:38 | Zane[未注册用户]

#19楼[楼主]   回复  引用  查看    

@FantasySoft
十分感谢,有机会多去你那学学IronPython。
2007-02-07 09:39 | Justin      

#20楼   回复  引用  查看    

@Justin

您太客气了。我得谢谢您才对,因为是您的文章让我去思考了。 :)

欢迎加入到学习IronPython、Python的行列。
2007-02-07 09:47 | FantasySoft      

#21楼   回复  引用  查看    

@Justin
噢,还有一点,您提供下载的代码最好压缩成通用的zip格式。读者就不需要使用付费软件WinRAR来打开了。
2007-02-07 09:49 | FantasySoft      

#22楼[楼主]   回复  引用  查看    

@FantasySoft
我还一直没太注意这个问题呢,谢谢提醒!以后一定都采用zip格式。
2007-02-07 10:12 | Justin      

#23楼   回复  引用    

LZ,肯定喜欢看武林外传。
哈哈,写的不错
2007-02-07 10:15 | endisoft[未注册用户]

#24楼   回复  引用  查看    

又是一篇巨作,收藏回家慢慢读~
觉得您在文章的最后反过头来通过设计原则来解析设计模式非常的好,让我对OOD有了更深入的认识,希望接下来的文章都能保持这个优点。
2007-02-07 10:31 | shenfx      

#25楼   回复  引用    

写的相当好,策略模式差不多就是把变化的部分设计成接口,具体实现放在类里面,最好在场景里面应用适配器模式封装策略,
2007-02-07 13:45 | 蓝狗[匿名][未注册用户]

#26楼   回复  引用    

@ChuPaChuPs


CodeProject里面一篇讲策略模式的文章也是拿鸭子做样例的
2007-02-07 13:47 | 蓝狗[匿名][未注册用户]

#27楼   回复  引用  查看    

写的好呀,模式就是要结合实际讲,不然如何了解它的用处.
2007-02-07 16:35 | 轻剑傲风      

#28楼   回复  引用    

Justin你是设计师还是分析师???别说你是程序员啊!!!
2007-02-07 17:41 | zhanyu[未注册用户]

#29楼[楼主]   回复  引用  查看    

@zhanyu
我们都是程序员
2007-02-07 18:13 | Justin      

#30楼   回复  引用  查看    

文章写的不错,支持一下!

不过,你上面提到的那个joe的困惑,我本人认为用装饰模式也是一种不错的解决方案,本人认为装饰模式可以很好的解决动态的加载和去除某一种行为,从而更具有灵活性,带来的代码的工作量也非常少,功能扩展也很方便。

期待lz的更好的文章。
2007-02-08 14:03 | today      

#31楼[楼主]   回复  引用  查看    

@today
十分感谢你的建议!
在介绍装饰模式的文章里,我会再提到Joe的问题,并对两个模式的应用场景和使用目的做一些对比~
2007-02-08 17:33 | Justin      

#32楼   回复  引用  查看    

又仔细读了一遍您的文章,感觉“Favor composition over inheritance.”这个原则的最佳实践者应当是Bridge模式吧?
---------------------------------------------
对于这个例子来讲,最终修改后的类图应当是Strategy与Bridge模式的结合,Stragey本身的应用仅在于FlyBehavior接口对于不同飞行方式的抽象(对于变化的封装),而Duck类对于两个接口的聚合则是Bridge模式的体现。
2007-02-08 23:02 | shenfx      

#33楼[楼主]   回复  引用  查看    

@shenfx
十分感谢,这是个好问题,鸭子的问题,再最后是会被重构的,会在这个问题里应用更多的模式。
Strategy和Bridge确实很象,所以很容易弄混,但是它们还是有很明显的差别的,这点从它们的定义上就可以看得出来。关于Bridge和Bridge与Strategy的异同,我现在几句话确实很难说清楚,我会在后面的系列文章里逐渐展开这些内容的...
2007-02-08 23:48 | Justin      

#34楼   回复  引用    

这个例子违背了liskov substitution principle
2007-02-09 09:14 | roland[未注册用户]

#35楼   回复  引用  查看    

@Justin
期待您后面的文章~
2007-02-09 09:38 | shenfx      

#36楼   回复  引用    

@Justin
用7z也行啊

英文太差,刚看了第一章,一边看英文的一边看umlchina出的chapter1
又来你这再看了遍。
2007-02-09 17:06 | coos

#37楼[楼主]   回复  引用  查看    

@coos
"7z"是什么意思?

英文差不要紧,坚持看下去,慢慢就好了~
2007-02-10 21:20 | Justin      

#38楼[楼主]   回复  引用  查看    

@roland
LSP:Liskov替换原则,子类型必须能够替换掉它们的基类型。

上面的例子或Strategy那里违背这个原则了呢?

2007-02-10 21:24 | Justin      

#39楼   回复  引用    

希望有更多的用这种方式介绍设计模式的文章,期待ing
2007-02-11 12:59 | max[未注册用户]

#40楼[楼主]   回复  引用  查看    

@max
谢谢支持,我会努力的~
2007-02-11 14:55 | Justin      

#41楼   回复  引用    

@Justin
您的文章,轻松幽默,通俗易懂,看完后让人受益匪浅。
对于您给的鸭子的例子,有一个疑问。
假如按照您的实现方法,不会飞的鸭子的PerformFly()方法也暴露给客户端调用了,这样妥当吗?
2007-02-11 16:34 | 3000+[未注册用户]

#42楼[楼主]   回复  引用  查看    

@3000+
嘿嘿!谢谢支持和问题!
这是一个好问题,看来你也是一个完美主义者,从更符合ISP原则的角度来说,这样做确实有些不妥~但是从抽象的角度来说,Duck是基类,它负责抽象所有子类共性的东西,这个PerformFly()方法负责抽象的东西是飞行的行为,从这个角度来理解,会飞和不会飞的统一就是PerformFly()方法,客户可以任意调用,不过调用后的结果有所不同“会飞的飞了,不会飞的不动而已”。从另一个角度来说,我们这里也并没有严格区分会飞与不会飞这两类鸭子,因为这个矛盾并不尖锐,所以只把“不会飞”的飞行行为也归结为一种飞行行为而已。如果这个矛盾很尖锐,那么我们应该从根本解决这个问题,就是为会飞的鸭子和不会飞的鸭子分别提供不同的基类,当然那时只有会飞的鸭子的基类才会有PerformFly()方法了。

总之,设计的目的不是追求完美,它只是追求在某种特定条件下的一种平衡,够用就好。
2007-02-11 17:26 | Justin      

#43楼   回复  引用  查看    

Head First design patterns的中文翻译
2007-02-11 19:03 | 一醉解千愁      

#44楼[楼主]   回复  引用  查看    

@一醉解千愁
部分是的
2007-02-11 19:17 | Justin      

#45楼   回复  引用  查看    

写的非常精彩啊!
2007-02-12 11:53 | Daniel Pang      

#46楼   回复  引用  查看    

不错,讲的浅显易懂!
2007-02-12 12:39 | 生米煮成稀饭      

#47楼[楼主]   回复  引用  查看    

@生米煮成稀饭
@Daniel Pang

Thanks~
2007-02-12 23:04 | Justin      

#48楼   回复  引用    

@Justin
你上面的解释已经说明了PerformFly方法违背了LSP原则,不会飞的子类是替换不了会飞的父类的[当然这也要看语义上的理解了]。基本同意你的解释
2007-02-13 17:28 | roland[未注册用户]

#49楼[楼主]   回复  引用  查看    

@roland
呵,我还是倾向于认为是没有违反LSP,因为虽然不会飞的鸭子“不会飞”,但是在运行时如果调用了不会飞的鸭子的飞行行为,程序是不会出错的。
2007-02-13 21:25 | Justin      

#50楼   回复  引用    

感觉很好!!!
2007-02-23 14:12 | safkj[未注册用户]

#51楼   回复  引用  查看    

严重期待下文!!
2007-02-27 11:23 | stonezhu      

#52楼[楼主]   回复  引用  查看    

@stonezhu
@safkj
^_^ 严重感谢支持!刚回北京,近期会出下一篇...
2007-02-27 11:53 | Justin      

#53楼   回复  引用  查看    

Justin,你的文章写得很好,文笔更是一流!
祝愿你早日把这个系列写完!
2007-03-22 14:31 | GerryJiang      

#54楼   回复  引用    

哥们,你太强悍了
愿你早日将这个系列写完,实在是等不及想接着看下去啊
2007-03-23 17:28 | 路过[未注册用户]

#55楼   回复  引用    

虽说翻译辛苦吧,却也不能把人家的东西当成是你的原创啊~~~
粗略看了下,你原创的部分估计就是代码部分改java为c#来实现了~~~
2007-04-01 22:23 | xhYuan[未注册用户]

#56楼[楼主]   回复  引用  查看    

@xhYuan
既然你只是“粗略看了下”,那么请你再“仔细看一下”吧!
同时希望你以后在任何时候任何地方做出这种具有侮辱性评论之前,都请“仔细看一下”!

BTW:谢谢支持!
2007-04-02 00:08 | Justin      

#57楼   回复  引用    

学习中,最近也在看这本head first
2007-04-03 23:26 | Blindsniper[未注册用户]

#58楼   回复  引用  查看    

Program to an interface, not an implementation
面向接口编程,而不要面向实现编程。

你称这个为第二个原则
哪前面的这个一篇"开篇-模式和原则"里的原则哪边没有提到

困惑
找到系统中变化的部分,将变化的部分同其它稳定的部分隔开。 这个可理解前面提到的(开刻关闭,受保护的方法)
2007-04-05 14:24 | 风云.NET      

#59楼   回复  引用  查看    

面向接口编程,而不要面向实现编程。)

这个..是哪个依赖倒置的吗? 只是在在文章里第一次出现才称为第一个.

同一个意思两个不同叫法
2007-04-05 14:37 | 风云.NET      

#60楼   回复  引用    

从你的这个例子来看,似乎可以这样理解:

Strategy模式,把本来属于每个子类的方法或者需要实现的接口的方法(如不同的fly和quack)剥离成一个个独立的类,然后通过接口的方式在每个Duck子类的构造函数动态的调入,这样做的最大的好处是低耦合。

你的文章写得很生动,一点不死板,希望尽快可以看到续作!
2007-04-06 16:33 | Stephen[未注册用户]

#61楼   回复  引用    

希望代码贴出来,因为并不一定每个人都能在家里上网(可以放个链接,到另一个地方),msdn有的地方就是这样的。

谢谢,讲的非常好,期待下一篇。
2007-04-10 14:49 | 王德水[未注册用户]

#62楼[楼主]   回复  引用  查看    

@王德水
谢谢,准备采纳你的建议~
2007-05-01 17:19 | Justin      

#63楼   回复  引用  查看    

不错,<Head First Design Patterns>的英文版看过了,再看看你的,巩固巩固,期待后续文章!
2007-05-01 19:41 | Kevin Wan      

#64楼[楼主]   回复  引用  查看    

@周鹏
怎么留言在这里,引的确是观察者模式的文章啊
2007-05-03 07:11 | Justin      

#65楼   回复  引用  查看    

非常之好
2007-05-11 15:27 | 农夫三拳      

#66楼[楼主]   回复  引用  查看    

@农夫三拳
谢谢,下一篇也不错啊~ :-)
2007-05-11 15:45 | Justin      

#67楼   回复  引用  查看    

您在啊,我正在看呢, 写的真好啊,后面没了。。可惜啊
2007-05-11 16:24 | 农夫三拳      

#68楼   回复  引用  查看    

btw, 我e文名,也是Justin,hoho
2007-05-11 16:24 | 农夫三拳      

#69楼[楼主]   回复  引用  查看    

@农夫三拳
后面还有一篇观察者模式啊,再后面的装饰者模式近期就会发上来的
2007-05-11 18:04 | Justin      

#70楼   回复  引用    

"program to an interface " really means "program to a super type"
所以,我认为你对这点理解上还是有些欠缺,不仅仅局限于接口.(我个人观点)
我的理解是,对父型类(abstract,interface)进行编程,而不要对具体的实例进行编程.
希望你的批改.
2007-07-13 08:44 | IecRory[未注册用户]

#71楼[楼主]   回复  引用  查看    

@IecRory
你好!你说的问题我在文中已经给出明确解释,具体看对"Program to an interface, not an implementation."原则的解释那段。
2007-07-13 08:52 | Justin      

#72楼   回复  引用    

嘿嘿.不好意思,我刚才发表言论的时候,没仔细看你写的东西....有些莽撞了.别介意啊.
现在看到了....
我现在也在看headfirst这本书.
可能以后还有很多问题要向您请教,希望能够互相促进.
design pattern里不也讲到过吗:
"Talking at the pattern level allows you to stay in the design pattern longer."
呵呵!
2007-07-13 09:22 | IecRory[未注册用户]

#73楼[楼主]   回复  引用  查看    

@IecRory
没关系,欢迎多多交流
2007-07-13 10:07 | Justin      

#74楼   回复  引用    

文章写得不错,使我受益匪浅!
2007-08-15 09:38 | lyy[未注册用户]

#75楼   回复  引用    

不过能够结合实际例子多讲讲不同设计模式的比较和应用就更好了。
2007-08-15 09:40 | lyy[未注册用户]

#76楼   回复  引用    

我们群是.NET开发人员的交流空间,欢迎广大.NET开发者来我们群一起讨论,研究.NET技术。(凑热闹的、其他语言开发人员以及喜欢涉及多门语言的误扰!)专业研究,探讨.NET技术的群:12339353.

#77楼   回复  引用  查看    

看的懂,可设计的时候用不好~~~~

2007-10-18 17:31 | ant520      

#78楼[楼主]   回复  引用  查看    

@ant520
慢慢体会,反复思考,螺旋进步
2007-10-18 22:02 | Justin      

#79楼   回复  引用  查看    

你好,我想问你一个问题。
我要做一个访问下载统计的东西,想让你帮我看一下我这样的分析可以不可以。
要统计的对象有多种,可能是一个页面,也可能是一个文档(下载),或者是整个网站。
对于不同的被统计对象,使用的统计方法也不同(即不同的行为)。
我想能不能用策略模式?
谢谢~
2007-11-12 10:27 | JerryGao      

#80楼[楼主]   回复  引用  查看    

@JerryGao
您好!应该可以!

想知道你的问题是否适合使用某种模式或技术来解决,首先搞清楚你要解决什么问题?比如为了隔离变化点(不同的统计方法)?还是为了简化代码,使其更抽象一下,以便跟能适应需求变化?(但是要先搞清楚那些事变化点)

不管事为了什么目的,只要你的目的和你要采用的方式的目的一致,当然就可以了!但不管到什么时候,不要为了使用设计模式而设计模式!
2007-11-12 19:04 | Justin      

#81楼   回复  引用    

o(∩_∩)o...哈哈,好文章,谢谢分享
2008-01-23 13:04 | tangss[未注册用户]

#82楼   回复  引用    

小弟有一个问题,看了上面的讲解 貌似FlyBehavior也可以用一个class,或者abstract class。而不一定非要定义成interface。是不是这样的?如果我把它定义成class或者abstract class跟定义成interface有什么不同?希望博主给小弟讲解一下。我的邮箱xucongqi@gmail.com。多谢!
2008-04-18 15:43 | scottie_xu[未注册用户]

#83楼[楼主]   回复  引用  查看    

@scottie_xu
你的问题其实是interface和Abstract Class 的异同:
1、在这个例子里,从单纯的“面向接口”编程的角度,这里使用哪个都行,因为这些都可以理解成是“接口”;
2、interface和Abstract Class甚至Class,在一定程度上应该是可以互相替换的,但是它们各自的特点决定了它们存在的价值。这方面如果展开说不是几句话的事情,你还是google一下吧,应该有很多专门阐述这个问题的文章。
2008-04-19 08:46 | Justin      

#84楼   回复  引用  查看    

讲行很动动形象。让我豁然开朗。谢谢!
2008-06-19 14:03 | 寻梦E.net      

#85楼[楼主]   回复  引用  查看    

@寻梦E.net
谢谢你的支持!
2008-06-19 15:19 | Justin      

#86楼   回复  引用    

我提点不同意见。lz在这里举的例子,Duck类,现在为部分Duck添加新的功能,怎么感觉用装饰模式也蛮适合的阿!
lz能不能讲讲装饰模式和策略模式的区别吗?
2008-06-21 11:20 | zieckey[未注册用户]

#87楼   回复  引用    

为了帮助理解,举例来说明。
我们的程序要实现加密功能。加密有一个加密算法,加密算法有很多种,比如MD5,DES等,我们的程序需要实现这个功能,就是可以让用户选择不同的加密算法进行加密。
这个过程我们可以用Strategy模式来实现。

文件一览:
Client
测试类。
EncryptStrategy
相当于Strategy角色。加密算法的抽象类/接口。
DesStrategy
相当于ConcreteStrategy角色。Des加密算法。
MD5Strategy
相当于ConcreteStrategy角色。MD5加密算法。
EncryptContext
相当于Context角色。

http://www.lifevv.com/sysdesign/doc/20071203214955037.html
这篇文章讲的很清楚!
2008-06-21 12:53 | zieckey[未注册用户]

#88楼[楼主]   回复  引用  查看    

@ jeffrey hsu
希望你认真看过之后再留言
2008-07-24 10:37 | Justin      

#89楼   回复  引用  查看    

好文
2008-07-24 12:12 | 赵武涛      

#90楼[楼主]   回复  引用  查看    

@赵武涛
3ks
2008-07-24 17:13 | Justin      

#91楼   回复  引用  查看    

太感谢了,终于明白了些策略模式,一直都不是特别懂
2008-10-06 07:03 | 飞林沙      

#92楼[楼主]   回复  引用  查看    

@飞林沙
有问题可以留言,大家一起讨论
2008-10-06 07:09 | Justin      

#93楼   回复  引用  查看    

个人认为每个模式都举一个.NET框架的例子,比如说这个的Sort是策略模式,这样非常有助于人理解!
2008-10-14 16:55 | 飞林沙      

#94楼[楼主]   回复  引用  查看    

@飞林沙
很高兴你能喜欢,谢谢留言支持!
2008-10-14 18:14 | Justin      

#95楼   回复  引用    

大哥!写的好好的!!!!小弟服了的!!!!!!
2008-12-22 00:09 | 应[未注册用户]

#96楼[楼主]   回复  引用  查看    

@应
谢谢你的反馈
2008-12-23 21:42 | Justin      

#97楼   回复  引用  查看    

很生动阿!
2009-01-08 17:22 | pillow      

#98楼   回复  引用  查看    

good
2009-01-14 17:14 | 不懂.NET      

#99楼   回复  引用  查看    

very good!
2009-02-18 10:16 | kuailewangzi1212      

#100楼   回复  引用    

很精辟呀.
行为抽象.+
HAS-A, NOT IS-A.

个人有个小看法,题外话.如果将ConcreteStrategy的实例化用一个类厂,在C++中,则可以避免ConcreteStrategy的头文件漫天飞.
呵呵,c#在这方面做得不错.
2009-02-20 17:41 | xmc[未注册用户]

#101楼   回复  引用  查看    

OOO,这是Head First 里的例子呀! 嘿嘿,刚好看到。
2009-03-13 15:26 | 一线风      

#102楼   回复  引用    

每看了这篇文章收获好大,呵呵,我是刚刚接触设计模式,说一点我对这个鸭子场景的理解哈:
我觉得在这场景中和joe以前遇到的问题环境最大的不同点是,鸭子的动作如fly() 既 不是一种(即所有的鸭子都一样飞),也不是各不相同(即每种鸭子都具有自己的飞行方法),而是分了几类,且飞行方法分类也可能出现新的分类。对于“所有的鸭子都一样飞”可以把fly()直接写进基类就好;对于““每种鸭子都具有自己的飞行方法“ 当然是使用多态,在子类中覆盖拉。 但是在这个场景中,把fly()直接写进基类肯定不行,当时用多态给每个具体的鸭子写具体的fly()就会有大量的重复劳动,为了给飞行动作提供一个分类的抽象,所以把fly()这个动作作进一个接口中,这样就可以有几种fly就实现几个接口,从而避免了重复的在具体鸭子中覆盖fly, 同时可以通过接口来扩展fly的种类。

那么是不是这个模式就是用于解决某个基类动作“分类”出现的问题啊?
2009-03-13 15:50 | Crane1983[未注册用户]

#103楼   回复  引用  查看    

写得太好了,最近在看设计模式的书,都不太懂,看了你的文章确实受益非浅,收藏了
2009-03-13 16:51 | 汗津津      

#104楼   回复  引用  查看    

写的很生动形象。受益匪浅
2009-03-23 15:07 | 重庆串串      

#105楼   回复  引用  查看    

用这个鸭子这个例子来说明策略模式,似乎有点大材小用,我认为这个例子能说明2个模式的复合,策略模式+桥接模式。
鸭子的子类可以和fly和quack组合,而桥接模式正是可以实现2个多个维度上的变化,将抽象和实现分离,将功能和行为分离。所以这个例子可以作为桥接模式的好例子,但其中分析的过程,很经典,赞一个,楼主扎实的翻译能力也不错。
2009-03-26 19:49 | 尚希杰      

#106楼   回复  引用    

very good!!!
2009-04-08 16:46 | ajaxor[未注册用户]

#107楼   回复  引用    

个人感觉这个例子比较复杂,更应该用来解释桥梁模式
2009-05-12 17:47 | yct[未注册用户]

#108楼   回复  引用    

策略模式强调的是算法的可替代,而楼主的某种鸭子是否会飞是一种属性,而不是一种策略。所以这个例子用来说明策略模式不妥。
2009-05-12 18:00 | yct[未注册用户]

#109楼[楼主]   回复  引用  查看    

@yct
不想为这个争论,喜欢的话,用它说什么都好
2009-05-14 23:18 | Justin      

#110楼   回复  引用    

看了博主的文章感觉非常受用,这是我看了关于策略模式(Strategy)的各种文章中最清晰详细的文章。
2009-05-16 20:37 | zzyu[未注册用户]

#111楼[楼主]   回复  引用  查看    

@zzyu
谢谢回复,很高兴文章对你有帮助!
2009-05-17 08:55 | Justin      

#112楼   回复  引用  查看    

so good! 博主。

详详细细,我功力提升了!
2009-06-14 16:51 | mrxliu      

#113楼   回复  引用    

RubberDuck继承了Duck. 实现了quack,不知道是不是眼花,好像看到<模拟鸭子图3>的链接指像了fly
2009-08-30 20:19 | katsu[未注册用户]

#114楼[楼主]   回复  引用  查看    

引用katsu:RubberDuck继承了Duck. 实现了quack,不知道是不是眼花,好像看到<模拟鸭子图3>的链接指像了fly


看得够细了,哪里确实是画错了,RubberDuck应该实现的是Quackable接口。
谢谢!
2009-08-30 21:00 | Justin      

#115楼   回复  引用  查看    

好文章,博主(曾经)辛苦了!
我是个业余的学习者,经验很少,想请教个问题:

类似策略这样改继承为组合的模式,要把一个类拆成几个类,那就难免需要对象间通信吧,如果各个策略类的实现需要获得 Context 类中的众多私有数据的值,应该如何处理比较好呢?
A. Context 往 Strategy 传递所有需要的参数?(好麻烦)
B. Context 和 Strategy 成为友元类?(紧耦合)
C. 还是参考 Memonto 模式的做法“在不破坏封装性的前提下,捕获一个对象的内部状态”(Memonto 具体内容我还没看呢,应该不比第一种方便吧?)?

有啥便捷的好方法吗?还是这样的情况有更好的模式?
先谢谢!
2009-10-07 20:00 | boylinxing      

#116楼   回复  引用    

受益匪浅,感谢博主,继续拜读其它文章~~
2009-12-21 15:29 | happystar[未注册用户]

#117楼   回复  引用  查看    

用楼主的话说就是:酷毙了~~
2010-01-19 14:52 | 小伦      
评论共2页: 上一页 1 2