文档章节

HeadFirst设计模式(一) - 策略模式

XuePeng77
 XuePeng77
发布于 2016/03/28 22:35
字数 2231
阅读 394
收藏 16

鸭子游戏例子

    Joe上班的公司做了一套相当成功的模拟鸭子游戏:SimUDuck。游戏中出现各种鸭子,一边游泳戏水(swim),一边呱呱叫(quack),注意是呱呱叫,而不是吱吱叫。此系统的内部设计使用了标准的OO技术,设计了一个鸭子超类(Superclass),并让各种鸭子继承此超类。

    公司需求有新增,主管要求让让鸭子可以飞。

    Joe当然觉得这没有什么问题,只需要在Duck类中加入fly()方法就可以,他也是这么做的。

    在产品发布会上,主管打来电话说,他在发布会看到很多橡皮鸭子在屏幕飞来飞去……   

    Joe了忽略一件事情,并非所有的子类都可以拥有fly()方法,比如后来新加入的橡皮鸭(RubberDuck)就不能飞,而且橡皮鸭也不会呱呱叫,只能吱吱叫!在超类中加入新的行为,会使得某些并不合适该行为的子类也具有该行为。对代码所做的局部修改,影响层面可不只是局部。

利用继承的特性   

    Joy想,既然在父类加入方法行不通,那么可以使用继承的特性,在子类中覆盖fly()方法,让它什么都不做,同时覆盖quack()方法,让它吱吱叫。

    但,如果以后又加入新的鸭子类型,比如诱饵鸭(DecoyDuck),即不会飞也不会叫……还有很多,我们可以自己想得到。这种设计方式有一下几种缺点:

  1. 代码在多个子类中重复;

  2. 运行时的行为不容易改变;

  3. 很难知道所有鸭子的全部行为;

  4. 改变会牵一发而动全身,造成其他鸭子不想要的改变;

利用接口的特性

    Joe认识到继承可能不是答案,Joe知道规格会常常改变,每当有新的鸭子子类出现,他就要被迫检查并尽可能覆盖fly()方法和quark()方法……这简直是无穷的噩梦。

    所以,他需要一个更清晰的方法,让“某些”(而不是全部)鸭子类型可以飞或叫。

    他决定把fly()方法从超类中取出来,放进一个Flyable接口中。这么依赖,只有会飞的鸭子才实现此接口。同样的方式,也可以用来设计一个Quackable接口,因为不是所有的鸭子都会叫。

    

    Joe的主管告诉他,这真是一个超笨的主意,这么一来重复的代码会变多,如果认为覆盖几个方法就算是差劲,那么对于48的Duck的子类都要稍微修改一下飞行的行为,又怎么说?

如果你的Joe,你要怎么办?

    我们知道,并非“所有”的子类都具有飞行和呱呱叫的行为,所以继承并不是适当的解决方式。虽然Flyable与Quackable可以解决“一部分”问题(不会再有会飞的橡皮鸭),但却造成了代码无法复用,这只能算是从一个噩梦跳进另一个噩梦。身子,在会飞的鸭子中,飞行的动作可能还有多种变化……

    不管你在何处工作,构建些什么,用何种编程语言,在软件开发上,一致伴随你的哪个不变的真理就是需求变更!

    幸运的是,有一个设计原则,恰好适用于此状况。

设计原则

找出应用中可能需要变化之处,把他们独立出来,不要和哪些不需要变化的代码混在一起。

    换句话说,如果每次新的需求一来,都会使某方面的代码发生变化,那么你就可以确定,这部分的代码需要被抽出来,和其他稳定的代码有所区分。

    也就是说,把会变化的部分取出并封装起来,以便以后可以轻易地改动或拓展此部分,而不影响不需要变化的其他部分。

    好,该是把鸭子的行为从Duck类中取出来的时候了!

    我们知道Duck类内的fly()和quack()会随着鸭子的不同而改变。为了要把这两个行为从Duck类中分开,我们将把它们从Duck类中取出来,建立一组心累来代表每个行为。

    如何设计那组实现飞行和呱呱叫的行为的类呢?

    我们希望一切都有弹性,还向能够“指定”行为到鸭子的实例。避让说,产生一个新的绿头鸭实例,并指定特定类型的飞行行为给它。干脆顺便让鸭子的行为可以动态地改变好了。换句话说,应该在鸭子类中包涵设定行为的方法,这样就可以在“运行时”动态地“改变”绿头鸭的飞行行为。

    有了这些目标要实现,接着看看第二个设计原则:

