设计模式解读之一: 策略模式——鸭子游戏

当我们掌握了Java的语法,当我们了解了面向对象的封装、继承、多态等特性,当我们可以用Swing、Servlet、JSP技术构建桌面以及Web应用,不意味着我们可以写出面向对象的程序,不意味着我们可以很好的实现代码复用,弹性维护,不意味着我们可以实现在维护、扩展基础上的代码复用。一把刀,可以使你制敌于无形而于江湖扬名,也可以只是一把利刃而使你切菜平静。Java,就是这把刀,它的威力取决于你使用的方式。当我们陷入无尽无止重复代码的泥沼,当我们面临牵一发而动全身的维护恶梦, 你应该想起“设计模式”这个行动秘笈。面向对象的精义,看似平淡,其实要经过艰苦实践才能成功。而构造OO系统的隐含经验于是被前人搜集而成并冠以“设计模式”之名。我们应该在编码行动初始就携带以它。接下来,让我们步“四人组”先行者之后,用中国文字、用实际案例领略模式于我们代码焕然一新的改变:

1.       模式定义

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

2.       问题缘起

当涉及至代码维护时,为了复用目的而使用继承,结局并不完美。对父类的修改,会影响到子类型。在超类中增加的方法,会导致子类型有该方法,甚至连那些不该具备该方法的子类型也无法免除。

模拟鸭子的简单应用

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

示例,一个鸭子类型:

public abstract class Duck {

//所有的鸭子均会叫以及游泳,所以父类中处理这部分代码
  public void quack() {

System.out.println("Quack");

}

public void swim() {

System.out.println("All ducks float, even decoys.");

}

//因为每种鸭子的外观是不同的,所以父类中该方法是抽象的,由子类型自己完成。

public abstract void display();

}

}

public class MallardDuck extends Duck {

//野鸭外观显示为绿头

public void display() {

System.out.println("Green head.");

}

}

public class RedHeadDuck extends Duck {

//红头鸭显示为红头

public void display() {

System.out.println("Red head.");

}

}

public class RubberDuck extends Duck {

//橡皮鸭叫声为吱吱叫,所以重写父类以改写行为

public void quack() {

System.out.println("Squeak");

}

//橡皮鸭显示为黄头

public void display() {

System.out.println("Yellow head.");

}

}

还是上面的鸭子游戏,如果我们想让鸭子飞起来呢?我们来看看使用继承方法在Duck中加入飞行动作之后鸭子的代码

public abstract class Duck {

//所有的鸭子均会叫以及游泳,所以父类中处理这部分代码
  public void quack() {

System.out.println("Quack");

}

public void swim() {

System.out.println("All ducks float, even decoys.");

}

//因为每种鸭子的外观是不同的,所以父类中该方法是抽象的,由子类型自己完成。

public abstract void display();

}

public void fly() {

System.out.println("flying.");//加入的飞行动作

}

}

//其他鸭子子类代码如上,略。

上述代码,初始实现得非常好。现在我们如果给Duck.java中加入fly()方法的话,那么在子类型中均有了该方法,于是我们看到了会飞的橡皮鸭子,你看过吗?当然,我们可以在子类中通过空实现重写该方法以解决该方法对于子类型的影响。但是父类中再增加其它的方法呢?

(你也许想到了另一种方法,在会飞的鸭子类里才添加该方法不就可以了?)

代码修改如下:

public class MallardDuck extend Duck{

public void display(){

System.out.println("Green head.");

// 外观是绿色的

}

}

