`
chineseoa
  • 浏览: 102969 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

设计模式解读之一: 策略模式

阅读更多

当我们掌握了Java的语法,当我们了解了面向对象的封装、继承、多态等特性,当我们可以用Swing、Servlet、JSP技术构建桌面以及Web应用,不意味着我们可以写出面向对象的程序,不意味着我们可以很好的实现代码复用,弹性维护,不意味着我们可以实现在维护、扩展基础上的代码复用。一把刀,可以使你制敌于无形而于江湖扬名,也可以只是一把利刃而使你切菜平静。Java,就是这把刀,它的威力取决于你使用的方式。当我们陷入无尽无止重复代码的泥沼,当我们面临牵一发而动全身的维护恶梦, 你应该想起“设计模式”这个行动秘笈。面向对象的精义,看似平淡,其实要经过艰苦实践才能成功。而构造OO系统的隐含经验于是被前人搜集而成并冠以“设计模式”之名。我们应该在编码行动初始就携带以它。接下来,让我们步“四人组”先行者之后,用中国文字、用实际案例领略模式于我们代码焕然一新的改变:

 

设计模式解读之一: 策略模式

 

    1. 模式定义
   
        把会变化的内容取出并封装起来,以便以后可以轻易地改动或扩充部分,而不影响不需要变化的其他部分;

    2. 问题缘起

    当涉及至代码维护时,为了复用目的而使用继承,结局并不完美。对父类的修改,会影响到子类型。在超类中增加的方法,会导致子类型有该方法,甚至连那些不该具备该方法的子类型也无法免除。示例,一个鸭子类型:

    public abstract class Duck {
        //所有的鸭子均会叫以及游泳,所以父类中处理这部分代码
        public void quack() {
            System.out.println("Quack");
        }
       
        public void swim() {
            System.out.println("All ducks float, even decoys.");       
        }
       
        //因为每种鸭子的外观是不同的,所以父类中该方法是抽象的,由子类型自己完成。
        public abstract void display();
    }

    public class MallardDuck extends Duck {
        //野鸭外观显示为绿头
        public void display() {
            System.out.println("Green head.");
        }
    }

    public class RedHeadDuck extends Duck {
        //红头鸭显示为红头
        public void display() {
            System.out.println("Red head.");
        }
    }

    public class RubberDuck extends Duck {
        //橡皮鸭叫声为吱吱叫,所以重写父类以改写行为
        public void quack() {
            System.out.println("Squeak");
        }

        //橡皮鸭显示为黄头
        public void display() {
            System.out.println("Yellow head.");
        }
    }

    上述代码,初始实现得非常好。现在我们如果给Duck.java中加入fly()方法的话,那么在子类型中均有了该方法,于是我们看到了 会飞的橡皮鸭子,你看过吗?当然,我们可以在子类中通过空实现重写该方法以解决该方法对于子类型的影响。但是父类中再增加其它的方法呢?

    通过继承在父类中提供行为,会导致以下缺点:

    a. 代码在多个子类中重复;
    b. 运行时的行为不容易改变;
    c. 改变会牵一发动全身,造成部分子类型不想要的改变;

    好啦,还是刚才鸭子的例子,你也许想到使用接口,将飞的行为、叫的行为定义为接口,然后让Duck的各种子类型实现这些接口。这时侯代码类似于:

    public abstract class Duck {
        //将变化的行为 fly() 以及quake()从Duck类中分离出去定义形成接口,有需求的子类中自行去实现

        public void swim() {
            System.out.println("All ducks float, even decoys.");       
        }
       
        public abstract void display();
    }

    //变化的 fly() 行为定义形成的接口
    public interface FlyBehavior {
        void fly();
    }

    //变化的 quack() 行为定义形成的接口
    public interface QuackBehavior {
        void quack();
    }

    //野鸭子会飞以及叫,所以实现接口  FlyBehavior, QuackBehavior
    public class MallardDuck extends Duck implements FlyBehavior, QuackBehavior{
        public void display() {
            System.out.println("Green head.");
        }

        public void fly() {
            System.out.println("Fly.");               
        }

        public void quack() {
            System.out.println("Quack.");               
        }
    }

    //红头鸭子会飞以及叫,所以也实现接口  FlyBehavior, QuackBehavior
    public class RedHeadDuck extends Duck implements FlyBehavior, QuackBehavior{
        public void display() {
            System.out.println("Red head.");
        }   

        public void fly() {
            System.out.println("Fly.");               
        }

        public void quack() {
            System.out.println("Quack.");               
        }   
    }

    //橡皮鸭不会飞,但会吱吱叫,所以只实现接口QuackBehavior
    public class RubberDuck extends Duck implements QuackBehavior{
        //橡皮鸭叫声为吱吱叫
        public void quack() {
            System.out.println("Squeak");
        }

        //橡皮鸭显示为黄头
        public void display() {
            System.out.println("Yellow head.");
        }
    }

    上述代码虽然解决了一部分问题,让子类型可以有选择地提供一些行为(例如 fly() 方法将不会出现在橡皮鸭中).但我们也看到,野鸭子MallardDuck.java和红头鸭子RedHeadDuck.java的一些相同行为代码不能得到重复使用。很大程度上这是从一个火坑跳到另一个火坑。

    在一段程序之后,让我们从细节中跳出来,关注一些共性问题。不管使用什么语言,构建什么应用,在软件开发上,一直伴随着的不变的真理是:需要一直在变化。不管当初软件设计得多好,一段时间之后,总是需要成长与改变,否则软件就会死亡。

    我们知道,继承在某种程度上可以实现代码重用,但是父类(例如鸭子类Duck)的行为在子类型中是不断变化的,让所有子类型都有这些行为是不恰当的。我们可以将这些行为定义为接口,让Duck的各种子类型去实现,但接口不具有实现代码,所以实现接口无法达到代码复用。这意味着,当我们需要修改某个行为,必须往下追踪并在每一个定义此行为的类中修改它,一不小心,会造成新的错误。

    设计原则:把应用中变化的地方独立出来,不要和那些不需要变化的代码混在一起。这样代码变化引起的不经意后果变少,系统变得更有弹性。

    按照上述设计原则,我们重新审视之前的Duck代码。

    1) 分开变化的内容和不变的内容

       Duck类中的行为 fly(), quack(), 每个子类型可能有自己特有的表现,这就是所谓的变化的内容。
           Duck类中的行为 swim() 每个子类型的表现均相同,这就是所谓不变的内容。

       我们将变化的内容从Duck()类中剥离出来单独定义形成接口以及一系列的实现类型。将变化的内容定义形成接口可实现变化内容和不变内容的剥离。其实现类型可实现变化内容的重用。这些实现类并非Duck.java的子类型,而是专门的一组实现类,称之为"行为类"。由行为类而不是Duck.java的子类型来实现接口。这样,才能保证变化的行为独立于不变的内容。于是我们有:

       变化的内容:

       //变化的 fly() 行为定义形成的接口
       public interface FlyBehavior {
        void fly();
       }
        
       //变化的 fly() 行为的实现类之一
       public class FlyWithWings implements FlyBehavior {
        public void fly() {
            System.out.println("I'm flying.");
        }
       }

       //变化的 fly() 行为的实现类之二
       public class FlyNoWay implements FlyBehavior {
        public void fly() {
            System.out.println("I can't fly.");
        }
       }

           -----------------------------------------------------------------

       //变化的 quack() 行为定义形成的接口
       public interface QuackBehavior {
        void quack();
       }

       //变化的 quack() 行为实现类之一
       public class Quack implements QuackBehavior {
        public void quack() {
            System.out.println("Quack");
        }
       }

       //变化的 quack() 行为实现类之二
       public class Squeak implements QuackBehavior {
        public void quack() {
            System.out.println("Squeak.");
        }
       }

       //变化的 quack() 行为实现类之三
       public class MuteQuack implements QuackBehavior {
        public void quack() {
            System.out.println("<< Slience >>");
        }
       }

       通过以上设计,fly()行为以及quack()行为已经和Duck.java没有什么关系,可以充分得到复用。而且我们很容易增加新的行为, 既不影响现有的行为,也不影响Duck.java。但是,大家可能有个疑问,就是在面向对象中行为不是体现为方法吗?为什么现在被定义形成类(例如Squeak.java)?在OO中,类代表的"东西"一般是既有状态(实例变量)又有方法。只是在本例中碰巧"东西"是个行为。既使是行为,也有属性及方法,例如飞行行为,也需要一些属性记录飞行的状态,如飞行高度、速度等。

    2) 整合变化的内容和不变的内容

       Duck.java将 fly()以及quack()的行为委拖给行为类处理。

       不变的内容:

       public abstract class Duck {
            //将行为类声明为接口类型,降低对行为实现类型的依赖
        FlyBehavior flyBehavior;
        QuackBehavior quackBehavior;

        public void performFly() {
            //不自行处理fly()行为,而是委拖给引用flyBehavior所指向的行为对象
            flyBehavior.fly();
        }

        public void performQuack() {
            quackBehavior.quack();
        }

        public void swim() {
            System.out.println("All ducks float, even decoys.");       
        }
       
        public abstract void display();
       }

       Duck.java不关心如何进行 fly()以及quack(), 这些细节交由具体的行为类完成。
      
       public class MallardDuck extends Duck{
        public MallardDuck() {
            flyBehavior=new FlyWithWings();
            quackBehavior=new Quack();       
        }
       
        public void display() {
            System.out.println("Green head.");
        }
       }

           测试类:

       public class DuckTest {
        public static void main(String[] args) {
            Duck duck=new MallardDuck();
            duck.performFly();
            duck.performQuack();       
        }
       }

       在Duck.java子类型MallardDuck.java的构造方法中,直接实例化行为类型,在编译的时侯便指定具体行为类型。当然,我们可以:
      
       1) 我们可以通过工厂模式或其它模式进一步解藕(可参考后续模式讲解);
       2) 或做到在运行时动态地改变行为。

    3) 动态设定行为

       在父类Duck.java中增加设定行为类型的setter方法,接受行为类型对象的参数传入。为了降藕,行为参数被声明为接口类型。这样,既便在运行时,也可以通过调用这二个方法以改变行为。

       public abstract class Duck {
        //在刚才Duck.java中加入以下二个方法。
        public void setFlyBehavior(FlyBehavior flyBehavior) {
            this.flyBehavior=flyBehavior;
        }
       
        public void setQuackBehavior(QuackBehavior quackBehavior) {
            this.quackBehavior=quackBehavior;
        }

        //其它方法同,省略...
       }

           测试类:

       public class DuckTest {
        public static void main(String[] args) {
            Duck duck=new MallardDuck();
            duck.performFly();
            duck.performQuack();
            duck.setFlyBehavior(new FlyNoWay());
            duck.performFly();
        }
       }

       如果,我们要加上火箭助力的飞行行为,只需再新建FlyBehavior.java接口的实现类型。而子类型可通过调用setQuackBehavior(...)方法动态改变。至此,在Duck.java增加新的行为给我们代码所带来的困绕已不复存在。

    该是总结的时侯了,让我们从代码的水中浮出来,做一只在水面上自由游动的鸭子吧:

    3.  解决方案

        MallardDuck 继承  Duck抽象类;          -> 不变的内容
        FlyWithWings 实现 FlyBehavior接口;     -> 变化的内容,行为或算法
    在Duck.java提供setter方法以装配关系;    -> 动态设定行为

    以上就是策略模式的实现三步曲。接下来,让我们透过步骤看本质:
   
    1) 初始,我们通过继承实现行为的重用,导致了代码的维护问题。          -> 继承, is a
    2) 接着,我们将行为剥离成单独的类型并声明为不变内容的实例变量并通过  -> 组合, has a
       setter方法以装配关系;

        继承,可以实现静态代码的复用;组合,可以实现代码的弹性维护;使用组合代替继承,可以使代码更好地适应软件开发完后的需求变化。

    策略模式的本质:少用继承,多用组合

分享到:
评论
78 楼 chendw_hz 2009-12-24  
关键看你怎么理解继承和组合的关系 继承是强是关系 而组合是弱是关系 楼主的列子讲的很好
77 楼 zhao_xiao_dong 2009-12-15  
一直拥有一本Java与模式,到现在都没有沉下心去看过,自控力怎么就下降了呢,
该反思一下了,楼主的模式分析,让我反思了一下哈
76 楼 329087553 2009-12-14  
顶,讲的很详细,学习了!
75 楼 kkfbai 2009-11-26  
条理很清晰,一步步明确地阐述了策略模式
74 楼 javatestoracle 2009-11-25  
我看了楼主的程序,其实策略模式一点不难,主要要理解好类与类之间关系

  1 泛化 2 关联、聚合、合成 3:依赖

关联、聚合、合成 都体现在成员变量上
73 楼 明天的昨天 2009-11-25  
这有点像 spring中的依赖注入
不知道理解是否正确
72 楼 uniworldson 2009-11-18  
这个内容好像和headfirst的第一章讲的完全一样,感兴趣的朋友可以仔细阅读headfirst的设计模式,十分经典!
71 楼 lsycg87 2009-10-29  
hubulyy 写道
从没有人讲这个模式写的如此透彻,清晰,首先从遇到的问题着手,分析问题产生的原因,对产生原因的每个要素进行剖析而找到最好的解决办法,然后通过这种分析问题,解决问题,这种过程,而升华为一种思想本质,道出了真理,令人拨云见日,茅塞顿开,醍醐灌顶啊。。。。。。敬佩

 
  你是算用心的一个,学习了..
70 楼 hubulyy 2009-10-24  
从没有人讲这个模式写的如此透彻,清晰,首先从遇到的问题着手,分析问题产生的原因,对产生原因的每个要素进行剖析而找到最好的解决办法,然后通过这种分析问题,解决问题,这种过程,而升华为一种思想本质,道出了真理,令人拨云见日,茅塞顿开,醍醐灌顶啊。。。。。。敬佩
69 楼 googlename 2009-10-12  
个人觉得策略模式的局限性还是比较大的。何为不变的内容,何为变化的内容。当需求发生改变的时候初期认为不变的也会变成变化了的内容。就拿楼主举的例子中的游泳来说。难道游泳方法就都是是统一的了吗?当需要定义一些各种游泳状态的鸭子时又该如何设计呢。那时还是需要在子类中重新定义游泳这个方法。感觉楼主讲的还是漏了点。。。。。。
68 楼 ahuango 2009-10-12  
终于比较清楚的理解了策略模式,有一点不是很明白 。如果使用继承的方法,不是可以通过方法复写来改变父类的行为。因为我看到在使用策略模式的时候,看到橡皮鸭同样包含了fly方法,照理说它 不应该有这样的行为。
67 楼 chenzhou0418 2009-09-19  
总感觉在使用瀑布模型开发项目时候,客户的需求不断改变,导致项目开发效率低下,看到了圆形模式和循环开发模式,今天又看到了这个策略模式。
66 楼 emparadise329 2009-09-18  
wujie2008 写道
总结的不错。受益匪浅。学习中。
我认为楼主的意思是:
   1.通过继承实现的代码复用容易引起牵一发而动全身的弊端。而且在父类中添加方法可以使不用拥有此方法的类也拥有此方法。
   2.通过组合实现的代码复用更具弹性,而且便于扩展。
   3.面向抽象编程可以提高程序的复用率。增加灵活性。
   4.策略模式的核心是,将问题的可变性和不可变性分开处理,将可变性单独抽取成接口,并且按照具体策略对其进行不同的实现,再通过类的组合达到代码复用,避免继承复用代码,每当需要添加新的方法是,就添加对应的接口和实现类,解决继承造成的代码复用问题。

不知理解的对不对,
另外有一个问题:
  当需求不断变化,需要添加许多的方法,而且有不同的实现,那么系统的类是不是会呈现爆炸式增长。怎么解决这个问题。


分析得很透彻,肯定会出现爆炸式增长的局面,但是这种情况怎么解决?
65 楼 _xiao_wei_ 2009-09-18  
策略模式的本质是:少用继承,多用组合。
64 楼 only_java 2009-08-12  
我想问下,假设我要实现的是橡皮鸭(它不会飞,但却定义了飞的动作),那在这个例子中使用策略模式怎么实现?
public abstract class Duck {
            //将行为类声明为接口类型,降低对行为实现类型的依赖
        FlyBehavior flyBehavior;
        QuackBehavior quackBehavior;

        public void performFly() {
            //不自行处理fly()行为,而是委拖给引用flyBehavior所指向的行为对象
            flyBehavior.fly();
        }

        public void performQuack() {
            quackBehavior.quack();
        }

        public void swim() {
            System.out.println("All ducks float, even decoys.");       
        }
       
        public abstract void display();
       }

63 楼 only_java 2009-08-12  
最终目的都是解耦
62 楼 pillarmorgan 2009-08-07  
thanks。楼主分析得不错。
61 楼 bsq519 2009-08-06  
很好。我学习了。!
60 楼 zhy20045923 2009-08-05  
wujie2008 写道
总结的不错。受益匪浅。学习中。
我认为楼主的意思是:
   1.通过继承实现的代码复用容易引起牵一发而动全身的弊端。而且在父类中添加方法可以使不用拥有此方法的类也拥有此方法。
   2.通过组合实现的代码复用更具弹性,而且便于扩展。
   3.面向抽象编程可以提高程序的复用率。增加灵活性。
   4.策略模式的核心是,将问题的可变性和不可变性分开处理,将可变性单独抽取成接口,并且按照具体策略对其进行不同的实现,再通过类的组合达到代码复用,避免继承复用代码,每当需要添加新的方法是,就添加对应的接口和实现类,解决继承造成的代码复用问题。

不知理解的对不对,
另外有一个问题:
  当需求不断变化,需要添加许多的方法,而且有不同的实现,那么系统的类是不是会呈现爆炸式增长。怎么解决这个问题。

当需求不断变化,需要添加许多的方法,而且有不同的实现,那么系统的类是不是会呈现爆炸式增长。这个问题有什么办法解决吗?还是说,策略模式的局限性就在这里?
59 楼 中国大人 2009-08-04  
不错,学习了。

相关推荐

    设计模式解读之一: 策略模式——鸭子游戏.doc

    设计模式解读之一:策略模式——鸭子游戏 策略模式是指定义amily of algorithms,encapsulate each one,and make them interchangeable,这种模式让我们可以在不修改原始代码的情况下,灵活地更换算法。下面我们来...

    设计模式解读之--策略模式.pdf

    ### 设计模式解读之策略模式 #### 模式定义与核心思想 策略模式是一种行为设计模式,它使得算法可以独立于使用它的客户而变化。在软件工程中,策略模式允许一组算法封装成一系列的类,它们共享相同的接口,但内部...

    Gof23中设计模式解读

    《Gof23中设计模式解读》是一篇深入解析设计模式的重要资料,它详细阐述了Gang of Four(Gof)在他们的经典著作《设计模式:可复用面向对象软件的基础》中提出的23种设计模式。设计模式是软件工程中的重要概念,它们...

    java 设计模式幽默解读

    "java 设计模式幽默解读"这个主题以轻松的方式探讨了这一关键主题,分为五个部分,旨在帮助开发者更好地理解和应用设计模式。 第一部分“大旗不挥,谁敢冲锋”可能是指设计模式在项目中的核心地位。如同战场上的...

    二十三种设计模式的详细解读 让您迅速了解设计模式

    设计模式是软件工程中的一种最佳实践,它们是解决常见编程问题的经验总结,为软件开发提供了可复用的解决方案。在本文中,我们将深入探讨二十三种主要的设计模式,帮助您快速掌握这些重要的概念。 1. **单例模式**...

    GOF 23种设计模式

    《GOF 23种设计模式》是软件工程领域中极具影响力的一本书,由Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides四位大师合著,因此也被称为“Gang of Four”(GOF)的设计模式。这本书详细阐述了在面向...

    设计模式汇总_圣思园Java版

    它可能会涵盖23种GOF(GoF, Gamma, Helm, Johnson, Vlissides)设计模式,包括结构型模式(如代理模式、装饰模式)、行为型模式(如策略模式、责任链模式)和创建型模式(如工厂模式、原型模式)。 学习这些设计...

    策略模式解读带源程序

    策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在C#中,策略模式经常被用来处理多种算法或行为的选择问题,使得代码更加灵活,易于扩展。在这个"策略模式解读带源程序"的例子中,我们看到一个用...

    C#23种设计模式【完整】(测试可用)

    以下是对C#中23种设计模式的详细解读: 1. 单例模式(Singleton):确保一个类只有一个实例,并提供一个全局访问点。在C#中,通常通过私有构造函数和静态方法来实现。 2. 工厂模式(Factory Method):定义一个...

    JAVA 设计模式的详细讲解另类解读

    在《设计模式之禅(第2版)》和《24种设计模式》这两本书中,你会找到每种模式的详细解释、适用场景、优缺点以及实际代码示例。例如,单例模式在Java中常用于控制资源的唯一访问,而装饰器模式则允许动态地给对象...

    C#设计模式 James Cooper

    《C#设计模式》是James Cooper的一本经典著作,它深入浅出地介绍了软件开发中的设计模式,并结合C#编程语言进行了详细的阐述。设计模式是软件工程中的宝贵经验总结,它们是解决常见问题的最佳实践,可以提高代码的...

    head设计模式+设计模式解析(第二版).rar

    另一方面,《设计模式解析(第二版)》可能更为深入,它不仅涵盖了GoF的设计模式,还可能包括近年来出现的新模式和对经典模式的更新解读。书中可能会探讨模式之间的相互关系,以及如何根据具体需求选择和组合不同的...

    我们的设计模式(绝对通俗易懂)

    ### 设计模式——通俗易懂的解读 #### 引言 设计模式是在软件工程领域内广泛应用的一种编程思想,它能够帮助开发者解决常见的编程难题,并提供一套成熟、经过验证的解决方案。设计模式通常被分为三大类:创建型...

    java的23种经典设计模式

    以下是对这23种设计模式的详细解读: 1. **单例模式**:确保一个类只有一个实例,并提供全局访问点。在Java中,可以通过双重检查锁定、静态内部类和枚举等方式实现。 2. **工厂方法模式**:定义一个用于创建对象的...

    24种设计模式介绍与6大设计原则.zip

    设计模式是软件工程中的一种最佳实践,它是在特定上下文中解决常见问题的经验总结。这个压缩包文件"24种设计模式介绍与6大设计原则.zip"涵盖了软件开发中至关重要的两个方面:设计模式和设计原则。以下是它们的详细...

    小梅设计模式

    - 策略模式(Strategy):定义一系列的算法,并将每一个算法封装起来,使它们可以互相替换。 - 模板方法模式(Template Method):定义一个操作中的算法骨架,而将一些步骤延迟到子类中。 了解并熟练运用这些设计...

    设计模式之禅1

    《设计模式之禅》是一本深入探讨软件设计模式的书籍,作者秦小波以其丰富的实践经验,重新解读了6大设计原则,并详细介绍了23种经典的设计模式。这本书旨在帮助开发者提升代码质量和可维护性,通过理解和应用这些...

    心得体会之java设计模式

    文档《设计模式列表.doc》很可能包含了各种常见的设计模式分类,如创建型模式(单例、工厂方法、抽象工厂、建造者、原型)、结构型模式(适配器、桥接、组合、装饰器、外观、享元、代理)和行为型模式(责任链、命令...

Global site tag (gtag.js) - Google Analytics