策略模式

这是 设计模式列表 的第一种设计模式:策略模式。

什么是策略模式?

什么是策略?策略可以认为是一种方法,一种做事情的方式。由于在不同的场合,我们会用不同的方式来处理不同的事情,自然而然地,我们就选择了不同的策略。例如吃饭我们会用筷子,而在喝汤时,我们会选择用勺子,这就算是策略。而所谓的策略模式,就是将这种可变的策略抽离出来,而后采用委托(可以认为是接口或抽象类)来决定采用哪一种策略。

细说策略模式

这里直接用《Head First 设计模式》中的例子进行说明。

先上例子

假设我们要设计一个游戏,游戏里面是各种鸭子。这些鸭子呢,会飞,还会叫。自然地,我们会设计一个鸭子的抽象类,让每一种具体的鸭子继承这个鸭子抽象类。

 //这是鸭子的抽象类
 public abstract class Duck{
    // 这是鸭子叫的方法
    public abstract void quack();
    // 这是鸭子飞的方法
    public abstract void fly(); 
 }

 //具体的鸭子

 // 黄色的鸭子
 public class YellowDuck extends Duck{
    //抽象父类中的叫方法
    public void quack(){
        System.out.println("呱呱呱");
    } 
    //抽象父类中的飞方法
    public void fly(){
        System.out.println("我飞,飞,飞");
    }
 }

// 绿色的鸭子
public class GreenDuck extends Duck{
    //抽象父类中的叫方法
    public void quack(){
        System.out.println("呱呱呱");
    }
    //抽象父类中的飞方法
    public void fly(){
        System.out.println("我飞,飞,飞");
    }
}

如果我们想在游戏中增加一个玩具鸭子,它的叫法还是“呱呱呱”,但是它不会飞。我们该怎么做。不是容易嘛,我们在写一个ToyDuck就可以了。

//玩具鸭
public class ToyDuck extends Duck{
    //抽象父类中的叫方法
    public void quack(){
        System.out.println("呱呱呱");
    }
    //抽象父类中的飞方法
    public void fly(){
        System.out.println("我不会飞!");
    }
}

上面例子中存在的不足

虽然,上面的代码能够解决我们游戏设计鸭子的问题。但是上面的代码存在许多不足

  1. 代码冗余。不管是YelloDuck,GreenDuck还是ToyDuck,它们叫的方式都是“呱呱呱”。显然这样重复的代码写多次时没有必要的。如果有一百种鸭子,它们都是呱呱叫,我们就要写一百次这样的代码,这就变得繁琐了。这不简单?我们直接让父类实现“呱呱呱”叫的方法不就行了吗?这种方式是可以解决问题,但不合适。为什么呢?因为并不是所有的鸭子都是呱呱呱叫的。上面的例子刚好都是呱呱呱叫的情况,但是当我们要设计“吖吖吖”叫的鸭子时,这就显得不太合适了。

  2. 行为被固定死了。上面的三种鸭子都是“呱呱呱”叫,因此在游戏进行中,它们就只会呱呱呱叫了。对于YellowDuck和GreenDuck,它们都会飞。对于ToyDuck,它就不会飞。这些行为都被代码固定死了。假设我们想在游戏中增加一些新的花样,让鸭子能够根据某些情况改变叫的方式,例如在鸭子不同心情时有不同的叫法。如果还是上面的代码,就只能对代码懂手脚了。这和面向对象设计中的“对扩展开发,对修改关闭”这一“开闭原则”是相违背的。

解决方式:将可变化的行为抽离封装

对于鸭子类,叫的方式,飞的方法都可以说是一种策略,因为对于不同的鸭子,可能有不同的叫法和飞法。所以我们可以把鸭子叫和鸭子飞这两种行为抽离出来,封装成接口。


// 叫的接口
public interface QuackBehavior{
    void quack();
}

// 飞的接口
public interface FlyBehavior{
    void fly();
}

例如上面的鸭子呱呱呱叫,我们就可以写一个Quack类,实现QuackBehavior接口,来表示鸭子呱呱呱叫的方式。

