Head First设计模式学习笔记一策略模式

假如我们现在有一个鸭子,鸭子会呱呱叫,也会游泳,但是每个鸭子的外观不相同(有白颜色的,有绿色的),那么你会怎么设计这个鸭子呢?

我们第一肯定是想到设计一个鸭子超类,这个超类包括swim()和quack()两个方法,还有一个抽象的dispaly()方法。

public abstract class Duck {
    public void swim(){
        System.out.println("I can swimming");
    }
    
    public void quack(){
        System.out.println("quack quack");
    }

    public abstract void display();
}

白色的鸭子继承Duck类,并实现display()方法

public class WhiteDuck extends Duck {
    public void display() {
        System.out.println("I am a white duck!");
    }
}

绿色鸭子也继承Duck类,实现自己的display()方法

public class GreenDuck extends Duck {
    public void display() {
        System.out.println("I am a green duck!");
    }
}

如果这个时候我们加了一个需求,要求要鸭子会飞,那么你又会怎么设计呢?你是不是首先想到在Duck类上加上一个fly()方法,像下面这样

public abstract class Duck {

    public void swim(){
        System.out.println("I am swimming");
    }

    public void quack(){
        System.out.println("quack quack");
    }

    public abstract void display();

    public void fly(){
        System.out.println("I can fly");
    }
}

但是如果并不是所有的鸭子都会飞,比如橡皮鸭子不会飞。这时候你会想是不是想可以在橡皮鸭中覆盖掉fly()方法,让fly()方法啥也不做,像下面这样

public class RubberDuck extends Duck{
    public void display() {
        System.out.println("I am a rubber duck");
    }

    @Override
    public void fly() {
        
    }
}

虽然上面这个方法可以暂时解决这个问题,但是如果这个时候加入了一个木头鸭子,它既不会呱呱叫,也不会飞,那这个时候你是不是就会想到在木头鸭子里覆盖掉fly()方法和quack()方法。但这样带来的问题就是如果有成千上百个鸭子,每次都要检查quack()方法和fly()方法,这简直是无穷无尽的噩梦。

那如果把fly()方法和quack()方法抽出来呢,放到一个Flyable接口和一个Quackable接口当中。让会飞的鸭子实现Flyable接口,会呱呱叫的鸭子实现Quackable()接口。
Flyable接口

public interface Flyable {
    void fly();
}

WhiteDuck类

public class WhiteDuck extends Duck implements Flyable{
    public void display() {
        System.out.println("I am a white duck");
    }

    public void fly() {
        System.out.println("I am fly with wing");
    }
}

假如现在有个火箭鸭,它能以火箭的动力飞行,我们可以这样

public class RocketDuck extends Duck implements Flyable{
    public void fly() {
        System.out.println("I can fly with rocket");
    }

    public void display() {
        System.out.println("I am a rocket duck");
    }
}

这样看好像没什么问题,每个种类的鸭子都可以选择性的实现自己想实现的接口。但是你忽视了一个非常大的问题,假如鸭子一共就有三种飞行方法(用翅膀飞、不会飞、以火箭动力去飞),这个时候如果你有几十种鸭子的话,就会造成大量的代码冗余,如果有的鸭子要修改一下飞行行为,就要对这些鸭子的fly()方法逐一的修改。

那么我们再来看看这种情况策略模式怎么去做的呢?
策略模式会将代码中变化的部分抽取出来封装(比如fly,quack),以便以后可以轻易的改动或扩充此部分,而不影响不需要变化的其他部分。其实这种思想是每个设计模式背后的精神所在。所有的设计模式都提供了一套方法让“系统中的某部分改变不会影响其他部分”。

我们利用接口代表每个行为,比方说,FlyBehavior与QuackBehavior,而行为的每个实现都将实现其中的一个接口。所以这次鸭子类不会负责实现Flyable与Quackable接口,反而是由我们制造的一组其他类专门负责实现FlyBehavior与QuackBehavior,这种就称为“行为类”。所以实际的行为实现不会绑死在鸭子的子类当中。

FlyBehavior接口

public interface FlyBehavior {
    void fly();
}

FlyWithWing类,用翅膀飞

public class FlyWithWing implements FlyBehavior {
    public void fly() {
        System.out.println("I can fly with wing");
    }
}

FlyWithRocket类,以火箭的动力飞

public class FlyWithRocket implements  FlyBehavior {
    public void fly() {
        System.out.println("I can fly with rocket");
    }
}

FlyNoWay类,不会飞

public class FlyNoWay implements FlyBehavior {
    public void fly() {
        System.out.println("I can not fly");
    }
}

QuackBehavior接口同理,这里就不贴代码了。

我们看到这样的设计可以使相同的行为代码能被复用,即使我们再新增一些行为也不会影响既有的行为类,也不会影响到使用飞行行为的鸭子类。

