zoukankan      html  css  js  c++  java
  • 策略模式

       先从模拟鸭子游戏说起,游戏中会出现各种鸭子,一边游泳,一边瓜瓜叫,由此设计了一个superclass,并让各种鸭子继承此超类。

    现在要增加会飞的鸭子,如果在Duck类增加了fly(),则出现问题,不是所有鸭子都会飞。当然,你可以再不会飞的鸭子中把fly覆盖掉,并且方法体为空。但是以后你每加不会飞的鸭子,都必须重新覆盖一遍。

    我们这里意识到:

       当涉及“维护”时,为了复用而使用“继承”,结局并不完美。

    利用接口如何?

        注意我们上面的超类是有问题的,并不是所有的鸭子都会叫,而且quack方式不同。所以我们想到写2个接口。

    Flyable  { fly(){ } }

    Quackable{ quack(){ } )

    用接口是一个stupid的注意,想想看,这一来重复的代码比覆盖的方法还要多,对于48会飞的鸭子,必须实现接口类,如果要修改一下飞行的行为,这些都要改变。

    软件开发的一个不变真理:Change

     不管当初软件设计得多好,一段时间后,总是需要成长和改变。否则软件就会“死亡”。

    把问题归零

       现在我们意识到采用接口不是一个很好的办法,因为java接口不具有代码实现,所以继承接口无法达到代码的复用。这意味着:无论何时你需要修改某个行为,你必须往下追踪并在定义此行为的类中修改它,一不小心,可能会造成新的错误。幸运的是,有一个设计原则,恰好适用于此状况:

    设计原则:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

    即把变化的部分取出并“封装”起来,好让其他部分不受影响。使系统变得更有弹性。这个概念很简单,几乎是每个设计模式背后的精神所在,所以的模式都提供了一套方法让“系统中的某部分改变不会影响其他部分”。

      现在来分开变化和不变的部分。为了分开,我们建立了2组类(完全远离Duck类),一个和fly相关,一个和quack相关。每一组类将实现各自的动作。

     为了把这两个行为从Duck类分开,我们从Duck去取出来,建立一组新类来代表每个行为。

    设计鸭子的行为

        如何设计那组实现fly和quack的行为的类呢?

      我们希望一切具有弹性。正因为一开始鸭子行为没有弹性,才让我们走上现在这条路,我们还想能够“指定”行为到鸭子的实例。换句话说,我们应该在鸭子类中包含设定行为的方法,这样,我们就可以在“运行时”动态地“改变”飞行行为。

    设计原则: 针对接口编程,而不是针对实现编程。

      我们利用接口代表每个行为,比方说,FlyBehavior与QuackBehavior,而行为的每个实现都放在其中的一个接口。

      所以这次鸭子类不会复制实现Flying与Quacking接口,反而是由我们制造的一组其他类专门实现FlyBehavior与QuackBehavior,这称为“行为”类,由行为类而不是Duck类实现行为接口。

      这样的做法不同于以往,以前的做法是:行为来自Duck超类的具体实现,或是继承某个接口并由子类自行实现而来,这两种做法都是依赖于“实现,我们被实现绑的死死的,没办法更改行为(除非写更多代码)

     从现在开始,鸭子的行为将被放在分开的类中,此类专门提供某行为接口的实现。这样,鸭子类就不在需要知道行为的实现细节了。

    为什么非要把FlyBehavior设计成接口,而不是使用抽象超类,这样不就可以使用多态了吗?

    针对”接口编程“真正的意思是”针对超类型supertype编程“;

      这里的”接口“有多个函数,接口是一个”概念“,也是一种java的interface构造,你可以在不涉及java interface的情况下,”针对接口编程“。关键在于多态,利用多态,程序可以针对超类型编程,执行时会根据实际情况来执行真正的行为,不会被绑死在超类型的行为上。针对超类型编程“这句话,可以更明确说成”变量的声明类型应该是超类型,通常是一个抽象类或者是一个接口,如此,只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量,这也意味着,声明类时不用理会以后执行时的真正对象类型

     举个简单例子,假设有一个抽象类Animal,有2个具体的实现Dog和Cat继承Animal,做法如下:

    “针对实现编程”

    Dog d=new Dog(); 

    d.bark();

    但是,针对接口/超类型编程做法如下:

    Animal animal=new Dog();

    animal.makeSound();

    更好的是,子类实例化的动作不在需要在代码中硬编码,例如new Dog(),而是在运行时才指定具体实现的对象。

    a=getAnimal();

    a.makeSound(); 我们不知道实际子类型是什么, 我们只关心她知道如何正确地进行makesound就够了。

    问题:用一个类代表一个行为,感觉是否有点奇怪?类不是应该代表某种“东西”吗?

      在oo中,类代表的东西一般都是既有专题(实例变量)又有方法,不过在本例中,恰好“东西”是个“行为”,但是即使是行为,也可以由状态和方法,例如,飞行的行为可以有实例变量,记录飞行行为的属性等等。

    整合鸭子的行为

       关键在于,鸭子现在将fly和quack的动作“委托”delegate别人除了,而不是使用定义在Duck或子类的方法。

    做法是这样的:首先在Duck中加入两个实例变量,flyBehavior与quackBehavior,声明为接口类型(而不是具体实现类型),

    将原来在Duck类中的fly与quack换成performFly和performQuack。

    package headfirst.strategy;
    
    public abstract class Duck {
        FlyBehavior flyBehavior; //实例变量在运行时持有特定的行为的引用,每只鸭子都是引用实现接口的对象
        QuackBehavior quackBehavior;
     
        public Duck() {
        }
     
        abstract void display();
     
        public void performFly() {
            flyBehavior.fly();  //
        }
     
        public void performQuack() {
            quackBehavior.quack();//鸭子不亲自处理quakc行为,而是委托给quackBehavior引用的对象
        }
     
        public void swim() {
            System.out.println("All ducks float, even decoys!");
        }
    }

    接着,来设定flyBehavior与quackBehavior的实例变量

    package headfirst.strategy;
    
    public class MallardDuck extends Duck {
     
        public MallardDuck() {
     
            quackBehavior = new Quack();
                    flyBehavior = new FlyWithWings();
     
    
        }
     
        public void display() {
            System.out.println("I'm a real Mallard duck");
        }
    }

    当mallardDuck实例化时,构造器会把继承而来的实例变量quackBehavior与flyBehavior实例化。

     FlyWithWings是FlyBehavior的具体实现类。

    等等,不是说过我们将不对具体实现编程吗?但是我们的构造函数里做了什么,制造一个具体的Quack实现类的实例

    暂时我们是这么做的,在以后的学习中,有跟好的方法来修正这一点。仍请注意,我们把行为设定成具体的类,但是还是可以在运行时“轻易地”改变它。

      所以,目前的做法还是很有弹性的,只是初始化实例变量的做法不够弹性罢了

    编写2个行为实行类FlyWithWings.java和FlyNoway.java

    public interface FlyBehavior {
        public void fly();
    }
    public class FlyWithWings implements FlyBehavior {
        public void fly() {
            System.out.println("I'm flying!!");
        }
    }
    public class FlyNoWay implements FlyBehavior {
        public void fly() {
            System.out.println("I can't fly");
        }
    }

    最后编写测试类:

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

    我们可以增加功能,能够动态设定行为:

      在Duck增加设setter method 来设定实例变量:

        public void setFlyBehavior (FlyBehavior fb) {
            flyBehavior = fb;
        }
     
        public void setQuackBehavior(QuackBehavior qb) {
            quackBehavior = qb;
        }
     

    封装行为的大局观

      我们设计了Duck类,飞行行为类实现FlyBehavior接口,呱呱叫行为类实现QuackBehavior接口。注意,我们描述事情的方式稍微改变,不再把鸭子的行为说成是“一组行为”,我们开始把行为想成是“一簇算法”,想想看,在我们设计中,算法代表鸭子能做的事(不提的叫法和飞行法)。

    has-a 可能比is -a 更好

       "has-a"关系非常有趣:每一组鸭子都有一个flyBehavior和一个QuackBehavior,好将fly和quack委托给他们代为处理

     当你将2个类结合起来使用,如同本例,这就是”组合“composition,这种做法和”继承“不同之处在于:鸭子的行为不是继承来的,而是和适当的行为对象”组合“而来的。

      这是一个很重要的技巧,其实是使用了我们的第3个设计原则:

    设计原则;多用组合,少用继承

     组合用在”许多“设计模式中。

    上面我们用到的是”策略模式“,strategy pattern定义了算法族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于是用算法的客户

  • 相关阅读:
    poj1141
    poj1260
    poj1080
    poj1221
    用Microsoft Office SharePoint Designer 2007开发aspx
    在Web Part中使用User Control
    MOSS中的WebPart开发
    在MOSS中开发一个模块化的feature
    SharePoint Web Service的身份验证
    MOSS中对列表的一些操作(创建,查询等)
  • 原文地址:https://www.cnblogs.com/youxin/p/2590529.html
Copyright © 2011-2022 走看看