问题引入
有各种鸭子(如:野鸭、北京鸭),鸭子有各种行为(如:叫、飞和游泳),现在要求写一个程序将各种鸭子以及它们的行为做一个管理,并显示鸭子的信息
传统解决方案
写一个 Duck 类(可以是抽象类,也可以不是,具体看需求)
public abstract class Duck {
public abstract void display(); //显示鸭子信息
public void quack() {
System.out.println("鸭子嘎嘎叫~~");
}
public void swim() {
System.out.println("鸭子会游泳~~");
}
public void fly() {
System.out.println("鸭子会飞翔~~~");
}
}
再写一个 WildDuck 类继承 Duck 类
public class WildDuck extends Duck {
@Override
public void display() {
System.out.println(" 这是野鸭 ");
}
}
再写一个 PekingDuck 类继承 Duck 类
public class PekingDuck extends Duck {
@Override
public void display() {
System.out.println("~~北京鸭~~~");
}
//因为北京鸭不能飞翔,因此需要重写fly
@Override
public void fly() {
System.out.println("北京鸭不能飞翔");
}
}
从上面的代码可以看出,为了解决北京鸭不能飞这个问题,我们必须在 PekingDuck 类中重写 fly() 方法
假如现在又有一种玩具鸭,它不具备飞和游泳的行为,那么我们就又要重写 fly() 和 swim() 方法
问题分析
上面谈到的问题,其实是由于继承带来的,主要有两个部分:
- 子类的行为如果和父类不一致,就需要在子类中重写父类的方法
- 一旦父类 Duck 发生改变,则该类的所有子类都要发生改变
为了解决这样的问题,我们可以使用策略模式
策略模式
对于概念的解释,这里不写那样让人看不懂的定义,仅结合本例和自己的理解来阐述,相信在看懂我讲的东西之后,再去看那些官方的定义,一定会有所收获
策略模式就是将我们在前面写的属于父类 Duck 的每一个行为都单独抽取出来,做成一个个的接口(策略接口),然后针对每一个接口(每一种行为)去写其实现类(具体策略类)
针对飞这种行为举例如下
把飞(fly)这种行为单独抽取出来,写一个名为 FlyBehavior 的接口(飞的接口,也即策略接口)
public interface FlyBehavior {
void fly(); // 让实现类去具体实现
}
因为不同种类的鸭子,飞的行为可能不一样,所以我们需要写一个或多个实现类来实现 FlyBehavior 接口,以实现不同的飞的行为
比如有的鸭子不会飞,我们就写一个名为 NoFlyBehavior 的类来实现 FlyBehavior 接口
NoFlyBehavior 是飞的实现类,也即具体的策略类,放在本例来讲,就是用于阐述如何飞(使用哪种策略飞)
public class NoFlyBehavior implements FlyBehavior{
@Override
public void fly() {
System.out.println("不会飞");
}
}
再比如有的鸭子飞得比较差,我们就再写一个名为 BadFlyBehavior 的类来实现 FlyBehavior 接口
public class BadFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("飞的技术比较差");
}
}
再比如有的鸭子很会飞,我们就再写一个名为 GoodFlyBehavior 的类来实现 FlyBehavior 接口
public class GoodFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("很会飞");
}
}
接着我们再写一个名为 WildDuck 的类(相当于有些同学在其它文章中看到的 Context 类,这个类到底需不需要是抽象类,具体看需求),将前面抽取出来的行为的接口作为该类的一个成员变量(组合),如果有多个接口(叫或游泳),就在 WildDuck 类中写多个就行了
至于具体使用哪个实现类,我们可以在构造函数中指定
public class WildDuck {
// 属性(策略接口),如果还需要其它属性,接着添加即可
private FlyBehavior flyBehavior;
// 初始化时,传入具体的策略对象
public WildDuck(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
// 显示鸭子信息
public void display() {
System.out.println("野鸭");
}
public void executeFly() {
// 根据具体的策略对象,调用该策略对象的方法
flyBehavior.fly();
}
}
最后就是测试类的编写了
public class Test {
public static void main(String[] args) {
WildDuck wildDuck = new WildDuck(new GoodFlyBehavior());
wildDuck.display(); // 野鸭
wildDuck.executeFly(); // 很会飞
}
}
类结构安排如下(不一定非得这样,个人认为这样比较清晰)
其实做完上面这些,策略模式基本就讲清楚了,但是为了让同学们更直观地感受到策略模式相比继承的优势,我们现在加点需求
现在要求给野鸭加上一个“叫”的行为,那我们应该如何做呢?
-
写一个“叫”这种行为的接口,取名为 QuackBehavior
public interface QuackBehavior { void quack(); // 让实现类去实现 }
-
写一个名为 LoudQuackBehavior 的类(怎么叫?大声叫)
public class LoudQuackBehavior implements QuackBehavior { @Override public void quack() { System.out.println("大声叫"); } }
-
在之前写的 WildDuck 类中添加 QuackBehavior 作为成员变量,并添加一个执行 quack() 函数的函数,同时还要添加一个重载的构造函数,更改后的代码如下
public class WildDuck { // 属性(策略接口),如果还需要其它属性,接着添加即可 private FlyBehavior flyBehavior; private QuackBehavior quackBehavior; // 新加的 public WildDuck(FlyBehavior flyBehavior) { this.flyBehavior = flyBehavior; } // 新加的 public WildDuck(QuackBehavior quackBehavior) { this.quackBehavior = quackBehavior; } // 显示鸭子信息 public void display() { System.out.println("野鸭"); } public void executeFly() { flyBehavior.fly(); } // 新加的 public void executeQuack() { quackBehavior.quack(); } }
测试类的写法如下
public class Test {
public static void main(String[] args) {
WildDuck wildDuck = new WildDuck(new LoudQuackBehavior());
wildDuck.display(); // 野鸭
wildDuck.executeQuack(); // 大声叫
}
}
假如现在还有一个需求:加一种玩具鸭(可以叫不会飞),那我们又该如何做呢?
只要添加一个名为 ToyDuck 的类即可
public class ToyDuck {
private QuackBehavior quackBehavior;
public ToyDuck(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
// 显示鸭子信息
public void display() {
System.out.println("玩具鸭");
}
public void executeQuack() {
quackBehavior.quack();
}
}
测试类的写法如下:
public class Test {
public static void main(String[] args) {
ToyDuck toyDuck = new ToyDuck(new LoudQuackBehavior());
toyDuck.display(); // 玩具鸭
toyDuck.executeQuack(); // 大声叫
}
}
总结
- 策略模式的关键在于意识到策略是变的,因此我们需要为每种策略定义一个接口,让属于该策略的各种不同的具体的策略都去实现这个接口
- 策略模式的核心思想是:多用聚合/组合,少用继承;用行为类组合,而不是行为的继承
- 策略模式体现了对修改关闭,对扩展开放的原则,客户端增加行为不用修改原有代码,只要添加一种策略(行为)即可
- 提供了可以替换继承关系的办法:策略模式将算法封装在独立的策略类中,使得你可以独立于 Context 改变它,使得它易于切换、扩展和理解
- 需要注意的是:每增加一种策略,就要多增加一些类,当策略过多会导致类数目庞大。一般来说,如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题