设计模式——策略模式

应用背景

继承一定好用吗?

当我们做到一个项目类似于设计鸭子,鸭子呢有游泳和嘎嘎叫的能力,另外他们都有display显示外貌的方法。现在比如有两个鸭子,绿头鸭和红头鸭。绿头鸭的头是绿色的,可以游泳,叫声是ll声,红头鸭的头是红色的,可以游泳,叫声是rr声。

如果我们要设计这两个鸭子类其实可以直接使用继承来实现,我们可以先设计一个Duck基类,类中有display方法,该方法是抽象的(所以类也是抽象的),因为每个鸭子的外貌不一样。还有swim(),quack()方法,其中定义了基类默认的,比如说会游泳会嘎嘎叫等,之后绿头鸭红头鸭就需要实现display方法和重写swim(),quack()方法就行了。

但是这时候,客户增加需求说要增加会飞行的鸭子。也许我们可以在父类里增加fly(),但是这对原先的子类都会有改动,如果绿头鸭不会飞我们还需要重新覆盖fly()方法,如果我们也像display方法变成抽象的,那么重写的代码太多,代码太冗余。

继承的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分。影响会有溢出效应

超类挖的一个坑,每个子类都要来填,增加工作量,复杂度O(N^2)。不是好的设计方式

使用组合更加灵活可扩展

上面设计的鸭子类主要就是灵活性太差,其实我们可以使用分离的技巧,我们先来看鸭子这个类,它的方法中有变的和不变的,当我们增加需求或者改动需求的时候是只涉及变的的。

比如我们现在将叫声和飞行分离出来变成一个行为,然后我们分别实现这两个行为接口QuackBehavior,FlyBehavior,这两个接口里各有方法quack和fly方法,当我们的叫声和飞行方法有变动的时候我们只需要实现要改变的方法的接口就行。比如我们现在需要有一个火箭飞行的行为那么我们创建一个RocketFlyBehavior实现FlyBehavior接口然后重写方法就行了。

那么具体怎么使用呢?我们可以直接在Duck基类里增加两个接口字段QuackBehavior和FlyBehavior就行,然后我们基类的fly和quack方法中直接调用接口字段的fly方法和quack方法就行了,如果这时候我们需要给一个鸭子更换功能,我们直接调用接口字段的set方法就行了。

代码示例

Duck抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* @author Lin
* @date 2019-05-21 20:07
**/
public abstract class AbstractDuck {

protected FlyBehavior mFlyBehavior;
protected QuackBehavior mQuackBehavior;

public AbstractDuck(){

}

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

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

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

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

public abstract void display();

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

}

两个实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class GreenHeadDuck extends AbstractDuck {
public GreenHeadDuck() {
mFlyBehavior = new GoodFlyBehavior();
mQuackBehavior = new GeGeQuackBehavior();
}
@Override
public void display() {
System.out.println("**GreenHead**");
}

@Override
public void fly() {
System.out.println("~~no fly~~");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class RedHeadDuck extends AbstractDuck {

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

@Override
public void display() {
System.out.println("**RedHead**");
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* @author Lin
* @date 2019-05-21 20:58
**/
public class Main {

public static void main(String[] args) {
AbstractDuck greenHeadDuck = new GreenHeadDuck();
AbstractDuck redHeadDuck = new RedHeadDuck();

//默认调用
greenHeadDuck.display();
greenHeadDuck.fly();
greenHeadDuck.quack();
greenHeadDuck.swim();

//将红头鸭的飞行方法改成不能飞
redHeadDuck.setFlyBehavior(new BadFlyBehavior());
redHeadDuck.display();
redHeadDuck.fly();
redHeadDuck.quack();
redHeadDuck.swim();

//当我们增加行为的时候增加相应接口然后实现相应接口并将接口加入基类字段并实现set方法和相应方法调用接口里面的方法就行了
}

}

总结

策略模式:分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。

原则就是:分离变化部分,封装接口,基于接口编程各种功能。此模式让行为算法的变化独立于算法的使用者

-------------本文结束感谢阅读-------------