【设计模式】2. 策略模式

设计模式学习笔记


版权声明:本文由 Hov 所有,发布于 http://chenhy.com ,转载请注明出处。



1 策略模式

1.1 简介

策略模式作为一种软件设计模式,指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。

比如,买东西要付款,但是付款的方式有多种,比如有的人现金支付,有的人网上支付,有的人找人代付。

1.2 思路

策略模式将每一个算法封装起来,实现算法族(业务规则),这些算法可互换代替。

策略模式通过分离变化部分,封装接口,基于接口实现不同的功能。也就是说,对于同一类型的操作,将复杂多样的处理方式分离开来,有选择的实现各自特有的操作。通常,在超类放行为接口对象,在子类设定具体行为对象。

1.3 使用场景

  • 对于同一类型问题的多种处理方式,仅仅具体行为有差别。
  • 需要安全的封装多种同一类型的操作。
  • 出现同一抽象多个子类,又需要使用if或switch选择语句来选择。

1.4 优缺点

优点:

  1. 算法可自由切换;
  2. 避免使用多重条件判断;
  3. 扩展性良好。
  4. 可替换继承关系

缺点:

  1. 策略类会增多;
  2. 所有策略类都需要对外暴露。


2 实例讲解

我们引入模拟鸭子这个栗子(这是极客学院中讲解的栗子,个人觉得简单易懂,推荐)。

2.1 原始需求

我们通过程序模拟鸭子,假设我们的需求是这样的:鸭子有绿头鸭和红头鸭两种,它们都会嘎嘎叫和游泳。

根据需求,借助面向对象思维,我们可以设计如下代码:

(1)首先设计一个抽象类:鸭子

/* Duck.java */

public abstract class Duck{

    public Duck(){
    
    }

    public abstract void display(); //外观
    
    public void Quack(){            //嘎嘎叫
        System.out.println("--gaga--");
    }

    public void swim(){             //游泳
        System.out.println("--swimming--");
    }

}

(2)绿头鸭继承自鸭子这个超类

/* GreenHeadDuck.java */

public class GreenHeadDuck extends Duck{
    @Override
    public void display(){
        System.out.println("**GreenHeadDuck**");    //我是绿头鸭
    }
}

(3)同理,红头鸭也是继承鸭子这个超类

/* RedHeadDuck.java */

public class RedHeadDuck extends Duck{
    @Override
    public void display(){
        System.out.println("**RedHeadDuck**");      //我是红头鸭
    }
}

(4)最后,模拟绿头鸭、红头鸭。

/* StimulateDuck.java */

public class StimulateDuck {

    public static void main(String[] args) {
        GreenHeadDuck mGreenHeadDuck = new GreenHeadDuck(); //绿头鸭
        RedHeadDuck mRedHeadDuck = new RedHeadDuck();       //红头鸭
        
        //绿头鸭出场
        mGreenHeadDuck.display();
        mGreenHeadDuck.Quack();
        mGreenHeadDuck.swim();
        
        //红头鸭出场
        mRedHeadDuck.display();
        mRedHeadDuck.Quack();
        mRedHeadDuck.swim();
    }

}

程序输出结果如下:绿头鸭嘎嘎叫和游泳,红头鸭嘎嘎叫和游泳。

**GreenHeadDuck**
--gaga--
--swimming--
**RedHeadDuck**
--gaga--
--swimming--

可见,我们通过程序成功模拟鸭子。嘎嘎叫和游泳是所有鸭子的共同技能,但是鸭子的外观(种类)有可能不同。所以,在超类 Duck 将 Display 方法定义成抽象方法,由子类(不同鸭子)去实现 Display 这个方法。

2.2 新的需求

前面的鸭子只能嘎嘎叫和游泳,但现在我们有了新的需求,我们要让有的鸭子能飞。

(1)面向对象思维的解决方案:像游泳这个技能一样,在超类 Duck 中增加会飞这个技能,这样继承自超类 Duck 的鸭子都会飞。

修改 Duck.java 代码如下:

/* Duck.java */

public abstract class Duck{

    public Duck(){
    
    }
    
    public abstract void display(); //外观
    
    public void Quack(){            //嘎嘎叫
        System.out.println("--gaga--");
    }

    public void swim(){             //游泳
        System.out.println("--swimming--");
    }
    
    //增加 Fly 方法
    public void Fly(){              //飞
        System.out.println("--flying--");
    }

}

但是,这样会带来比较大的问题,继承自这个超类的鸭子都会飞。

这也是继承存在的问题:对类的局部改动,尤其超类的局部改动,会影响其它部分。

事实上,并不是所有的鸭子都会飞,所以我们能不能让有的鸭子会飞,有的鸭子不会飞呢。

(2)继续用面向对象思维来解决上面的问题:我们在子类中覆盖 Fly 这个方法,比如绿头鸭不会飞。

修改 GreenHeadDuck.java 代码如下:

/* GreenHeadDuck.java */

public class GreenHeadDuck extends Duck{

    @Override
    public void display(){
        System.out.println("**GreenHeadDuck**");
    }
    
