策略模式---动态更改算法

引用:http://www.cnblogs.com/wenjiang/archive/2013/02/28/2937623.html

策略模式是设计模式中很重要的一种,它的主要意图就是:定义了算法族,分别封装起来,让它们之间可以互相替换。它让算法的变化可以独立于使用算法的客户,使得我们可以随时更改它们而不影响客户端的代码,而客户端可以自由选择不同的算法执行。

       要想了解策略模式,我们就要理解几个重要概念:

      1.什么是策略?
      2.什么是算法?
      3.算法可替换的条件?
 
       所谓的策略就是指在给定的输入条件下,实现某个目标的计划或方案,而算法是一个定义好的过程,能够根据一组输入产生一个输出。对于这两者可以这样理解:策略是一组可替换的算法。
       能够相互替换的算法必须具有相同的特点,就是它们处理的对象的来源一样,去向也一样,至于对象的类型,不要求相同。
       明白了策略和算法的区别,我们接下来就要讨论策略模式的几个重要方面:
       
      1.使用策略模式的原因?
      2.策略模式的实现?
      3.策略模式的优缺点?
 
      为什么我们要使用策略模式呢?想想这样的情况,我们负责一个基地战斗机数据的记录,战斗机有各种型号和性能,性能中的武器和飞行方式又是各种各样,但是我们提供的接口只有两个方法:fly()和attack()。想要在这两个方法中展现不同战斗机的性能,我们可能会用继承。
        首先,我们会定义一个抽象类:Plane,这个抽象类的大概样子像是下面这样:
public abstract class Plane{
   private abstract void fly();
   private abstract void attack();
 }

       接着我们就开始定义一些具体型号的战斗机:

复制代码
      public class FlyMaxPlane extends Plane{
             private void fly(){
                 System.out.println("This plane fly with Rocket");
            }
            private void attack(){
                 System.out.println("This plane can't attack");
            }
       }
       
       public class AttackMaxPlane extends Plane{
           private void fly(){
                  System.out.println("This plane can't fly");
           }
           private void attack(){
                  System.out.println("This plane attack with Rocket");
           }
      }
复制代码

          然后就在我们的程序中这样写:

复制代码
 public static void main(String[] args){
            FlyMaxPlane plane1 = new FlyMaxPlane();
            AttackMaxPlane plane2 = new AttackMaxPlane();
           
            showFunctionOfPlane(plane1);
            showFunctionOfPlane(plane2);
       }
      
      private void showFunctionOfPlane(Plane plane){
             plane.fly();
             plane.attack();
      }
复制代码

        使用继承可以解决这个问题,但是,继承也有它自己的问题。继承最大的问题就是,基类的改变会传给所有的子类,这是我们类设计者不想看到的。那么,不使用继承不就可以了?接口,就是我们这种情况下最好的替代方案。

       使用接口,我们的代码就可以更加灵活。
       接口是一个好东西,但如何使用,也是一个重要的问题,就是我们该让什么成为接口?如果我们这里将战斗机这个抽象作为接口,像是这样:
      
    public Interface Plane{
         void fly();
         void attack();
     }

       这样就将具体的实现交给实现类,从而避免我们上面的问题。确实如此,但不同型号的战斗机,就算外观差距太大,基本的东西都是不变的,像是重量这些基本的属性,至少在很长的一段时间都不会发生变化,如果用接口的话,我们就不能设置一些共同的属性和方法,当然我们可以将这样的东西交给实现类来实现,这样,代码重复的程度太可怕了!

       抽象类还是有抽象类的好处,接口依然不能完全代替,就像上面的例子。接口在很大程度上都被人滥用了,因为它是一个非常好用的东西,尤其是多态的使用。但是类的设计应该贴近现实生活,就像上面战斗机的例子,我们是对战斗机这样的具体东西进行抽象,而且代码应该能体现程序员对待一个问题的思维,并不仅仅是交给计算机自己处理的字节码。滥用接口本身就是对这种原则的破坏,因为很多人都不清楚接口的真正意义。
       使用抽象类是为了表达“is-a”关系,而使用接口是为了表达"has-a"关系。继承自一个抽象类,子类本身就是抽象类的一个特例,在分类上它属于抽象类,但是实现一个接口,并不能说,我们的实现类就是一个接口,准确的说法就是我们的实现类具有该接口定义的行为,当然,对于类型识别来说,接口和抽象类是没有什么区别的,都是一个事物的抽象。接口的真正意义是一组行为协议,规定我们的实现类应该具有的行为。也许有些人会说,这样是“is-like-a"关系,这样的说法其实搞错了"is-like-a"关系,"is-like-a"关系本身也属于继承的一种关系,传统意义上的继承应该是完全继承,就是不添加新的功能,只是覆写我们基类的方法,这样子类就可以向上转型为基类而不会出错,但是,现实就是子类会有它们自己的行为,会有自己特有的属性和行为,使得它们无法向上转型,这就是"is-like-a"关系。
        理解好接口和抽象类的逻辑意义,我们在设计的时候就能根据现实生活来决定到底应该采用什么样的抽象。上面的例子,我们依然使用抽象类,因为我们需要一个地方来存放所有战斗机都具有的属性和行为,抽象类是一个非常好的选择。接着,我们将会改动的行为抽取出来作为接口。如何判断一个行为是否应该抽取出来,我们就看:如果我们对该行为进行修改,相应的其他代码是否也要进行修改,如果需要,说明这个行为是一个变化的行为因素。这里我们就抽取出飞行和攻击这两个行为。
        我们现在抽取出飞行和攻击这两个接口:
