策略模式

一个鸭子的应用

假设我们需要设计一个鸭子的应用,可以生成各种各样的鸭子,那么根据面向对象的做法,我们需要首先创建一个鸭子的基类,然后再利用继承的方式来生成各种各样的鸭子类型,如下所示:

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
37
38
39
40
41
42
43
44
45
46
47
48
package com.changyuan.demo;
abstract class Duck{
public void quack(){
System.out.println("quack!");
}
public void swim(){
System.out.println("I am swimming!");
}
abstract public void display();
}

class MallarDuck extends Duck{
public void display(){
System.out.println("I am MallarDuck");
}
}

class RedHeadDuck extends Duck{
public void display(){
System.out.println("I am RedHeadDuck");
}
}

class RubberDuck extends Duck{
public void quack(){
System.out.println("zhizhi!");
}
public void display(){
System.out.println("I am RubberDuck");
}
}

public class helloWorld {
public static void main(String[] args) {
MallarDuck mallarDuck = new MallarDuck();
mallarDuck.quack();
mallarDuck.swim();
mallarDuck.display();
RedHeadDuck redHeadDuck = new RedHeadDuck();
redHeadDuck.quack();
redHeadDuck.swim();
redHeadDuck.display();
RubberDuck rubberDuck = new RubberDuck();
rubberDuck.quack();
rubberDuck.swim();
rubberDuck.display();
}
}

这里我们生成了三种鸭子,它们重写了基类的相关方法,实现了我们的目的。

那么我们如果想要鸭子会飞呢?

那么就需要给duck基类添加一个fly的方法。

1
2
3
4
5
6
7
8
9
10
11
12
abstract class Duck{
public void quack(){
System.out.println("quack!");
}
public void swim(){
System.out.println("I am swimming!");
}
abstract public void display();
public void fly(){
System.out.println("I am Flying");
}
}

但是这样有一个问题,不是所有的鸭子都会飞,比如我们可以新创建一个橡皮鸭的类,这个类也继承了基类的fly方法,它也会飞,这就不合适了。那么有没有解决方法呢?有,可以在橡皮鸭的类中我们重写fly方法,让他什么都不做就好了。但是这样的效果并不好。

给基类增加了方法,会导致它的所有的子类都需要进行排查甚至修改。这样子影响太大。

那么其他的解决方法呢?

利用接口

现在我们引入第一个设计原则:

分类应用代码中的变化部分以及不变部分

我们看我们的duck类,不变的是swim和display,而fly和quack是每个鸭子可能有也可能没有的,那么可以用接口来承载

引入第二个原则:

针对接口编程,而不是针对实现编程

我们可以针对变化的部分利用接口来表示它们的行为

针对接口编程有一个优点,就是可以用基类调用子类的方法,这样就可以实现在运行的时候才指定具体实现的对象。
如下所示我们可以利用基类做多态的调用

1
2
3
4
5
Dog dog = new Dog();
dog.bark()

Animal animal = new Dog();
animal.makeSound();

我们需要重新设计Duck类,将变化的部分抽出到一个新的类中,不变的部分保留。

首先我们设计两个新的接口

1
2
3
4
5
6
7
public interface QuackBehavior {
public void quack();
}

public interface FlyBehavior {
public void fly();
}

然后新的类实现定义的接口,分别定义了3种声音以及2种飞行模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Quack implements QuackBehavior{
public void quack() {
System.out.println("Quack!");
}
}
public class MuteQuack implements QuackBehavior{
public void quack() {
System.out.println("<< silence >>!");
}
}
public class Squeak implements QuackBehavior{
public void quack() {
System.out.println("Squeak!");
}
}
1
2
3
4
5
6
7
8
9
10
public class FlyWithWings implements FlyBehavior{
public void fly() {
System.out.println("I am flying!");
}
}
public class FlyNoWay implements FlyBehavior{
public void fly() {
System.out.println("I cannot fly!");
}
}

有了上面的类,我们就可以重新设计Duck类,利用基类可以多态的调用的原则,我们首先给duck类定义两种行为,一个为flyBehavior,一个为quackBehavior。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class Duck{
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck(){
}
public abstract void display();

public void performQuack() {
quackBehavior.quack();
}
public void performFly() {
flyBehavior.fly();
}
public void swim() {
System.out.println("Swimming!");
}
}

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MallarDuck extends Duck{
public MallarDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
public void display(){
System.out.println("I am MallarDuck");
}
}

public class helloWorld {
public static void main(String[] args) {
Duck mallarDuck = new MallarDuck();
mallarDuck.performQuack();
mallarDuck.performFly();
mallarDuck.display();
}
}

我们定义了一个MallarDuck,在它的构造函数里面指定了quackBehavior和flyBehavior的行为。这样每当我们新创建类的时候,就可以直接指定好这两个行为了。这对扩展及其友好。

上面的实现还是太死了,它在构造函数中写死了行为,实际上我们可以动态的设定行为。

我们可以在基类上添加两个set方法,这样就可以动态的改变类的行为了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
abstract class Duck{
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck(){
}
public abstract void display();

public void performQuack() {
quackBehavior.quack();
}
public void performFly() {
flyBehavior.fly();
}
public void swim() {
System.out.println("Swimming!");
}
public void setFlyBehavior(FlyBehavior fb) {
flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
}

然后我们新增加一个类:

1
2
3
4
5
public class FlyRocketPower implements FlyBehavior {
public void fly() {
System.out.println("I am flying with a rocket!");
}
}

然后我们就可以在运行的时候动态的改动对象的状态了。

1
2
3
4
5
6
7
8
9
public class helloWorld {
public static void main(String[] args) {
Duck modelDuck = new ModelDuck();
modelDuck.display();
modelDuck.performFly();
modelDuck.setFlyBehavior(new FlyRocketPower());
modelDuck.performFly();
}
}

显示结果如下:

1
2
3
I am ModelDuck
I cannot fly!
I am flying with a rocket!

下面是上面的整个逻辑的类图

duckDesignPattern

下面我们引入第三个设计的原则:

多用组合,少用继承

上面的所有的代码就解释了一个设计模式:

策略模式

策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立与使用算法的客户。

代码路径:

https://github.com/changyuanchn/DesignPattern/tree/main/src/com/changyuan/strategyPattern

显示 Gitment 评论