设计模式解读之一: 策略模式——鸭子游戏
当我们掌握了Java的语法,当我们了解了面向对象的封装、继承、多态等特性,当我们可以用Swing、Servlet、JSP技术构建桌面以及Web应用,不意味着我们可以写出面向对象的程序,不意味着我们可以很好的实现代码复用,弹性维护,不意味着我们可以实现在维护、扩展基础上的代码复用。一把刀,可以使你制敌于无形而于江湖扬名,也可以只是一把利刃而使你切菜平静。Java,就是这把刀,它的威力取决于你使用的方式。当我们陷入无尽无止重复代码的泥沼,当我们面临牵一发而动全身的维护恶梦, 你应该想起“设计模式”这个行动秘笈。面向对象的精义,看似平淡,其实要经过艰苦实践才能成功。而构造OO系统的隐含经验于是被前人搜集而成并冠以“设计模式”之名。我们应该在编码行动初始就携带以它。接下来,让我们步“四人组”先行者之后,用中国文字、用实际案例领略模式于我们代码焕然一新的改变:
1.       模式定义
把会变化的内容取出并封装起来,以便以后可以轻易地改动或扩充部分,而不影响不需要变化的其他部分;
2.       问题缘起
当涉及至代码维护时,为了复用目的而使用继承,结局并不完美。对父类的修改,会影响到子类型。在超类中增加的方法,会导致子类型有该方法,甚至连那些不该具备该方法的子类型也无法免除。
模拟鸭子的简单应用
Joe(乔)上班的公司做了一套相当成功的模拟鸭子游戏SimUDuck,游戏中出现各种鸭子, 一边游泳戏水, 一边呱呱叫。此系统的内部设计使用了标准的OO技术,设计了一个鸭子超类(Superclass),并让各种鸭子继承此超类。
示例,一个鸭子类型:
publicabstractclass Duck {
 //所有的鸭子均会叫以及游泳,所以父类中处理这部分代码
 publicvoid quack() {
System.out.println("Quack");
 }
 publicvoid swim() {
    System.out.println("All ducks float, even decoys.");
 }
 //因为每种鸭子的外观是不同的,所以父类中该方法是抽象的,由子类型自己完成。
 publicabstractvoid display();
      }
}
publicclass MallardDuck extends Duck {
 //野鸭外观显示为绿头
 publicvoid display() {
System.out.println("Green head.");
 }
}
publicclass RedHeadDuck extends Duck {
 //红头鸭显示为红头
 publicvoid display() {
    System.out.println("Red head.");
 }
}
publicclass RubberDuck extends Duck {
 //橡皮鸭叫声为吱吱叫,所以重写父类以改写行为
 publicvoid quack() {
    System.out.println("Squeak");
 }
 //橡皮鸭显示为黄头
 publicvoid display() {
    System.out.println("Yellow head.");
 }
}
还是上面的鸭子游戏,如果我们想让鸭子飞起来呢?我们来看看使用继承方法在Duck中加入飞行动作之后鸭子的代码
publicabstractclass Duck {
 //所有的鸭子均会叫以及游泳,所以父类中处理这部分代码
 publicvoid quack() {
System.out.println("Quack");
 }
 publicvoid swim() {
    System.out.println("All ducks float, even decoys.");
 }
 //因为每种鸭子的外观是不同的,所以父类中该方法是抽象的,由子类型自己完成。
 publicabstractvoid display();
      }
  publicvoid fly() {
    System.out.println("flying.");//加入的飞行动作
 }
}
//其他鸭子子类代码如上,略。
上述代码,初始实现得非常好。现在我们如果给Duck.java中加入fly()方法的话,那么在子类型中均有了该方法,于是我们看到了会飞的橡皮鸭子,你看过吗?当然,我们可以在子类中通过空实现重写该方法以解决该方法对于子类型的影响。但是父类中再增加其它的方法呢?
(你也许想到了另一种方法,在会飞的鸭子类里才添加该方法不就可以了?)
代码修改如下:
public class MallardDuck extend Duck{
public void display(){
System.out.println("Green head.");
// 外观是绿色的
}
}
publicclass RubberDuck extends Duck {
 //橡皮鸭叫声为吱吱叫,所以重写父类以改写行为
 publicvoid quack() {
    System.out.println("Squeak");
 }
 //橡皮鸭显示为黄头
 publicvoid 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接口,因为不是所有的鸭子都会叫,也只让会叫的鸭子才去实现这个接口。
publicabstractclass Duck {
 //将变化的行为 fly() 以及quake()从Duck类中分离出去定义形成接口,有需求的子类中自行去实现
 publicvoid swim() {
System.out.println("All ducks float, even decoys.");
 }
 publicabstractvoid display();
}
}
//变化的 fly() 行为定义形成的接口
publicinterface FlyBehavior {
 void fly();
}
//变化的 quack() 行为定义形成的接口
publicinterface QuackBehavior {
 void quack();
}
//野鸭子会飞以及叫,所以实现接口FlyBehavior, QuackBehavior
publicclass MallardDuck extends Duck implements FlyBehavior, QuackBehavior{
 publicvoid display() {
    System.out.println("Green head.");
 }
 publicvoid fly() {
    System.out.println("Fly.");   
 }
 publicvoid quack() {
    System.out.println("Quack.");  
 }
}
//红头鸭子会飞以及叫,所以也实现接口FlyBehavior, QuackBehavior
publicclass RedHeadDuck extends Duck implements FlyBehavior, QuackBehavior{
 publicvoid display() {
    System.out.println("Red head.");
 }   
 publicvoid fly() {
    System.out.println("Fly.");  
 }
 publicvoid quack() {
    System.out.println("Quack.");   
 }  
}
//橡皮鸭不会飞,但会吱吱叫,所以只实现接口QuackBehavior
publicclass RubberDuck extends Duck implements QuackBehavior{
 //橡皮鸭叫声为吱吱叫
 publicvoid quack() {
    System.out.println("Squeak");
 }
 //橡皮鸭显示为黄头
 publicvoid 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() 行为定义形成的接口
publicinterface FlyBehavior {
 void fly();
}
//变化的 fly() 行为的实现类之一
publicclass FlyWithWings implements FlyBehavior {
 publicvoid fly() {
System.out.println("I'm flying.");
 }
}
//变化的 fly() 行为的实现类之二
publicclass FlyNoWay implements FlyBehavior {
 publicvoid fly() {
    System.out.println("I can't fly.");
 }
}
//变化的 quack() 行为定义形成的接口
publicinterface QuackBehavior {
 void quack();
}
//变化的 quack() 行为实现类之一
publicclass Quack implements QuackBehavior {
 publicvoid quack() {
System.out.println("Quack");
 }
}
//变化的 quack() 行为实现类之二
publicclass Squeak implements QuackBehavior {
 publicvoid quack() {
    System.out.println("Squeak.");
 }
}
//变化的 quack() 行为实现类之三
publicclass MuteQuack implements QuackBehavior {
 publicvoid quack() {
    System.out.println("<< Slience >>");
 }
}
通过以上设计,fly()行为以及quack()行为已经和Duck.java没有什么关系,可以充分得到复用。而且我们很容易增加新的行为, 既不影响现有的行为,也不影响Duck.java。但是,大家可能有个疑问,就是在面向对象中行为不是体现为方法吗?为什么现在被定义形成类(例如Squeak.java)?在OO中,类代表的"东西"一般是既有状态(实例变量)又有方法。只是在本例中碰巧"东西"是个行为。既使是行为,也有属性及方法,例如飞行行为,也需要一些属性记录飞行的状态,如飞行高度、速度等。
2) 整合变化的内容和不变的内容
publicabstractclass Duck {
 //将行为类声明为接口类型,降低对行为实现类型的依赖
 FlyBehavior flyBehavior;
 QuackBehavior quackBehavior;
 publicvoid performFly() {
//不自行处理fly()行为,而是委拖给引用flyBehavior所指向的行为对象
    flyBehavior.fly();
 }
 publicvoid performQuack() {
    quackBehavior.quack();
 }
 publicvoid swim() {
    System.out.println("All ducks float, even decoys.");
 }
 publicabstractvoid display();
}
Duck.java不关心如何进行fly()以及quack(), 这些细节交由具体的行为类完成。
publicclass MallardDuck extends Duck{
 public MallardDuck() {
flyBehavior=new FlyWithWings();
    quackBehavior=new Quack();
 }
 publicvoid display() {
    System.out.println("Green head.");
 }
}
测试类:
publicclass DuckTest {
 publicstaticvoid main(String[] args) {
Duck duck=new MallardDuck();
    duck.performFly();
    duck.performQuack(); 
 }
}
测试结果略!
类图结构如下:
如同本例一般,当你将两个类结合起来使用,这就是组合(composition)。这种作法和『继承』不同的地方在于,鸭子的行为不是继承而来,而是和适当的行为对象『组合』而来。
这是一个很重要的技巧。其实是使用了策略模式中的第三个设计原则:
多用组合,少用继承。

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

  1. 设计模式入门(策略模式)

    [0]README 0.1)本文部分文字描述转自 "head first 设计模式",旨在学习 设计模式入门(策略模式) 的基础知识: 0.2)本文章节4和5的source cod ...

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

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

  3. 设计模式 ( 十八 ) 策略模式Strategy(对象行为型)

    设计模式 ( 十八 ) 策略模式Strategy(对象行为型) 1.概述 在软件开发中也经常遇到类似的情况,实现某一个功能有多种算法或者策略,我们能够依据环境或者条件的不同选择不同的算法或者策略来完毕 ...

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

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

  5. java基础_设计模式_设计基础(小鸭子游戏)

    小鸭子游戏,是好多爱好者接触设计模式.认知设计模式概念的一个入门. 每个初学者的理解不同,我加上自己的理解大体是这样的:前提是处理大规模时,假设池塘中有10000头小鸭子,有红头鸭,野鸭子,木头鸭子等 ...

  6. Java设计模式之十一 ---- 策略模式和模板方法模式

    前言 在上一篇中我们学习了行为型模式的访问者模式(Visitor Pattern)和中介者模式(Mediator Pattern).本篇则来学习下行为型模式的两个模式,策略模式(Strategy Pa ...

  7. Head First设计模式读书笔记——策略模式

    问题描述: 目前的任务是实现一个FPS类游戏的各种角色(友军.敌军.平民和狗.猫.鸭子等动物)以及他们的各种行为(攻击.游泳等). 设计方案一 很简单,只要实现一个角色超类,将角色的各种行为放入超类中 ...

  8. 从王者荣耀看设计模式(一.策略模式)

    从王者荣耀看设计模式(策略模式) 一:简介 游戏开始前,玩家需要选择英雄,再根据所选择的阵容自由选择召唤师技能,游戏开始,玩家可以控制英雄进行普通攻击和使用召唤师技能攻击 二:策略模式 策略模式将可变 ...

  9. Head First Design Mode(2)-设计模式入门(策略模式)

    该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动! 设计模式入门: 欢迎来到设计模式世界: 我们会看到设计模式的用途和优点,再看看关键的OO原则,通过实例来了解模式是如何运作的 ...

最新文章

  1. 深刻理解Vue中的组件
  2. python处理excel文件-python读取excel文件
  3. e300氛围灯哪里调节_奥迪Q5L安装原厂32色20灯源氛围灯
  4. mysql命令gruop by报错this is incompatible with sql_mode=only_full_group_by
  5. (三十一)web 开发基础项目
  6. “忘恩负义”的浪胃仙,是个真狠人!
  7. python保存html图_如何保存“完整网页”不仅仅是使用Python的基本HTML
  8. 语音识别PPT.ppt
  9. fstream流对象形参时出现的错误问题(codeblocks+gcc)
  10. Visual Studio 2010全球发布会 上海站(图)
  11. Redis常用管理脚本
  12. 汽车线性二自由度动力学模型-simulink仿真
  13. 2022年编程语言排名,官方数据来了,让人大开眼界
  14. 如何测试计算机的运行速度,如何查看cpu运行速度
  15. [转贴]色彩调和的原理
  16. 【历史上的今天】12 月 20 日:苹果收购 NeXT;苏联超级计算机先驱诞生;《绝地求生》发布
  17. 最新Centos7.6 部署ELK日志分析系统
  18. 微信公众号发送模板通知
  19. 苹果cms怎么更换模板教程
  20. 洛谷 P1194 买礼物 (题解+代码)

热门文章

  1. Tips:error C4996: 'GetVersionExA': 被声明为已否决
  2. Proteus仿真STM32的课设实例3——汽车倒车测距提示仪
  3. python制作简易的音乐播放器(5.改进:添加歌单列表)
  4. Python反转输出正整数
  5. C语言scanf返回值怎么写,C语言 scanf 返回值
  6. C#中Split的使用
  7. ffmpeg视频小结
  8. $function(){}页面加载函数全局和局部的问题和如何给a标签或者其他标签在ajax中异步绑定事件出现异步的问题
  9. [转载] 全本张广泰——第十三回 广泰认义父 善心救江玉
  10. 汇编语言学习笔记(十二)-浮点指令