    @Override
    public void Fly() {     //覆盖超类 Duck 中的 Fly 方法
        System.out.println("--can not fly--");  //不会飞
    }

}

这样就可以让有些鸭子能飞,有些鸭子不能飞了。遗憾的是,这样降低了代码的复用性。如果我们又新增一类鸭子,那是不是要在子类上也覆盖 Fly 这个方法呢。显然,每次增加新的鸭子,我们都需要复杂的修改,这不是好的设计模式。

2.3 策略模式

在新的需求下,我们要让有的鸭子能飞,有的鸭子不能飞。在 2.2 中,我们遗留了一个问题:一是如果只在超类中定义新的 Fly 方法,那么所有鸭子都会飞,不符合要求;二是在超类定义 Fly 方法,在子类覆盖 Fly 方法,这样子类可以修改能不能飞这个属性,但是每次新增一个子类(新的鸭子)我们都要覆盖 Fly 方法,这样代码复用性不高,而且操作繁琐。

策略模式将解决我们的烦恼。

(1)通过接口封装行为

/* FlyBehavior.java */

public interface FlyBehavior {      //FlyBehavior行为族
    void fly();
}
/* GoodFlyBehavior.java */

public class GoodFlyBehavior implements FlyBehavior {   //会飞

    @Override
    public void fly() {
        System.out.println("--GoodFly--");  
    }

}
/* BadFlyBehavior.java */

public class BadFlyBehavior implements FlyBehavior {    //不会飞

    @Override
    public void fly() {
        System.out.println("--BadFly--");   
    }

}

GoodFlyBehavior 、 BadFlyBehavior 继承自 FlyBehavior 。我们通过 FlyBehavior 将会不会飞这个行为封装了起来, FlyBehavior 就是我们所说的行为族。 GoodFlyBehavior 、 BadFlyBehavior 是 FlyBehavior 行为族下的不同行为策略,分别对应会飞、不会飞。

GoodFlyBehavior 、 BadFlyBehavior 可以被我们用来实例化不同的鸭子,这样就可以让鸭子有不同的行为策略,即有的鸭子会飞,有的鸭子不会飞。

(2)修改超类Duck

/* Duck.java */

public abstract class Duck{
 
    FlyBehavior mFlyBehavior;   
    
    public Duck(){
    
    }
    
    public void Quack(){
        System.out.println("--gaga--");
    }

    public abstract void display();

    public void swim(){
        System.out.println("--swimming--");
    }
    
    //注意这里我们没有设定鸭子会不会飞,而是让子类去考虑
    public void Fly(){
        mFlyBehavior.fly();     
    }
    
    //动态设置FlyBehavior
    public void SetFlyBehavior(FlyBehavior fb) {
        mFlyBehavior = fb;
    }
    
}

(3)在子类设定绿头鸭、红头鸭会不会飞

/* GreenHeadDuck.java */

public class GreenHeadDuck extends Duck{
    
    public GreenHeadDuck() {
        mFlyBehavior = new GoodFlyBehavior();   //将mFlyBehavior实例化成GoodFlyBehavior(会飞)
    }
    
    @Override
    public void display(){
        System.out.println("**GreenHeadDuck**");
    }
    
}
/* RedHeadDuck.java */

public class RedHeadDuck extends Duck{
    
    public RedHeadDuck() {
        mFlyBehavior = new BadFlyBehavior();    //将mFlyBehavior实例化成BadFlyBehavior(不会飞)
    }
    
    @Override
    public void display(){
        System.out.println("**RedHeadDuck**");
    }
    
}

(4)测试

/* StimulateDuck.java */

public class StimulateDuck {

    public static void main(String[] args) {
        Duck mGreenHeadDuck = new GreenHeadDuck();  //绿头鸭
        Duck mRedHeadDuck = new RedHeadDuck();      //红头鸭
        
        //绿头鸭出场
        mGreenHeadDuck.display();
        mGreenHeadDuck.Quack();
        mGreenHeadDuck.swim();
        mGreenHeadDuck.Fly();
        
        //红头鸭出场
        mRedHeadDuck.display();
        mRedHeadDuck.Quack();
        mRedHeadDuck.swim();
        mRedHeadDuck.Fly();
        
        //前面我们是在构造函数中定义鸭子会不会飞,这里动态地将绿头鸭从GoodFly变成BadFly
        mGreenHeadDuck.display();
        mGreenHeadDuck.SetFlyBehavior(new BadFlyBehavior());
        mGreenHeadDuck.Fly();
    }

}

运行结果:

**GreenHeadDuck**
--gaga--
--swimming--
--GoodFly--
**RedHeadDuck**
--gaga--
--swimming--
--BadFly--
**GreenHeadDuck**
--BadFly--

这就是策略模式的解决思路。通过封装行为族,超类不考虑具体的行为,而让子类去选择各自的行为。这样,我们增加新的鸭子,超类的代码无需改动,子类也不需覆盖超类的代码,只需构造不同的实例即可实现子类间不同的行为。


 
comments powered by Disqus