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

假设有一个模拟鸭子的游戏,游戏中会出现各种鸭子,一边游泳戏水,一边呱呱叫。这个游戏的内部设计了一个鸭子超类Duck,并让各种鸭子继承此超类。

public abstract class Duck {
public void quack(){
// 所有的鸭子都会呱呱叫,由Duck类负责实现
}
public void swim(){
// 所有的鸭子都会游泳,由Duck类负责实现
}
public abstract void display(); // 每个鸭子的子类负责实现自己的display
}

现在要增加一个功能,让鸭子能飞。

实现方法1:在Duck类里加上fly()方法

只需要在Duck类中加上fly()方法,所有的鸭子都会继承fly()。

但是问题来了,并非所有的子类都会飞,比如橡皮鸭子就不能飞。在超类中加上fly(),就会导致所有的子类都具备fly(),连那些不该具备fly()的子类也无法免除。

对代码所做的局部修改,影响层面可不只是局部!

实现方法2:把fly()从超类中取出来

我们可以把fly()从超类中取出来,放入一个“Flyable接口”中。这么一来,只有会飞的鸭子才能实现此接口。同样的方式,也可以用来设计一个“Quackable接口”,因为不是所有的鸭子都会叫。

这也是个超笨的主意,这样一来重复的代码会变多。如果有48个Duck的子类都要稍微修改一下飞行的行为,需要修改48个Duck的子类的代码。

虽然Flyable与Quackable可以解决“一部分”问题(不会再有会飞的橡皮鸭),但是却造成代码无法复用。

设计原则:找出应用中可能出现变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

分开变化和不会变化的部分

就我们目前所知,除了fly()和quack()的问题之外,Duck类还算一切正常。现在,为了要分开“变化和不会变化的部分”,我们准备建立两组类(完全远离Duck类),一个是“fly”相关的,一个是“quack”相关的,每一组类将实现各自的动作。

设计鸭子的行为

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

我们希望一切能有弹性,毕竟,正是因为一开始鸭子行为没有弹性,才让我们走上现在这条路。我们应该在鸭子类中包含设定行为的方法,这样可以实现在“运行时”动态地“改变”鸭子的飞行行为。

设计原则:针对接口编程,而不是针对实现编程。

// 针对实现编程
// 声明变量“d”为Dog类型,会造成我们必须针对具体实现编码
Dog d = new Dog();
d.bark(); // 针对接口/超类型编程
// 我们知道该对象是狗,但是我们现在利用animal进行多态的调用
Animal animal = new Dog();
animal.makeSound(); // 更棒的是,子类实例化的动作不再需要在代码中硬编码,
// 例如new Dog(),而是“在运行时才指定具体实现的对象”
// 我们不知道实际的子类型是“什么”,我们只关心它知道如何正确地进行makeSound()的动作就够了
a = getAnimal();
a.makeSound();

我们利用接口代表行为,比方说,FlyBehavior与QuackBehavior,而行为的每个实现都将实现其中一个接口。

所以这次鸭子类不会负责实现Flying与Quacking接口,反而是由我们制造一组其他类专门实现FlyBehavior与QuackBehavior,这就成为“行为”类。

这样的做法迥异于以往,以前的做法是:行为来自Duck超类的具体实现,或是继承某个接口并由子类自行实现。这两种做法都是依赖于“实现”,我们被“实现”绑得死死的,没办法更改行为。

在我们的新设计中,鸭子的子类将使用接口(FlyBehavior与QuackBehavior)所表示的行为,所以实际的“实现”不会被绑死在鸭子的子类中。

实现鸭子的行为

在此,我们有两个接口,FlyBehavior与QuackBehavior,还有对应的子类,负责实现具体的行为:

FlyWithWings继承自FlyBehavior,实现鸭子飞行;

FlyNoWay继承自FlyBehavior,实现不会飞的鸭子的行为;

Quack继承自QuackBehavior,实现鸭子呱呱叫;

Squeak继承自QuackBehavior,实现橡皮鸭子吱吱叫;

