关闭

《Head First 设计模式》模式1——策略模式

标签: 设计模式策略模式
578人阅读 评论(0) 收藏 举报
分类:

概念什么的都是正确的废话!

所以不说废话,直接上栗子:


简单的鸭子

需求:假设公司现在有个项目,要做的是一款鸭子游戏,游戏中会有各种鸭子,一边游泳戏水一边呱呱叫,而且外观不同。
通常我们的做法是直接先来个超类Duck

public abstract class Duck { 
    public Duck() {
    }

    public void swim() {
        System.out.println("All ducks float, even decoys!");
    }

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

    abstract void display();
}

再来Duck的两个子类MallardDuck(绿头鸭)和ReadheadDuck(红头鸭):
MallardDuck:

public class MallardDuck extends Duck { 
    public MallardDuck() {
    }

    public void display() {
        // 外观是绿头
    }
}

ReadheadDuck:

public class RedHeadDuck extends Duck { 
    public RedHeadDuck() {
    }

    public void display() {
        // 外观是红头
    }
}

让鸭子飞

设计好了“简单的鸭子”后,万恶的产品汪加了新需求——让鸭子飞!
这下必须改代码了,可怎么改好呢?

使用继承

既然要让鸭子飞,就在 Duck里加上方法fly(),这样所有鸭子都会飞啦!

public abstract class Duck { 
    public Duck() {
    }

    public void swim() {
    }

    public void quack() {
    }

    abstract void display();

    public void fly() {
        // 飞行动作
    }
}

but,麻烦来了:原本不会飞的RubberDuck(橡皮鸭子)也飞起来了!
原来,并非Duck的所有子类都会飞,加上fly()方法后使得某些并不适合该行为的子类也具有了该行为!

大家从这里使用继承可以体会到:

当涉及维护时,为了复用而使用继承并不理想

聪明的你可能会耍下小聪明:在RubberDuckfly()方法里可以什么也不做啊!
那我就要问你了:对于DecoyDuck(诱饵鸭,不会叫也不会飞),quack()fly()是不是都要override后什么也不做? 对于后面可能要加入各种 不叫不飞、只叫不飞、只飞不叫 的鸭子,是不是都要这样麻烦地去改代码???

使用接口

从上面我们可以看到此处使用继承的缺点

  1. 代码在多个子类中重复
  2. 运行时的行为不容易改变
  3. 很难知道鸭子的全部行为
  4. 改变会牵一发动全身,造成其他鸭子不想要的改变

既然使用继承不能让人满意,那就用接口吧!

fly()Duck中取出来,放进一个“Flyable接口”中,会飞的鸭子才实现此接口;同理可以设计“Quackable接口”,因为不是所有鸭子都会叫。

but,麻烦又来了:以前已经有很多只鸭子了,需要会飞的鸭子都必须实现Flyable接口,修改代码变多,导致代码无法复用!

抛弃继承转而使用接口,发现从一个坑跳进另一个坑。。。


重新分析

我们知道,软件开发的不变真理就是改变

不管当初软件设计得多好,一段时间后,总是需要成长与改变,不然软件就会“死亡。

把问题归零,可以发现:使用继承和接口,都留着一个坑——无论何时修改某个行为,都必须往下追踪并在每个定义了此行为的类中修改它,不小心就会造成新的错误!

这里引出第一个设计原则

把可能会变化的部分取出来并“封装”起来,以便以后可以轻易地改动或扩充此部分,而不影响其他不需要变化的部分。

这个原则很简单,几乎是每个设计模式背后的精神所在,即“让系统中的某部分改变不会影响到其他部分”。

封装变化

在第一原则的指导下,分开变化和不会变化的部分:fly()quack()会随着鸭子的不同而改变,将他们从 Duck中分开,建立新的类来表示每个行为。

定义FlyBehaviorQuackBehavior接口,特定的具体行为编写在实现了这两个接口的子类中。
FlyBehavior

public interface FlyBehavior {
    public void fly();
}

QuackBehavior

public interface QuackBehavior {
    public void quack();
}

FlyBehavior的实现类FlyWithWings

public class FlyWithWings implements FlyBehavior {
    public void fly() {
        System.out.println("I'm flying!!");
    }
}

FlyBehavior的实现类FlyNoWay

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

QuackBehavior的实现类Quack

public class Quack implements QuackBehavior {
    public void quack() {
        System.out.println("Quack");
    }
}

QuackBehavior的实现类Squeak

public class Squeak implements QuackBehavior {
    public void quack() {
        System.out.println("Squeak");
    }
}

QuackBehavior的实现类MuteQuack

public class MuteQuack implements QuackBehavior {
    public void quack() {
        System.out.println("<< Silence >>");
    }
}

动态设定鸭子的行为

设计的时候,我们希望能更有弹性,可以指定行为到鸭子的实例,动态地改变鸭子的行为:在鸭子类中包含设定行为的方法,在运行时动态地改变鸭子行为。

这里引出第二个设计原则

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

针对接口编程”不是说针对Java的interface编程,更明确的说是“针对超类型编程”,即:变量的申明类型应该是超类型,通常是一个抽象类或者是一接口,这样一来申明类时就不用理会执行时的真正对象类型。

由行为类而不是Duck类实现行为接口,Duck不需要知道行为的实现细节。如果是用前面说的子类继承Duck或子类实现某个接口,都是依赖于实现,实现细节被约束得死死的,没办法更改行为。

整合鸭子的行为