设计原则

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

    利用接口代表每个行为,设计两个接口,FlyBehavior与QuackBehavior,而行为的每个实现都将实现其中的一个接口。

    这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了。来个天鹅也可以用。而我们可以新增一些行为,不会影响到既有的行为类,也不会影响“使用”到飞行行为的鸭子类。

    下面开始编码,首先创建鸭子类、飞行行为和呱呱叫行为。

    飞行行为的接口代码:

package cn.net.bysoft.strategy;

/**
 * 飞行行为接口。
 * */
public interface FlyBehavior {
    /**
     * 飞行动作。
     * */
    void fly();
}

    飞行行为的实现类,目前有两个,分别是使用翅膀飞行和不能飞行:

package cn.net.bysoft.strategy;

/**
 * 飞行行为的实现类。 该类实现飞行行为,可以使用翅膀飞行。
 * */
public class FlyWithWings implements FlyBehavior {
    /**
     * 可以使用翅膀飞行。
     * */
    public void fly() {
        System.out.println("使用翅膀飞行");
    }
    
}

package cn.net.bysoft.strategy;

/**
 * 飞行行为的实现类。 该类实现飞行行为,不能飞行。
 * */
public class FlyNoWay implements FlyBehavior {
    /**
     * 不能飞行。
     * */
    public void fly() {
        System.out.println("不能飞行。");
    }
}

    呱呱叫行为的接口:

package cn.net.bysoft.strategy;

/**
 * 呱呱叫行为接口。
 * */
public interface QuackBehavior {
    /**
     * 呱呱叫动作。
     * */
    void quack();
}

    呱呱叫行为的实现类,目前有三个,分别是呱呱叫、吱吱叫和不能叫:

package cn.net.bysoft.strategy;

/**
 * 呱呱叫行为的实现类。 该类实现呱呱叫行为,可以呱呱叫。
 * */
public class Quack implements QuackBehavior {
    /**
     * 呱呱叫。
     * */
    public void quack() {
        System.out.println("呱呱叫。");
    }

}

package cn.net.bysoft.strategy;

/**
 * 呱呱叫行为的实现类。 该类实现呱呱叫行为,可以吱吱叫。
 * */
public class Squeak implements QuackBehavior {
    /**
     * 吱吱叫。
     * */
    public void quack() {
        System.out.println("吱吱叫");
    }
    
}

package cn.net.bysoft.strategy;

/**
 * 呱呱叫行为的实现类。 该类实现呱呱叫行为,不能叫。
 * */
public class QuackNoWay implements QuackBehavior {
    /**
     * 不能叫。
     * */
    public void quack() {
        System.out.println("不能叫。");
    }

}

    鸭子类的代码:

package cn.net.bysoft.strategy;

/**
 * 鸭子超类。
 * */
/**
 * @author Administrator
 * 
 */
public abstract class Duck {
    /**
     * 游泳。
     * */
    public void swim() {
        System.out.println("在水里游泳。");
    }

    /**
     * 调用飞行行为。
     * */
    public void performFly() {
        flyBehavior.fly();
    }

    /**
     * 调用呱呱叫行为。
     * */
    public void performQuack() {
        quackBehavior.quack();
    }

    /**
     * 动态设定飞行行为。
     * */
    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }
    
    /**
     * 动态设定呱呱叫行为。
     * */
    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }

    /**
     * 显示外观。
     * */
    public abstract void display();

    // 飞行行为。
    FlyBehavior flyBehavior;
    // 呱呱叫行为。
    QuackBehavior quackBehavior;
}

package cn.net.bysoft.strategy;

/**
 * 绿头鸭。
 * */
public class DuckOfMallard extends Duck {
    
    @Override
    public void display() {
        System.out.println("绿头鸭。");
    }

}

package cn.net.bysoft.strategy;

/**
 * 玩具鸭。
 * */
public class DuckOfToys extends Duck {

    @Override
    public void display() {
        System.out.println("玩具鸭。");
    }

}

    最后是客户端调用,动态指令行为:

package cn.net.bysoft.strategy;

public class Main {