那么我们怎样将行为类和和鸭子类进行整合呢?
首先,我们再Duck类中加入两个变量,分别为"flyBehavior"与"quackBehavior",申明为接口类型而不是具体的实现,每个鸭子都会动态的设置这些变量以在运行时引用正确的行为类型。我们再以两个相似的方法performFly()和performQuack()取代Duck类中的fly()方法和quack()方法。

改过之后的Duck类

public abstract class Duck {
    FlyBehavior flyBehavior;

    public void performFly(){
        flyBehavior.fly();
    }
    
    public abstract void display();
}

我们看到要在Duck类中,我们要实现飞行的行为,只需要flyBehavior去飞就行了,在Duck类中,我们不在乎flyBehavior接口的对象到底是什么,我们只要关心该对象如何进行飞行就行。

接下来我们通过RocketDuck看一下我们怎样去设置flyBehavior变量

public class RocketDuck extends Duck {
    public RocketDuck() {
        flyBehavior = new FlyWithRocket();
    }

    public void display() {
        System.out.println("I am a rocket duck");
    }
}

我们看到,RocketDuck类中的默认构造方法会设置flyBehavior变量为一个FlyWithRocket对象,当调用performFly()方法时就会去调用FlyWithRocket对象的fly()方法。我们写一个Main方法去执行看一下:

public class Main {
    public static void main(String[] args) {
        Duck duck = new RocketDuck();
        duck.performFly();
    }
}
输出:
I can fly with rocket

我们还可以在Duck类中通过setter方法类设定鸭子的行为类,以达到随时改变鸭子行为的效果。

public abstract class Duck {
    FlyBehavior flyBehavior;

    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    public void performFly(){
        flyBehavior.fly();
    }

    public abstract void display();
}

假设现在有一个模型鸭ModelDuck,我们就可以这样

public class ModelDuck extends Duck {
    public ModelDuck() {
        flyBehavior = new FlyWithWing();
    }

    public void display() {
        System.out.println("I am a model duck");
    }
}

再运行看一下:

public class Main {
    public static void main(String[] args) {
        Duck duck = new ModelDuck();
        duck.performFly();
        duck.setFlyBehavior(new FlyNoWay());
        duck.performFly();
    }
}
输出:
I can fly with wing
I can not fly

我们能看到通过setter方法可以动态的区改变鸭子的行为。

那么假设现在我们的鸭子会讲话,但是每种鸭子会讲的语言不一样,有的会讲英文,有的会讲中文,还有的会讲汉语,这个时候我们应该怎么做呢?

首先我们可以写一个SpeakBehavior接口,它有一个speak()方法。

public interface SpeakBehavior {
    void speak();
}

原后会讲汉语的鸭子和会讲英语的鸭子分别实现SpeakBehavior接口

public class SpeakChinese implements SpeakBehavior {
    public void speak() {
        System.out.println("I can speak chinese");
    }
}
public class SpeakEnglish implements SpeakBehavior {
    public void speak() {
        System.out.println("I can speak english");
    }
}

原后在Duck类中加入speakBehavior变量

public abstract class Duck {
    FlyBehavior flyBehavior;
    SpeakBehavior speakBehavior;

    public void setSpeakBehavior(SpeakBehavior speakBehavior) {
        this.speakBehavior = speakBehavior;
    }
    
    public void performBehavior(){
        speakBehavior.speak();
    }

    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    public void performFly(){
        flyBehavior.fly();
    }

    public abstract void display();
}

在RocketDuck类中添加说话的行为

public class RocketDuck extends Duck {
    public RocketDuck() {
        flyBehavior = new FlyWithRocket();
        speakBehavior = new SpeakChinese();
    }

    public void display() {
        System.out.println("I am a rocket duck");
    }
}

我们运行看一下

public class Main {
    public static void main(String[] args) {
        Duck duck = new RocketDuck();
        duck.performFly();
        duck.performSpeak();
        duck.setFlyBehavior(new FlyNoWay());
        duck.performFly();
        duck.setSpeakBehavior(new SpeakEnglish());
        duck.performSpeak();
    }
}
输出:
I can fly with rocket
I can speak chinese
I can not fly
I can speak english

我们看到新加的行为不会影响到老的行为的运行。

总结
每一个鸭子都有一个FlyBehavior和一个SpeakBehavior,好将飞行和讲话委托给他们处理。当你将两个类结合起来使用,如同本例一般,这就是组合(composition)。这种做法和“继承”不同的地方在于,鸭子的行为不是继承来的,而是和适当的行为对象“组合”来的。这是一个很重要的技巧。其实是使用了我们的第三个设计原则:
多用组合,少用继承

你可能感兴趣的:(Head First设计模式学习笔记一策略模式)