//代表鸭子呱呱呱叫行为的类
public class Quack implements QuackBehavior{
    public void quack(){
        System.out.println("呱呱呱");
    }
}

而鸭子飞的行为,可以设计一个CanFly类和一个CannotFly类,各自实现FlyBehavior接口,分别表示鸭子会飞和鸭子不会飞的行为。

//代表鸭子会飞的行为
public class CanFly implements FlyBehavior{
    public void fly(){
        System.out.println("我飞,飞,飞");
    }
}

//代表鸭子不会飞的行为
public class CannotFly implements FlyBehavior{
    public void fly(){
        System.out.println("我不会飞!");
    }
}

我们已经有了鸭子的行为类了。这时候,我们在设计鸭子的抽象类时,我们可以用组合的方式让鸭子具有会叫和会飞的行为。(所谓组合的方式,就是让鸭子的抽象类具有QuackBehavior和FlyBehavior的引用,使用这种引用的好处是,可以指向子类的实例,从而实现扩展)在面向对象程序设计时,一般我们更倾向于使用组合的方式,而少用继承的方式,因为继承本身就是一种依赖,具有一定的限制。

多用组合 少用继承

下面是我们的鸭子抽象类。

public abstract class Duck{
    //可以认为是叫和飞的委托
    protected QuackBehavior quackBehavior;
    protected FlyBehavior flyBehavior;

    public void quack(){
        //直接交由委托来处理
        quackBehavior.quack();
    }

    public void fly(){
        //直接交由委托来处理
        flyBehavior.fly();
    }

    //这里增加了动态改变叫行为的方法
    public void setFly(QuackBehavior quackBehavior){
        this.quackBehavior = quackBehavior;
    }

    //这里增加了动态改变飞行为的方法
    public void setFly(FlyBehavior flyBehavior){
        this.flyBehavior = flyBehavior;
    }
}

对于具体的鸭子,我们只需要在类的初始化时把quackBehavior和flyBehavior指向特定的行为类就好了。

public class YellowDuck extends Duck{
    
    //构造函数
    public YellowDuck(){
        //YellowDuck是呱呱呱叫的
        this->quackBehavior = new Quack();
        //YellowDuck会飞
        this->quackBehavior = new CanFly();
    }
}

public class GreenDuck extends Duck(){
    
    //构造函数
    public GreenDuck(){
        //GreenDuck是呱呱呱叫的
        this->quackBehavior = new Quack();
        //GreenDuck会飞
        this->quackBehavior = new CanFly();
    }
}

public class ToyDuck extends Duck(){

    //构造函数
    public ToyDuck(){
        //ToyDuck是呱呱呱叫的
        this->quackBehavior = new Quack();
        //ToyDuck不会飞
        this->quackBehavior = new CanNotFly();
    }
}

上面的所用的方法就是所谓的策略模式。将鸭子可变的行为(叫和飞)抽离出来,分别用QuackBehavior和FlyBehavir接口引用来指向特定的行为实例。在特定的鸭子类中我们可以将QuackBehavior和FlyBehavior引用指向特定的行为类,从而实现了行为的复用。而且在需要扩展行为时,(例如增加会吖吖吖叫的鸭子),我们只需要实现一个吖吖吖叫的行为类,例如YaYaYa类,让该类实现QuackBehavior接口,就实现了行为的扩展。使用时只需要修改相应鸭子类的构造函数,让QuackBehavior引用指向YaYaYa类就可以了。而且,通过策略模式,我们可以通过setQuack和setFly这样的方法实现动态地修改类的行为。

策略模式的UML简图

UML图和上面的例子有些许区别,是我之前画的,懒得改了。

策略模式

上面的图是通过processon这个网站在线画的。如果你也想画这样的图,你可以通过下方的邀请链接进入网站进行注册,这样我可以拿到3张免费文件数量 😃 。

www.processon.com

策略模式总结

  1. 将可变化的行为进行抽离封装
  2. 面向接口编程
  3. 多用组合,少用继承
posted @ 2017-07-13 14:45  smallpi  阅读(355)  评论(0编辑  收藏  举报

页脚