MuteQuack继承自QuackBehavior,实现不会叫的鸭子的行为。

这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了。这么一来,有了继承的“复用”好处,却没有继承所带来的包袱。

而我们可以新增一些行为,不会影响到既有的行为类,也不会影响“使用”到飞行行为的鸭子类。

整合鸭子的行为

在Duck类中加入两个实例变量,分别为“flyBehavior”与“quackBehavior”,声明为接口类型,每个鸭子对象都会动态地设置这些变量以在运行时引用正确的行为类型。

我们用两个相似的方法performFly()与performQuack()取代Duck类中的fly()与quack()。

实现performQuack():

// 在这部分代码中,我们不在乎quackBehavior接口的对象到底是什么,
// 我们只关心该对象知道如何进行呱呱叫就够了。
public class Duck {
// 每只鸭子都会引用实现QuackBehavior接口的对象
QuackBehavior quackBehavior; public void performQuack(){
// 鸭子对象不亲自处理呱呱叫行为,而是委托给quackBehavior引用的对象
quackBehavior.quack();
}
}

设定quackBehavior的实例变量:

public class MallardDuck extends Duck {
public MallardDuck(){
// 绿头鸭使用Quack类处理呱呱叫,所以当performQuack()被调用时,
// 叫的职责被委托给Quack对象,而我们得到了真正的呱呱叫
quackBehavior = new Quack();
}
}

同样的处理方式也可以用在飞行行为上。

组合

每一个鸭子都有一个FlyBehavior和QuackBehavior,好将飞行和呱呱叫委托给它们代为处理。

当你将两个类结合起来使用,如同本例一般,这就是组合(composition)。这种做法和“继承”不同的地方在于,鸭子的行为不是继承来的,而是和适当地行为对象“组合”来的。

设计原则:多用组合,少用继承。

使用组合建立系统具有很大的弹性,不仅可将算法封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可。

完整的代码

// Duck超类
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior; public Duck(){
} public 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类
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 interface FlyBehavior {
public void fly();
} // 飞行行为类
public class FlyWithWings implements FlyBehavior {
public void fly(){
System.out.println("I'm flying!!");
}
} // 飞行行为类
public class FlyNoWay implements FlyBehavior {
public void fly(){
System.out.println("I can't fly");
}
} // 叫声行为接口
public interface QuackBehavior {
public void quack();
} // 叫声行为类
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");
}
} // 测试类
public class MiniDuckSilmulator{
public static void main(String[] args){
Duck mallard = new MallardDuck();
mallard.performQuack();
mallard.performFly();
}
}