复制代码
 public Interface FlyAble{
     void fly();
  }
         
  public Interface AttackAble{
      void attack();
  }
复制代码
         这里我们就可能犯一个错误,像是这样:
复制代码
         public class FlyMaxPlane extends Plane implements FlyAble, AttackAble{
               void fly(){}
               void attack(){}
          }
          
          public class AttackMaxPlane extends Plane implements FlyAble, AttackAble{
               void fly(){}
               void attack(){}
          }
复制代码

         为什么会这样写?很简单,因为我们可能有些飞机根本不具有飞行能力,像是这样:

  public class NotFlyPlane extends Plane implements AttackAble{
       void attack(){}
   }

        但是,根本不需要我们的子类实现这些接口,接口更大的意义是对象组合,这样根本就失去了接口的优点。要想利用接口的这些优点,我们可以这样建立这两个接口的实现类组,像是这样:

           public class FlyMax implements FlyAble{
               void fly(){}
           }
           public class AttackMax implements AttackAble{
                void attack(){}
           }

       然后再在我们的代码中使用这些实现类:

  public class FlyMaxPlane extends Plane{
      FlyMax = new FlyMax();
      FlyMax.fly();
  }

       这就是使用对象组合的方式,但是这样的方式还不够优雅。这时,策略模式就正式登场了,因为它就是处理对象组合的一种模式。

       使用策略模式,我们需要有一个委托类,像是这样:
复制代码
              public class BehaviorChange{
                    private FlyAble fly;
                    private AttackAble attack;
                    
                    private void setFly(FlyAble fly){
                           this.fly = fly;
                     }
                    private  void setAttack(AttackAble attack){
                           this.attack = attack;
                     }
                     
                     private void execute(){
                             fly.fly();
                             attack.attack();
                      }
              }
复制代码

           接着再在我们的子类中使用这个委托类:

复制代码
           public class FlyMaxPlane extends Plane{
                 public static void main(String[] args){
                       BehaviorChange behaviorChange = new BehaviorChange();
                       behaviorChange.setFly(new FlyMax());
                       behaviorChange.execute();  
                 }
            }
复制代码
         如果战斗机以后的飞行能力发生变化,我们可以动态的更改它的行为,像是这样:
behaviorChange.setFly(new FlyMin());
       这样,它就从拥有最大飞行能力变成拥有最小飞行能力!!而且我们客户可以随时更换飞行能力,只要他喜欢。
       以上就是策略模式的标准用法,它完全体现了策略模式的意图。但是,继承的问题依然存在,基类的变化依然会传给子类,所以,我们必须保证,基类中的非抽象部分是在很长一段时间内都不会发生变化的。
      策略模式有必要使用委托类吗?委托类其实提供的就是一个间接层,我们不需要知道有关于FlyAble和AttackAble的具体细节,我们只知道,使用execute()就可以让我们的战斗机飞起来,攻击敌人。这就是封装。不使用委托类的话,我们就必须在子类中显示的调用它们的方法,也就是说我们必须知道FlyAble和AttackAble的内部代码,如果是委托类的话,我们就不需要知道它们的细节,因为所有的细节都封装在委托类里面,客户要了解的只是委托类中的方法。
       到了这里,策略模式的基本内容已经讲完了,通过使用接口来实现对象组合,我们就可以充分的做到代码复用。实现策略模式真的需要继承吗?不一定,因为策略模式只是为了封装一组算法族,然后实现算法的替换而已,只要达到这个目的都可以说是策略模式,就算委托类,也可以没有。
       介绍完策略模式后,最后的部分就是针对我们上面提出的三个问题进行解答:
       1.使用策略模式的原因?
          原因与意图一样。有些书本可能会讲,策略模式就是为了处理if...else if...else这种大量的条件语句块,但千万不要因为这样的提示而随便使用策略模式,虽然这里的确使用了算法的替换,但是,我们并不一定能完全去除掉这些条件判断,肯定的是我们可以将这些代码封装起来,尤其是那些类似的算法中的重复代码。但这并不是策略模式该做的事情,它更大的作用就是可以在运行时动态的改变算法,而且可以将算法的实现交给具体的子类实现,我们类的设计者就不需要为以后的变化考虑太多,只要为以后的变化留下改动的空间就可以了。
        2.策略模式的实现?
           策略模式的实现是各种各样,但是基本的思想是不会变的,不同的语言,不同的情境都可以使用不同实现的策略模式。
        3.策略模式的优缺点?
           优点就是它的意图,缺点呢?这个就难讲了,因为大部分情况下模式的缺点都是不正确的使用模式,而不能归于模式本身。所以,只要谨记策略模式的意图,我们事实上也就知道它的“缺点”,就是无法处理其他情况。
         最后就是贴出我们上面例子的UML图,策略模式的UML图大概就是这样:
         


      

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值