这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战
项目背景
我们正在开发一个模拟鸭子游戏,具体功能就是模拟鸭子叫和游泳,并且显示是哪一种鸭子。
首先,我们从OO的角度设计这个项目,鸭子超类,扩展超类。
鸭子父类:
public abstract class Duck {
public void Quack() {
System.out.println("~~gaga~~");
}
public abstract void display();
public void swim() {
System.out.println("~~im swim~~");
}
}
扩展超类-绿头鸭:
public class GreenHeadDuck extends Duck {
@Override
public void display() {
System.out.println("**GreenHead**");
}
}
红头鸭与之类似。
现在,我们添加一个新需求,会飞的鸭子。
那么我们需要修改超类,新增方法会飞的鸭子。
public abstract class Duck {
...;
public void fly() {
System.out.println("~~im fly~~");
}
}
那么问题来了,这个fly让所有子类都会飞了,这是不科学的。
继承的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分,影响会有溢出效应。
假设绿头鸭不会飞,如果我们继续用OO原则解决新需求的不足。
public class GreenHeadDuck extends Duck {
...;
public void Fly() {
System.out.println("~~no fly~~");
}
}
现在,又有一个新需求,新增一类石头鸭子,也不会飞,这时我们就需要来填坑:
public class StoneDuck extends Duck {
...;
}
超类挖的一个坑,每个子类都要来填,增加工作量,复杂度O(N^2)。 这不是好的设计方式。
下面我们用策略模式来解决新需求,应对项目的扩展性,降低复杂度。
策略模式
什么是策略模式?
策略模式就是首先分别封装行为接口,实现算法族。然后超类里放行为接口对象,在子类里具体设定行为对象,一个类的行为或其算法可以在运行时更改,这种类型的设计模式属于行为型模式。基本原则就是分离变化部分,封装接口,基于接口编写各种功能, 此模式让行为算法的变化独立于算法的使用者。听起来可能有点抽象,下面我们来分析具体问题以便于我们理解。
如何实现策略模式?
首先分析项目变化与不变部分,提取变化部分,抽象成接口+实现;那么鸭子哪些功能是会根据新需求变化的?
1.叫声不同,有些gege,有些gaga
2.飞行不一样,有些会飞,有些不会飞
首先分离叫声行为:
叫声-父类
public interface QuackBehavior {
void quack();
}
子类-GeGe
public class GeGeQuackBehavior implements QuackBehavior {
@Override
public void quack() {
System.out.println("__GeGe__");
}
}
子类GaGa类似
public class GaGaQuackBehavior implements QuackBehavior {
@Override
public void quack() {
System.out.println("__GaGa__");
}
}
然后分类飞行行为:
飞行-父类
public interface FlyBehavior {
void fly();
}
子类-不会飞:
public class NoFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("--NoFly--");
}
}
子类-会飞
public class GoodFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("--GoodFly--");
}
}
然后设计鸭子:
父类
public abstract class Duck {
protected FlyBehavior mFlyBehavior;
protected QuackBehavior mQuackBehavior;
public Duck() {
}
public void flyStrategy() {
mFlyBehavior.fly();
}
public void quackStrategy() {
mQuackBehavior.quack();
}
public abstract void display();
public void setQuackBehavoir(QuackBehavior qb) {
mQuackBehavior = qb;
}
public void setFlyBehavoir(FlyBehavior fb) {
mFlyBehavior = fb;
}
public void swim() {
System.out.println("~~im swim~~");
}
}
子类-红头鸭:
public class RedHeadDuck extends Duck {
public RedHeadDuck() {
mFlyBehavior = new GoodFlyBehavior();
mQuackBehavior = new GeGeQuackBehavior();
}
@Override
public void display() {
System.out.println("**RedHead**");
}
}
绿头鸭与之类似。
下面具体使用某种策略:
public class StimulateDuck {
public static void main(String[] args) {
System.out.println("红头鸭,会飞,gege");
Duck mRedHeadDuck = new RedHeadDuck();
mRedHeadDuck.display();
mRedHeadDuck.swim();
mRedHeadDuck.flyStrategy();
mRedHeadDuck.quackStrategy();
System.out.println("切换为其他策略,不会飞,gaga");
mRedHeadDuck.setFlyBehavoir(new NoFlyBehavior());
mRedHeadDuck.setQuackBehavoir(new GaGaQuackBehavior());
mRedHeadDuck.flyStrategy();
mRedHeadDuck.quackStrategy();
}
}
这样设计的好处: 新增行为简单,行为类更好的复用,组合更方便。又有继承带来的复用的好处,没有挖坑。
总结
最后我们来对策略模式做一个总结。
使用场景
1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
2、一个系统需要动态地在几种算法中选择一种。
3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
应用实例
1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。
2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。
优点
1、算法可以自由切换。
2、避免使用多重条件判断。
3、扩展性良好。
缺点
1、策略类会增多。
2、所有策略类都需要对外暴露。
注意事项
1、分析项目中变化部分与不变部分。
2、多用组合少用继承;用行为类组合,而不是行为的继承,更有弹性。
3、如果一个系统的策略多于四个,就需要考虑使用混合模式(工厂方法模式+策略模式+门面模式),解决策略类膨胀的问题。