    public static void main(String[] args) {
        // 创建一个玩具鸭。
        Duck toysDuck = new DuckOfToys();
        // 玩具鸭不会飞。
        toysDuck.setFlyBehavior(new FlyNoWay());
        // 玩具鸭不会呱呱叫,但是可以吱吱叫。
        toysDuck.setQuackBehavior(new Squeak());

        // 玩具鸭开始活动。
        toysDuck.display();
        toysDuck.performFly();
        toysDuck.performQuack();
        toysDuck.swim();

        System.out.println("\n-------------------------\n");

        // 创建一个绿头鸭。
        Duck mallardDuck = new DuckOfMallard();
        // 绿头鸭可以飞。
        mallardDuck.setFlyBehavior(new FlyWithWings());
        // 绿头鸭是真鸭子,可以呱呱叫。
        mallardDuck.setQuackBehavior(new Quack());

        // 绿头鸭开始活动。
        mallardDuck.display();
        mallardDuck.performFly();
        mallardDuck.performQuack();
        mallardDuck.swim();

        /**
         * output: 
         * 玩具鸭。 
         * 不能飞行。 
         * 吱吱叫 在水里游泳。
         * 
         * -------------------------
         * 
         * 绿头鸭。 
         * 使用翅膀飞行 呱呱叫。
         *  在水里游泳。
         * */
    }

}

    下面是重新设计后的类结构,所期望的一切都有:鸭子继承Duck类,飞行行为和呱呱叫行为可以在客户端调用时指派。

    请特别注意类之间的关系。关系有两种,可以是IS-A(是一个)和HAS-A(有一个)或IMPLEMENTS(实现)。“有一个”可能比“是一个”更好。

    这是一个很重要的技巧。也是第三个设计原则:

设计原则

多用组合,少用继承。

    以上就是策略模式的内容。

© 著作权归作者所有

XuePeng77
粉丝 49
博文 153
码字总数 205910
作品 0
丰台
私信 提问
加载中

评论(4)

周大壮
周大壮
不错
小乞丐
小乞丐
实用..
XuePeng77
XuePeng77 博主

引用来自“Joken0704”的评论

不错
谢谢
Joken0704
Joken0704
不错
【设计模式笔记】(十六)- 代理模式

一、简述 代理模式(Proxy Pattern),为其他对象提供一个代理,并由代理对象控制原有对象的引用;也称为委托模式。 其实代理模式无论是在日常开发还是设计模式中,基本随处可见,中介者模式中...

MrTrying
2018/06/24
0
0
设计模式梳理(一)

设计模式梳理(一) 总体来说设计模式分为三大类: @案例源码地址:https://gitlab.com/lxqxsyu/DisgnPattern 创建型模式 简单工厂模式 工厂类是整个模式的关键。它包含必要的判断逻辑,能够...

lxq_xsyu
2017/11/02
0
0
《PHP设计模式大全》系列分享专栏

《PHP设计模式大全》已整理成PDF文档,点击可直接下载至本地查阅 https://www.webfalse.com/read/201739.html 文章 php设计模式介绍之编程惯用法第1/3页 php设计模式介绍之值对象模式第1/5页...

kaixin_code
2018/11/06
203
0
设计模式 2014-12-19

book: 阎宏《JAVA与模式》 架构设计栏目 http://blog.csdn.net/enterprise/column.html 概要: http://bbs.csdn.net/forums/Embeddeddriver 23种设计模式分别是: 1.单例模式 2.工厂方法模式...

jayronwang
2014/12/19
309
0
PHP设计模式(一):简介及创建型模式

我们分三篇文章来总结一下设计模式在PHP中的应用,这是第一篇创建型模式。 一、设计模式简介 首先我们来认识一下什么是设计模式: 设计模式是一套被反复使用、容易被他人理解的、可靠的代码设...

juhenj
2014/05/15
292
2

没有更多内容

加载失败,请刷新页面

加载更多

如何有效地计算JavaScript中对象的键/属性数量?

计算对象的键/属性数的最快方法是什么? 是否可以在不迭代对象的情况下执行此操作? 即不做 var count = 0;for (k in myobj) if (myobj.hasOwnProperty(k)) count++; (Firefox确实提供了一...

技术盛宴
16分钟前
1
0
百度网址安全中心拦截解除的办法分享

临近2019年底,客户的公司网站被百度网址安全中心拦截了,公司网站彻底打不开了,影响范围很大,于是通过朋友介绍找到我们SINE安全公司寻求帮忙解封,关于如何解除百度的安全拦截提示,下面就...

网站安全
27分钟前
1
0
Tomcat8源码分析-启动流程-start方法

上一篇:Tomcat8源码分析-启动流程-load方法 前面讲了启动流程中的Catalina.load,进一步调用绝大部分组建的init操作,主要完成对server.xml解析,并根据解析的结果结合设置的Rule(规则)构造...

特拉仔
35分钟前
6
0
Xamarin.FormsShell基础教程(7)Shell项目关于页面的介绍

Xamarin.FormsShell基础教程(7)Shell项目关于页面的介绍 轻拍标签栏中的About标签,进入关于页面,如图1.8和图1.9所示。它是对应用程序介绍的页面。 该页面源自Views文件夹中的AboutPage.x...

大学霸
42分钟前
3
0
一步一步理解Impala query profile(一)

很多Impala用户不知道如何阅读Impala query profile来了解一个查询背后正在执行的操作,从而在此基础上对查询进行调优以充分发挥查询的性能。因此我想写一篇简单的文章来分享我的经验,并希望...

九州暮云
43分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部