设计模式之策略模式

时间:2022-08-13 22:01:39

在写策略模式时,这里就不采用先提出概念,然后一步一步论述的写法,我们先抛出问题,看看使用面向对象编程思想是怎么解决问题的。

在这里我们使用鸭子(Duck)这个项目来引出策略模式。我们接到这样一种需求,做一个鸭子类的游戏,作为项目负责人,我们首先应该怎么考虑了?

我们先使用面向对象的设计思想来设计,首先,鸭子都会叫、游泳,所以把这些动作设计在超类里面,然后子类继承父类,在这里超类设计成抽象类。代码如下:

public abstract class Duck {

public Duck() {
}

public void Quack() {
System.out.println("~~~ gagaga ~~~");
}

public void swim() {
System.out.println("~~~ duck swim ~~~");
}
}
每种鸭子都有自己的“显示方式”,也就是外表,在超类里面再添加下面抽象方法。

public abstract void display();
构造完超类之后,我们就来做具体的鸭子,假如有绿头鸭,红头鸭,子类设计如下:

/**
* 绿头鸭子类
*
*/
public class GreenHeadDuck extends Duck {

@Override
public void display() {
System.out.println("~~~ greenHeadDuc ~~~");
}

}
/** * 红头鸭子类实现 * */public class RedHeadDuck extends Duck{@Overridepublic void display() {System.out.println("~~~ redHead duck ~~~");}}
写一个主方法执行上述类:

public class StimulateDuck {
public static void main(String[] args) {

//绿头鸭红头鸭实例化对象
GreenHeadDuck greenHeadDuck = new GreenHeadDuck();
RedHeadDuck redHeadDuck = new RedHeadDuck();

//绿头鸭方法调用
greenHeadDuck.display();
greenHeadDuck.Quack();
greenHeadDuck.swim();
greenHeadDuck.Fly();

//红头鸭方法调用
redHeadDuck.display();
redHeadDuck.Quack();
redHeadDuck.swim();
redHeadDuck.Fly();

}
}
结果如下:
~~~ greenHeadDuc ~~~
~~~ gagaga ~~~
~~~ duck swim ~~~
~~~ no fly ~~~
~~~ redHead duck ~~~
~~~ gagaga ~~~
~~~ duck swim ~~~
~~~ duck fly ~~~

按照上面面向对象的思想,我们成功的解决了需求。但是,后来有新的需求,提出有会飞的鸭子,一种解决办法还是使用面向对象思想,在超类中提供一种方法,如下:

public void Fly() {
System.out.println("~~~ duck fly ~~~");
}
但是这种方法不是很好,比如说已经有了几十种鸭子,现在在超类中加了会飞的方法,后面的子类中都会出现会飞的鸭子。 事实上,有很多鸭子是不会飞的,这样的话解决办法就是在子类中重写Fly()方法,但是这种办法太麻烦,需要在不会飞的鸭子子类中都添加重写。

基于上面的问题,有人会提出另一种面向对象的解决思路,那就是在超类中添加一个Fly抽象方法,如下

public abstract void Fly();
这种解决办法的弊端和上面的基本一样,那就是要在会飞的鸭子子类中添加Fly方法的实现。这样的做法就是代码的复用性降低了,如果有十种会飞的鸭子,那么在每一个子类中都要实现此抽象方法,而且是相同的代码。

所以,使用继承的方式,虽然在某些子类上方便了,但是影响了其他的子类,所以代码的复用性降低了,有益处效应。再比如说,后来又有了新的需求,石头鸭子,这就又要覆盖所有的方法 所以,超类挖的一个坑,每一个子类都要填,增加工作量,复杂度为O(N^2)。不是好的设计方式。

那么,我们遇到软件工程问题怎么去分析?

这是一个方法论,并不是策略模式固有的,适用于我们遇到的所有软件工程问题。

1. 首先分析问题里面的变化部分和不变化部分,提取变化部分,抽象成接口+实现

2. 鸭子哪些功能是根据需求变化的。上面的鸭子项目,叫声、飞行都是变化的。


下面我们就按照上面的方法论来重新设计鸭子项目。

新的会飞的需求我们设计成接口+实现的方式,代码如下:

public interface FlyBehavior {
void fly();
}
写接口的好处,新增行为简单,行为类更好的复用,行为的组合更方便。既有继承带来的复用好处,也没有挖坑。对上面接口实现:

public class GoodFlyBehavior implements FlyBehavior {

@Override
public void fly() {
System.out.println("~~~ good fly ~~~");
}

}
public class BadFlyBehavoir implements FlyBehavior {@Overridepublic void fly() {System.out.println("~~~ bad fly ~~~");}}
public class NoFlyBehavior implements FlyBehavior {@Overridepublic void fly() {System.out.println("~~~ not fly ~~~");}}