《Head first设计模式》之策略模式的更多相关文章

  1. 设计模式:策略模式(Strategy)

    定   义:它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化, 不会影响到使用算法的客户. 示例:商场收银系统,实现正常收费.满300返100.打8折.......等不同收费 ...

  2. PHP设计模式之策略模式

    前提: 在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能.如查 找.排序等,一种常用的方法是硬编码(Hard Cod ...

  3. JavaScript设计模式之策略模式(学习笔记)

    在网上搜索“为什么MVC不是一种设计模式呢?”其中有解答:MVC其实是三个经典设计模式的演变:观察者模式(Observer).策略模式(Strategy).组合模式(Composite).所以我今天选 ...

  4. 乐在其中设计模式(C#) - 策略模式(Strategy Pattern)

    原文:乐在其中设计模式(C#) - 策略模式(Strategy Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 策略模式(Strategy Pattern) 作者:webabc ...

  5. JavaScript设计模式之策略模式

    所谓"条条道路通罗马",在现实中,为达到某种目的往往不是只有一种方法.比如挣钱养家:可以做点小生意,可以打分工,甚至还可以是偷.抢.赌等等各种手段.在程序语言设计中,也会遇到这种类 ...

  6. 【设计模式】【应用】使用模板方法设计模式、策略模式 处理DAO中的增删改查

    原文:使用模板方法设计模式.策略模式 处理DAO中的增删改查 关于模板模式和策略模式参考前面的文章. 分析 在dao中,我们经常要做增删改查操作,如果每个对每个业务对象的操作都写一遍,代码量非常庞大. ...

  7. [design-patterns]设计模式之一策略模式

    设计模式 从今天开始开启设计模式专栏,我会系统的分析和总结每一个设计模式以及应用场景.那么首先,什么是设计模式呢,作为一个软件开发人员,程序人人都会写,但是写出一款逻辑清晰,扩展性强,可维护的程序就不 ...

  8. 设计模式入门,策略模式,c++代码实现

    // test01.cpp : Defines the entry point for the console application.////第一章,设计模式入门,策略模式#include &quo ...

  9. 设计模式之策略模式和状态模式(strategy pattern & state pattern)

    本文来讲解一下两个结构比较相似的行为设计模式:策略模式和状态模式.两者单独的理解和学习都是比较直观简单的,但是实际使用的时候却并不好实践,算是易学难用的设计模式吧.这也是把两者放在一起介绍的原因,经过 ...

  10. python设计模式之策略模式

    每次看到项目中存在大量的if else代码时,都会心生一丝不安全感. 特别是产品给的需求需要添加或者更改一种if条件时,生怕会因为自己的疏忽而使代码天崩地裂,哈哈,本文的目的就是来解决这种不安全感的, ...

随机推荐

  1. ArcEngine 数据编辑(IWorkspaceFactory)

    数据编辑做过很多次,没怎么出现问题,今天出现了问题,浪费了大半天,记录一下. 问题:修改Featrue的属性,修改后停止编辑,但是没有提示是否保存修改 原因:在编辑数据的时候没有加StartEditO ...

  2. Scala实践3

    一.函数式对象 1.1  rational类的规格和创建 Rational类来源于有理数(rational number),来表示n(分子)/d(分母)的数字,同时对有理数的运算(加减乘除)建模,还具 ...

  3. Java 基础(一)| 使用泛型的正确姿势

    前言 为跳槽面试做准备,今天开始进入 Java 基础的复习.希望基础不好的同学看完这篇文章,能掌握泛型,而基础好的同学权当复习,希望看完这篇文章能够起一点你的青涩记忆. 一.什么是泛型 泛型,即&qu ...

  4. 四、JVM之栈与栈帧

    栈: 1.又名堆栈,它是一种运算受限的线性表.其限制是仅允许在表的一端进行插入和删除运算.这一端被称为栈顶,相对地,把 另一端称为栈底.其特性是先进后出. 2.栈是线程私有的,生命周期跟线程相同,当创 ...

  5. GitHub进阶之利用Git远程仓库篇

    #在上一篇文章,相信大家对GitHub已经有了一个基础的理解 接下来我们来学习一下如何利用git来远程仓库 一,git是什么 git:一个免费的开源版本控制软件 用途:利用Git管理GitHub上的代 ...

  6. Django框架的初使用

    1Django框架的初使用 说起Django框架,肯定需要首先明确一个概念,即软件框架.下面就是第一个问题: 1 软件框架(software framework) 1.1 概念界定 软件框架:通常指的 ...

  7. Xhemj的Minecraft皮肤信息

    xhemj Minecraft Profile UUID:086e0354-fbb6-446b-83d4-60bdf449ad4e UUID:086e0354fbb6446b83d460bdf449a ...

  8. python,finally的应用

    脚本执行过程中可能因为被测试的环境有改变导致中间某一部分无法继续执行下去 可以在最后一行加上finally来执行最后一句脚本 比如 最后执行退出 表示 无论中间过程失败还是成功,最终都会执行退出操作 ...

  9. jenkins集成jmeter-进阶篇

    1.gitlab自动触发jenkins构建 1⃣️安装插件: 2⃣️新建工程,设置git url,build when a change is pushed auto.sh /bin/sh echo ...

  10. Web前端-HTML、CSS、JS

    概述 HTML是英文Hyper Text Mark-up Language(超文本标记语言)的缩写,它是一种制作万维网页面标准语言(标记).相当于定义统一的一套规则,大家都来遵守他, 这样就可以让浏览 ...