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

假设有一个模拟鸭子的游戏,游戏中会出现各种鸭子,一边游泳戏水,一边呱呱叫。这个游戏的内部设计了一个鸭子超类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. oop(面向对象语言的三大特征):封装,继承,多态; (抽象),函数绑定

    封装/隐藏 : 通过类的访问限定符实现的   private    public 继承的意义之一:代码的复用 类的继承是指在一个现有类的基础上去构建一个新的类,构造出来的新类被称为派生类(子类),现有 ...

  2. JVM之JVM的体系结构

    一.JDK的组成 JDK:JDK是Java开发工具包,是Sun Microsystems针对Java开发员的产品.JDK中包含JRE(在JDK的安装目录下有一个名为jre的目录,里面有两个文件夹bin ...

  3. python切片(获取一个子列表(数组))

    切片: 切片指从现有列表中,获取一个子列表 返回一个新列表,不影响原列表. 下标以 0 开始: list = ['红','绿','蓝','白','黑','黄','青']# 下标 0 1 2 3 4 5 ...

  4. .Net Core Web Api实践(四)填坑连接Redis时Timeout performing EVAL

    前言:前两篇文章.net core+Redis+IIS+nginx实现Session共享中,介绍了使用Microsoft.Extensions.Caching.Redis实现Session共享的方法, ...

  5. 什么是aPaas?aPaas与低代码又是如何促进应用程序开发现代化的?

    从软件即服务(SaaS)到基础设施即服务(IaaS),云计算的兴起使“一切皆服务”(XaaS)模型得以泛滥,而aPaaS可能是这些模型中最鲜为人知的模型.随着aPaaS市场预计将从2018年的近90亿 ...

  6. JAVA中常用的异常处理方法

    1.在Java项目中经常遇到的异常情况 算术异常类:ArithmeticExecption 空指针异常类:NullPointerException 类型强制转换异常:ClassCastExceptio ...

  7. 清晰架构(Clean Architecture)的Go微服务: 事物管理

    为了支持业务层中的事务,我试图在Go中查找类似Spring的声明式事务管理,但是没找到,所以我决定自己写一个. 事务很容易在Go中实现,但很难做到正确地实现. 需求: 将业务逻辑与事务代码分开. 在编 ...

  8. 开发环境Vue访问后端接口教程(前后端分离开发,端口不同下跨域访问)

    原理:开发环境下的跨域:在node.js上实现请求转发,vue前端通过axios请求到node.js上,node.js将请求转发到后端,反之.响应也是,先到node.js上,然后转发vue-cil项目 ...

  9. XSS Challenges学习笔记 Stage#1~ Stage#19

    开门见山 Stage #1 http://xss-quiz.int21h.jp/?sid=2a75ff06e0147586b7ceb0fe68ee443b86a6e7b9 这一道题发现我们写入的内容直 ...

  10. 爬虫之协程,selenium

    1.什么是代理?代理和爬虫之间的关联是什么? 2.在requests的get和post方法常用的参数有哪些?分别有什么作用?(四个参数) - url headers parmas/data proxi ...