使用同样的方式对叫声进行设计:

public interface QuackBehavior {
void quack();
}

实现上面接口:

public class GaGaQuackBehavior implements QuackBehavior {

@Override
public void quack() {
System.out.println("~~~ gaga quack ~~~");
}

}
public class GeGeQuackBehavior implements QuackBehavior{@Overridepublic void quack() {System.out.println("~~~ gege quack ~~~");}}
public class NoQuackBehavior implements QuackBehavior {@Overridepublic void quack() {System.out.println("~~~ not quack ~~~");}}

下面引出策略模式:分别封装行为接口,实现算法族,超类里面放行为接口对象,在子类里具体设定行为对象。原则就是:分离变化部分,封装接口,基于接口编程各种功能。此模式让行为算法的变化独立与算法的使用者。

按照策略模式设计鸭子项目:

public abstract class Duck {

FlyBehavior mFlyBehavior;
QuackBehavior mQuackBehavior;

public Duck() {
}

public void fly(){
mFlyBehavior.fly();
}

public void quack(){
mQuackBehavior.quack();
}

public abstract void display();

public void setQuackBehavior(QuackBehavior quackBehavior){
mQuackBehavior = quackBehavior;
}

public void setFlyBehavior(FlyBehavior flyberBehavior){
mFlyBehavior = flyberBehavior;
}

public void swim(){
System.out.println("~~~ duck swim ~~~");
}
}

注意看超类中代码的变化,我们使用到了接口对象,如果看不懂的童鞋们,需要补习一下基础知识了。然后重新设计绿头鸭:

import com.java.strategymodeduck.flybehavior.GoodFlyBehavior;
import com.java.strategymodeduck.quackbehavior.GaGaQuackBehavior;

public class GreenHeadDuck extends Duck{

public GreenHeadDuck() {
mFlyBehavior = new GoodFlyBehavior();
mQuackBehavior = new GaGaQuackBehavior();
}

@Override
public void display() {
System.out.println("~~~ greenHead ~~~");
}

}
重新设计红头鸭:

import com.java.strategymodeduck.flybehavior.BadFlyBehavoir;
import com.java.strategymodeduck.quackbehavior.GeGeQuackBehavior;

public class RedHeadDuck extends Duck{

public RedHeadDuck() {
mFlyBehavior = new BadFlyBehavoir();
mQuackBehavior = new GeGeQuackBehavior();
}

@Override
public void display() {
System.out.println("~~~ redHead ~~~");
}

}

对每种鸭子进行调用实现:

import com.java.strategymodeduck.duck.Duck;
import com.java.strategymodeduck.duck.GreenHeadDuck;
import com.java.strategymodeduck.duck.RedHeadDuck;
import com.java.strategymodeduck.flybehavior.NoFlyBehavior;
import com.java.strategymodeduck.quackbehavior.NoQuackBehavior;

public class StimulateDuck {

public static void main(String[] args) {

Duck mGreenHeadDuck = new GreenHeadDuck();
Duck mRedHeadDuck = new RedHeadDuck();

mGreenHeadDuck.display();
mGreenHeadDuck.fly();
mGreenHeadDuck.quack();
mGreenHeadDuck.swim();

mRedHeadDuck.display();
mRedHeadDuck.fly();
mRedHeadDuck.quack();
mRedHeadDuck.swim();

mRedHeadDuck.display();
mRedHeadDuck.setFlyBehavior(new NoFlyBehavior());
mRedHeadDuck.fly();
mRedHeadDuck.setQuackBehavior(new NoQuackBehavior());
mRedHeadDuck.quack();
mRedHeadDuck.swim();
}

}

注意上面的调用,和使用面向对象设计是不一样的。对象类型是Duck,是超类类型,这样的好处就是,我们调用了之后直接调用超类里面的方法了。调用超类里面的方法之后,就到子类里面的行为对象调用这个方法。这样就屏蔽了子类里面的具体实现。也就是说,定义成超类类型的话,他只调用超类的方法的方式,而至于子类怎么实现,他不管。这样就屏蔽了上层的差别性。

策略模式的注意点:
1. 分析项目中变化部分和不变化部分
2. 多用组合少用继承,用行为来组合,而不是行为的继承,更有弹性(变化部分做成行为族之后,要考虑到这些行为之间,相互之间是不是可以替换的,这就是策略模式的特点)
3. 设计模式有没有相应的库直接使用? 有些库或框架本身就用某种设计模式设计的。
没有相应的库直接使用。比如Android中的列表对象,适配器,就是使用模板模式来设计的。
4. 如果找不到适用的模式怎么办?
 1) 对项目分析的还不够透彻
2) 确实遇到了比较奇怪的项目