public class RubberDuck extends Duck {

//橡皮鸭叫声为吱吱叫,所以重写父类以改写行为

public void quack() {

System.out.println("Squeak");

}

//橡皮鸭显示为黄头

public void display() {

System.out.println("Yellow head.");

public void fly{

//什么也不做

}

}

这样我们真实现了确实能飞的鸭子才可以飞起来了,看起来主意不错!问题到这儿似乎得到了解决

但我们现在有了一种新的鸭子,诱铒鸭(不会飞也不会叫),看来需要这样来写

public class DecoyDuck extend Duck{

public void quack(){

//覆盖,变成什么也不做

}

public void display(){

//诱饵鸭

System.out.println("DecoyDuck.");

}

public void fly(){

//覆盖,变成什么也不做

}

}

我们来想一下,每当有新的鸭子子类出现或者鸭子新的特性出现,你就不得不被迫在Duck类里添加并在所有子类里检查可能需要覆盖fly()和quark()...这简直是无穷尽的恶梦。

通过继承在父类中提供行为,会导致以下缺点:

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

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

c.       改变会牵一发动全身,造成部分子类型不想要的改变;

所以,我们需要一个更清晰的方法,让某些(而不是全部)鸭子类型可飞或可叫。让鸭子的特性能有更好的扩展性。

我们试着用一下接口的方式怎么样?把fly()取出来,放进一个Flyable接口中,这样只有会飞的鸭子才实现这个接口,当然我们也可以照此来设计一个Quackbable接口,因为不是所有的鸭子都会叫,也只让会叫的鸭子才去实现这个接口。

public abstract class Duck {

//将变化的行为 fly() 以及quake()从Duck类中分离出去定义形成接口,有需求的子类中自行去实现

public void swim() {

System.out.println("All ducks float, even decoys.");

}

public abstract void display();

}

}

//变化的 fly() 行为定义形成的接口

public interface FlyBehavior {

void fly();

}

//变化的 quack() 行为定义形成的接口

public interface QuackBehavior {

void quack();

}

//野鸭子会飞以及叫,所以实现接口FlyBehavior, QuackBehavior

public class MallardDuck extends Duck implements FlyBehavior, QuackBehavior{

public void display() {

System.out.println("Green head.");

}

public void fly() {

System.out.println("Fly.");

}

public void quack() {

System.out.println("Quack.");

}

}

//红头鸭子会飞以及叫,所以也实现接口FlyBehavior, QuackBehavior

public class RedHeadDuck extends Duck implements FlyBehavior, QuackBehavior{

public void display() {

System.out.println("Red head.");

}

public void fly() {

System.out.println("Fly.");

}

public void quack() {

System.out.println("Quack.");

}

}

//橡皮鸭不会飞,但会吱吱叫,所以只实现接口QuackBehavior

public class RubberDuck extends Duck implements QuackBehavior{

//橡皮鸭叫声为吱吱叫

public void quack() {

System.out.println("Squeak");

}

//橡皮鸭显示为黄头

public void display() {

System.out.println("Yellow head.");

}

}

但这个方法和上面提到的在子类里去实现fly一样笨,如果几十种都可以飞,你得在几十个鸭子里去写上一样的fly(),如果一旦这个fly有所变更,你将不得不找到这几十个鸭子去一个一个改它们的fly()方法。

你可能有些气恼了,这样也不太正确,那样也不太正确,谁来告诉我怎么样弄呢?还有没有更好的办法了呢?

让我们先来总结一下:我们知道了使用继承有一些缺失, 因为改变鸭子的行为会影响所有种类的鸭子,而这并不恰当。Flyable与Quackable接口一开始似乎还挺不错, 解决了问题( 只有会飞的鸭子才继承Flyable) , 但是Java的接口不具有实现代码, 所以继承接口无法达到代码的复用。这意味着:无论何时你需要修改某个行为,你必须得往下追踪并修改每一个定义此行为的类。

让我们来想一下策略模式的第一原则:

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

按照上述设计原则,我们重新审视之前的Duck代码。

1) 分开变化的内容和不变的内容

Duck类中的行为 fly(), quack(), 每个子类型可能有自己特有的表现,这就是所谓的变化的内容。

Duck类中的行为 swim() 每个子类型的表现均相同,这就是所谓不变的内容。

我们将变化的内容从Duck()类中剥离出来单独定义形成接口以及一系列的实现类型。将变化的内容定义形成接口可实现变化内容和不变内容的剥离。其实现类型可实现变化内容的重用。这些实现类并非Duck.java的子类型,而是专门的一组实现类,称之为"行为类"。由行为类而不是Duck.java的子类型来实现接口。这样,才能保证变化的行为独立于不变的内容。于是我们有:

我们利用接口代表每个行为,比方说, FlyBehavior与QuackBehavior,而行为的每个实现都必须实现这些接口之一。所以这次鸭子类不会负责实现Flying与Quacking接口,而是由其它类专门实现FlyBehavior与QuackBehavior, 这就称为“行为”类。由行为类实现行为接口,而不是由Duck类实现行为接口。

这样的作法迥异于以往,以前的作法是:行为是继承

自Duck超类的具体实现而来,或是继承某个接口并由子类

