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)。这种做法和“继承”不同的地方在于,鸭子的行为不是继承来的,而是和适当的行为对象“组合”来的。这是一个很重要的技巧。其实是使用了我们的第三个设计原则:
多用组合,少用继承

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容