现在,需要将飞行和呱呱叫的动作委托给Duck处理,而不是在Duck类(或子类)中定义fly()quack()方法。做法是在Duck 中加入两个实例变量——flyBehaviorquackBehavior,申明为接口类型而不是具体实现类型,让每个子类自己去动态地设置运行时的具体行为。

然后,将Duckfly()quack()删除,使用performQuack()performFly()代替。为了让子类动态设置运行时的具体行为,在Duck 加入动态设定行为的方法:setFlyBehavior()setQuackBehavior()

此时Duck类变成了:

public abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

    public Duck() {
    }

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

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

    abstract void display();

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

    public void performQuack() {
        quackBehavior.quack();
    }

    public void swim() {
        System.out.println("All ducks float, even decoys!");
    }
}

使用MallardDuck作为Duck的子类:

public class MallardDuck extends Duck { 
    public MallardDuck() { 
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();
    }

    public void display() {
        System.out.println("I'm a real Mallard duck");
    }
}

测试代码为:

public class MiniDuckSimulator { 
    public static void main(String[] args) { 
        Duck mallard = new MallardDuck();
        mallard.performQuack();
        mallard.performFly();

        mallard.setQuackBehavior(new Squeak());
        mallard.performQuack();
    }
}

控制台的打印结果是:

Quack 
I'm flying!! 
Squeak

整体格局

在上面的鸭子示例中,将两个类结合在一起使用,就是组合(Composition)。和继承不一样,鸭子的行为不是继承来的,而是和适当的对象组合而来的。(比如:DuckFlyBebavior组合)

这里引出第三个设计原则

多用组合,少用继承


策略模式

我们可以将一组行为想成一簇算法,在Duck中,算法代表鸭子能做的事(飞法和叫法):这种关系是HAS-A(有一个),而不是IS-A(是一个)。

吃完栗子可以讲正确的废话了!

策略模式的定义:

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


总结

  1. 坚持设计模式的三个原则,它们是我们做模式设计的根基。
  2. 软件开发后的维护时间比开发时间多很多,要致力于提高可维护性和可扩展性的复用程度。
  3. 使用组合使系统具有很大的弹性,不仅可以将算法封装成类,还可以在运行时动态地改变行为。
1
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

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

Head First 设计模式学习 ——策略模式简单理解     问题来源:     在子类继承父类时,如果子类不需要使用父类中的某些方法,该如何处理?     举个例子,如下图所示,父类Duck类抽...
  • u010429424
  • u010429424
  • 2016-07-27 19:22
  • 462

调侃《Head First设计模式》之总结篇

在之前的设计模式博客中,主要根据《Head First设计模式》谈了10个设计模式,今天来做下总结,好好梳理提炼x下精华,而且今天准备把GOF经典大作《设计模式》中的23个设计模式都总结一遍。(以下内...
  • sinat_23092639
  • sinat_23092639
  • 2015-06-27 09:38
  • 2171

调侃《First head 设计模式》之状态模式篇

现在有个糖果控制器,它的运行状态图如下:          我们要用java来实现这个糖果控制器。首先需要用一些实例变量来表示不同的状态:          我们的思路是创建一个糖果控制器类,...
  • sinat_23092639
  • sinat_23092639
  • 2015-05-24 10:42
  • 1063

图解 head first 设计模式

个人笔记,比较粗糙。详细内容请参考《head first 设计模式》 strategy 基础:抽象、封装、多态、继承 原则 封装变化:找出会变化的方面,把它们从不变的部分分离出来。 多用组合、少用继承...
  • qq_24145735
  • qq_24145735
  • 2016-07-16 23:13
  • 3061

读《大话设计模式》和《head first 设计模式》心得

1.面向对象的编程,并不是类越多越好,类的划分是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。 2.如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责...
  • fuqiaoyimeng
  • fuqiaoyimeng
  • 2013-11-10 18:17
  • 1163

《Head First设计模式》-策略模式C++实现

用C++实现了书中的设计鸭子飞行的例子。 鸭子类设计 #ifndef DUCK_H_ #define DUCK_H_ #include #include"Fly.h" #include"Quack...
  • a1358884804
  • a1358884804
  • 2017-01-11 18:33
  • 260

【设计模式】HeadFirst设计模式(四):工厂模式

设计模式要求我们不应该针对实现编程,为了降低耦合度,提高可维护性。当程序中出现“new”的时候,就证明程序在实例化一个具体类,所以用的是实现,而不是接口。如果代码绑着具体的类会导致代码更加脆弱,缺乏弹...
  • u010800530
  • u010800530
  • 2015-05-31 22:47
  • 980

策略模式(Strategy Pattern)(二):HeadFirst中鸭子的实现

一、问题描述 joe上班的公司做了一套成功的模拟鸭子的游戏:SimUDuck,游戏中会出现各种鸭子,一边游泳,一边呱呱叫,由于公司竞争压力加剧,必须重新设计鸭子(Duck)类,要求是:便于产...
  • jialinqiang
  • jialinqiang
  • 2013-05-11 09:40
  • 2746

Head First设计模式C++实现--第一章:策略模式

策略模式
  • FightForProgrammer
  • FightForProgrammer
  • 2014-06-21 22:51
  • 1668

调侃《HeadFirst设计模式》之工厂模式(一)

今天我们再次跟随《HeadFirst设计模式》的脚步,
  • sinat_23092639
  • sinat_23092639
  • 2015-04-11 21:23
  • 1769
    个人资料
    • 访问:79795次
    • 积分:1252
    • 等级:
    • 排名:千里之外
    • 原创:52篇
    • 转载:0篇
    • 译文:0篇
    • 评论:11条
    博客专栏