自行实现而来。这两种作法都是依赖于“实现”,我们被实现

绑得死死的,没办法更改行为(除非写更多代码)。

变化的内容:

//变化的 fly() 行为定义形成的接口

public interface FlyBehavior {

void fly();

}

//变化的 fly() 行为的实现类之一

public class FlyWithWings implements FlyBehavior {

public void fly() {

System.out.println("I'm flying.");

}

}

//变化的 fly() 行为的实现类之二

public class FlyNoWay implements FlyBehavior {

public void fly() {

System.out.println("I can't fly.");

}

}

//变化的 quack() 行为定义形成的接口

public interface QuackBehavior {

void quack();

}

//变化的 quack() 行为实现类之一

public class Quack implements QuackBehavior {

public void quack() {

System.out.println("Quack");

}

}

//变化的 quack() 行为实现类之二

public class Squeak implements QuackBehavior {

public void quack() {

System.out.println("Squeak.");

}

}

//变化的 quack() 行为实现类之三

public class MuteQuack implements QuackBehavior {

public void quack() {

System.out.println("<< Slience >>");

}

}

通过以上设计,fly()行为以及quack()行为已经和Duck.java没有什么关系,可以充分得到复用。而且我们很容易增加新的行为, 既不影响现有的行为,也不影响Duck.java。但是,大家可能有个疑问,就是在面向对象中行为不是体现为方法吗?为什么现在被定义形成类(例如Squeak.java)?在OO中,类代表的"东西"一般是既有状态(实例变量)又有方法。只是在本例中碰巧"东西"是个行为。既使是行为,也有属性及方法,例如飞行行为,也需要一些属性记录飞行的状态,如飞行高度、速度等。

2) 整合变化的内容和不变的内容

public abstract class Duck {

//将行为类声明为接口类型,降低对行为实现类型的依赖

FlyBehavior flyBehavior;

QuackBehavior quackBehavior;

public void performFly() {

//不自行处理fly()行为,而是委拖给引用flyBehavior所指向的行为对象

flyBehavior.fly();

}

public void performQuack() {

quackBehavior.quack();

}

public void swim() {

System.out.println("All ducks float, even decoys.");

}

public abstract void display();

}

Duck.java不关心如何进行 fly()以及quack(), 这些细节交由具体的行为类完成。

public class MallardDuck extends Duck{

public MallardDuck() {

flyBehavior=new FlyWithWings();

quackBehavior=new Quack();

}

public void display() {

System.out.println("Green head.");

}

}

测试类:

public class DuckTest {

public static void main(String[] args) {

Duck duck=new MallardDuck();

duck.performFly();

duck.performQuack();

}

}

测试结果略!

类图结构如下:

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

这是一个很重要的技巧。其实是使用了策略模式中的第三个设计原则:

多用组合,少用继承。

当然我们也可以动态设定行为:在父类Duck.java中增加设定行为类型的setter方法,接受行为类型对象的参数传入。为降低耦合,行为参数被声明为接口类型。这样,即便在运行时,也可以通过调用这两种方法以改变行为。

public abstract class Duck {

//在刚才Duck.java中加入以下二个方法。

public void setFlyBehavior(FlyBehavior flyBehavior) {

this.flyBehavior=flyBehavior;

}

public void setQuackBehavior(QuackBehavior quackBehavior) {

this.quackBehavior=quackBehavior;

}

//其它方法同,省略...

}

测试类:

public class DuckTest {

public static void main(String[] args) {

Duck duck=new MallardDuck();

duck.performFly();

duck.performQuack();

duck.setFlyBehavior(new FlyNoWay());

duck.performFly();

}

}

策略模式——鸭子游戏相关推荐

  1. 设计模式解读之一: 策略模式——鸭子游戏

    设计模式解读之一: 策略模式--鸭子游戏 当我们掌握了Java的语法,当我们了解了面向对象的封装.继承.多态等特性,当我们可以用Swing.Servlet.JSP技术构建桌面以及Web应用,不意味着我 ...

  2. [策略模式]在游戏开发中的应用

    设计模式中的每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心.这样,你就能一次又一次地使用该方案而不必做重复劳动. 设计模式在类间关系这个粒度上给出常见问题的解决方案.属于 ...

  3. 策略模式在游戏开发中的应用

    策略模式 在策略模式中,一个类的行为或者其算法可以在运行时更改.这种类型的设计模式属于行为模式.在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象而改变的Contenxt(上下文对象),策 ...

  4. android策略模式_Android游戏开发–设计游戏实体–策略模式

    android策略模式 在这一部分中,我将尝试解释我对好的游戏设计元素的理解. 我将在示例中使用droid,并编写基本的战斗模拟器脚本以查看其行为. 问题: 我指挥一个机器人,我想消灭敌人. 再次面对 ...

  5. 设计模式之策略模式(strategy)--游戏角色使用武器

    策略模式:定义了算法族,并且让算法之间可以相互替换,它可以将算法实现和算法的使用客户独立. 转载于:https://www.cnblogs.com/beyondwcm/archive/2007/11/ ...

  6. C++设计模式之策略模式(行为型模式)

    学习软件设计,向OO高手迈进! 设计模式(Design pattern)是软件开发人员在软件开发过程中面临的一般问题的解决方案. 这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来 ...

  7. 设计模式之策略模式(实例+Demo)

    马上要放清明小长假了,各位都想好去哪里玩了没?清明前的周日,整理下发型,梳理好心情,发个文章,再思考下清明去哪玩,哈哈哈. 不多废话,切入正题: 你从本文主要能了解到如下几点:1. 为什么要写这篇文章 ...

  8. 设计模式随笔系列:鸭子-策略模式(Strategy)

    鸭子-策略模式(Strategy) 前言 万事开头难,最近对这句话体会深刻!这篇文章是这个系列正式开始介绍设计模式的第一篇,所以肩负着确定这个系列风格的历史重任,它在我脑袋里默默地酝酿了好多天,却只搜 ...

  9. 游戏设计模式思考:“穿越火线”中的“策略模式”

    前言:在前段时间陆陆续续在博客园更新了一系列关于重构的文章.在重构我们既有的代码时,往往会用到设计模式.在之前重构系列的文章中,我们在重构时用到了"工厂模式"."策略模式 ...

  10. 设计模式随笔系列:鸭子-策略模式(Strategy)[原]

    原文地址为: 设计模式随笔系列:鸭子-策略模式(Strategy)[原] 鸭子-策略模式(Strategy) 前言 万事开头难,最近对这句话体会深刻!这篇文章是这个系列正式开始介绍设计模式的第一篇,所 ...

最新文章

  1. 随手记——大整数运算模板(进化史)
  2. Ruby Fiber指南(三)过滤器
  3. js中while死循环语句_Java系列教程day06——循环语句
  4. 使用WPA Supplicant在Ubuntu 18.04/19.04上从终端连接到Wi-Fi的方法
  5. 链接在HTML的英文,英文:A链接标记ie下会自动补全href_HTML/Xhtml_网页制作
  6. JavaWeb笔记05-解决线程安全问题
  7. ASP.NET (C#开发环境)Request对象 之 ServerVariables集合
  8. python爬虫案例——csdn数据采集
  9. 2020年“华为杯”中国研究生数学建模竞赛
  10. 计算机文件及文件夹管理实验报告,计算机实验报告
  11. qstring 字符串查找_怎样用QT查找字符串并标记要查找的内容
  12. virtual box 静态ip设置
  13. 一个自动化专业的工控人自学编程之路
  14. 如何区分前端bug还是后端bug?
  15. JavaScript高级程序设计(第4版)学习随笔【第六章】
  16. vue-router 面试题
  17. 在gmail中使用邮件模板功能
  18. [渝粤教育] 西南科技大学 房屋建筑学 在线考试复习资料
  19. 基于C#和Sql Server的网上书店管理系统
  20. 图解MySQL 内连接、外连接、左连接、右连接、全连接……太多了

热门文章

  1. 道士后期时的技能运用有哪些
  2. WORD分节、分页、设不同页码页眉的方法
  3. 康师傅矿物质水黑幕:水源竟是自来水
  4. 致信息安全专业同学的一封信
  5. Cocos2d-x游戏引擎实战开发炸弹超人项目教程 全套下载 1至6课
  6. 计算机键盘正确指法,键盘指法,详细教您盲打及快速打字指法练习的步骤
  7. windows 内网域电脑无法ntp时间同步
  8. 鼠标自动不停地按右键
  9. 2020年中国水利行业发展状况及未来发展趋势分析[图]
  10. 机顶盒系统升级服务器地址,tvbox