设计模式_第二篇_策略模式

本文是我通过学习《Head First 设计模式》而写。

 

作为我要描述的第一个模式,首先要说什么是设计模式,然后,用一个实例,并对这个实例不断的改进,引出策略模式。

 

与其空泛地给出一堆描述,倒不如给出通过一个实例、一个情景,来引出你要说的东西。因为,人们对于事物的理解,越是具体、形象,就越容易,而但凡理论性、抽象性的东西,你无论怎样描述它,也只是用一个概念去解释另一个概念。对于一个没有多少项目经验的人来说,着实不易。《Head First设计模式》这本书做得就很好。

 

上面这句话,有三个关键词:情境、问题和解决方案。所谓“情境”是应用某个设计模式的情况;所谓“问题”是你在某个情境下达到的目标,但也可以是某个情境下的约束;而“解决方案”是你所追求的,一个通用的设计,用来解决约束,达到目标。

 

需要明确几点:

  • 项目不一定非要使用设计模式。使用设计模式只是为了让程序更加灵活、更容易维护,尤其是当需求变化的时候。但模式会无形中增加程序的复杂性,这是我们所不期望的。我们期望,用简单、清晰的方法来解决复杂的问题,使程序更容易让人理解,而不是“机器”——简单的想1一样。任何一个程序员都能写出让机器理解的代码,但只有一个熟练的程序员才能写出让绝大多数人都能理解的程序。
  • 设计模式初学者的误区,包括我自己,总是希望为自己的程序找到一个模式(虽然这个初衷是想练习这些模式)。有一句经典的话:“我要为 'Hello World' 找一个设计模式”。
  • 你可以不会使用设计模式,但你绝对不能不知道它的存在。
  • 模式本身是不存在的,它只是在长期的项目实践中的一种经验。你可以有自己的模式,并发布出去。但你的模式必须有三次成功的案例,并经过其他人的评价后才可能被列入模式目录。

 

下面,通过一个实例,来说明策略模式。

假如,你所在公司要求你制作一个模拟各种鸭子飞行的程序。你立刻就会想到,所有的鸭子都会游泳、都会叫,那么,可以设计一个鸭子的抽象类(Duck),然后让所有的鸭子,如绿头鸭(MallardDuck)或红头鸭(RedHeadDuck)都继承这个抽象类。如图1所示:

01其中,

  • Duck 类是一个抽象类。MallardDuck 类和 RedHeadDuck 继承 Duck 类。
  • 所有的鸭子都会游泳、都会叫,因此,由 Duck 类来实现 quack() 和 swim() 方法。
  • 每个鸭子都有自己的 display() 方法,因此,Duck 类中的 display() 是抽象abstract方法。

这个设计看似不错,但有什么缺点?现在,公司要求——鸭子要能飞。你很容易想到,只要在 Duck 类中,加入并实现 fly() 方法,那么,Duck 类的子类都可以继承这个方法。如图2所示:

02

但问题也来了——不会飞的橡皮鸭 RubberDuck 到处飞。因此,在抽象类 Duck 中加入 fly() 方法后,其所有子类也就都具备了 fly() 方法,连不该具备 fly() 方法的子类也无法避免。解决的方法也很容易想到——覆盖子类的 fly() 方法。

但这样做也有问题啊!以后,要是再加入诱饵鸭 DecoyDuck?诱饵鸭即不会飞,也不会叫。这样,除了要覆盖 fly() 方法,还要覆盖 quack() 方法。一个公司的产品都会定期更新,加入其他种类的鸭子。这样,你不得不每次都要检查 fly() 和 quack() 方法。因此,这也不是一个好的解决方案。

采用接口总可以了吧!如图3所示:

03其中,

  • Duck 类仍然是个抽象类。所有种类的鸭子,如绿头鸭(MallardDuck)、红头鸭(RadHeadDuck)、橡皮鸭(RubberDuck)和诱饵鸭(DecoyDuck),都要继承 Duck 类。
  • Duck 类的子类必须继承 Flyable 和 Quackable 接口,并实现 fly() 和 quack() 方法。

对于这个设计,虽然不会出现,橡皮鸭(RubberDuck)到处飞的情况,但代码无法复用。因为,显然绿头鸭和红头鸭都会飞都会叫,即它们的 fly() 和 quack() 方法的实现一样,而橡皮鸭和诱饵鸭都不会飞,即 fly() 方法的实现一样等等——这只是从一个“恶梦”跳到另一个“恶梦”而已。

那么究竟应该怎么做?答案是:找出应用中可能需要变化的地方,把它们独立出来,不要和那些不需要变化的代码混在一起。在本情景中,将鸭子的“飞”和“叫”的行为从抽象类 Duck 中分离出来。如图4所示:

04 其中,

  • Duck 类还是一个抽象类。FlyBehavior 和 QuackBehavior 接口聚合到 Duck 类,作为 Duck 类的属性。
  • ModelDuck 类继承 Duck 类。该类重构的 display() 方法是所有鸭子都具备的共同需求。因为,无论什么种类的鸭子,最终都是要显示出来,或是一边飞,一边叫;或是不飞(也许会飞,也许不会飞),只叫;或是一边游泳,一边叫……等等。我在开发时,一般将像 ModelDuck 这样的类,命名为 BaseDuck。
  • 类 FlyNoWay、FlyWithWings、FlyRocketPowered 分别继承接口 FlyBehavior,实现里边的方法——“不会飞”、“用翅膀飞”和“坐火箭飞”。继承 QuackBehavior 接口的三个类同理。

这个设计究竟好在哪里?我觉得有如下几点:

  • 将鸭子“飞”和“叫”的行为(方法)从抽象类Duck中分离出来,这样,Duck类以及其子类就不需要再知道“飞”和“叫”是如何实现的,有利于加入新的鸭子子类,而完全不会影响现有的代码;
  • 当需要添加新的鸭子“飞”的行为或是新的“叫”的行为时,只要继承相应的接口即可,完全不会影响现有的代码;
  • 为了使程序能在运行时改变鸭子“飞行”和“叫”的状态,让程序更加灵活,在Duck类中添加两个set方法和perform方法,分别设置鸭子“飞行”和鸭子“叫”的状态,然后再让perform方法执行这些鸭子的行为;
  • 另外,通常情况下,在实际的项目中,当我们需要添加新类型的鸭子时,不会直接继承Duck,而是用一个基类先继承这个抽象类,比如用ModleDuck类继承Duck类,再让新的鸭子类继承这个基类,这样,会使程序变得更加灵活。

 

从以上在对策略模式的分析中,可以得到如下经验和结论:

  • 如果为了代码复用而使用继承,结局往往并不完美;
  • 针对接口编程;
  • 将程序变化的部分和不变化的部分分离。

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

转载于:https://www.cnblogs.com/liuning8023/archive/2011/08/25/2153738.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/422797.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C语言进阶深度学习记录】二十六 C语言中的字符串与字符数组的详细分析

之前有一篇文章是学习了字符和字符串的,可以与之结合学习:【C语言进阶深度学习记录】十二 C语言中的:字符和字符串 文章目录1 字符串的概念1.1 字符串与字符数组1.2 字符数组与字符串代码分析2 字符串字面量2.1 字符串字面量的本质的代码分析…

3天搞定的小型B/S内部管理类软件定制开发项目【软件开发实战10步骤详解】

2010-10-07 21:39 by 通用权限组件源码, 16580 visits, 收藏, 编辑 十一休假,杭州西湖边逛了一圈只能用人山人海来形容,浙大紫金港校区也逛了一圈风景如画,建设得真不错很棒,假期就去了这2个地方,然后在家里陪老婆、看…

【C语言进阶深度学习记录】二十八 数组指针与指针数组的分析

数组指针与指针数是非常重要的概念。面试中也是经常会被问到的 文章目录1 数组的类型1.1 定义数组的类型2 数组指针2.1 数组类型和数组指针的代码分析3 指针数组3.1 指针数组代码案例分析4 总结1 数组的类型 C语言中的数组有自己特定的类型。比如 int a[5]; 数组a…

【C语言进阶深度学习记录】二十九 main函数与命令行参数

文章目录1 main函数的返回值2 main函数的参数2.1 main函数的参数的代码案例分析3 main函数不一定是程序中第一个执行的函数4 总结1 main函数的返回值 main函数是操作系统调用的函数操作系统总是将main函数的返回值作为程序的退出状态main函数的返回值正常来说是0,如…

【C语言进阶深度学习记录】三十 二维数组与二维指针

文章目录1 二维指针(指向指针的指针)2 二维数组3 二维数组的类型3.2 如何动态申请二维数组4 总结1 二维指针(指向指针的指针) 指针的本质是变量指针的指针是保存指针变量的地址。如下面的代码: 为什么需要指向指针的存…

【C语言进阶深度学习记录】三十一 数组作为函数参数时退化为指针

之前的学习数组的文章中,已经知道一维数组作为函数参数的时候,最终会被编译器编译为指针。今天来看看二维数组的情形 文章目录1 为什么C语言中的数组作为函数参数会退化为指针?2 二维数组作为函数参数如何退化2.1 代码案例分析(传…

使用HTMLParser模块解析HTML页面

HTMLParser是python用来解析html和xhtml文件格式的模块。它可以分析出html里面的标签、数据等等,是一种处理html的简便途径。HTMLParser采用的是一种事件驱动的模式,当HTMLParser找到一个特定的标记时,它会去调用一个用户定义的函数&#xff…

前端学习(294):rem小实例

altz转换为rem <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv"X-UA-Compatible"…

【C语言进阶深度学习记录】三十二 函数指针与使用函数指针实现回调函数

回调函数是非常重要的概念 文章目录1 函数的类型2 函数指针2.1 函数指针的使用2.2 使用函数指针实现回调函数3 总结1 函数的类型 跟以前学数组的时候是一样的&#xff0c;C语言中的数组是有自己的类型的。函数也是有自己的类型的。 函数的类型由返回值、参数的类型、参数的个…

【C语言进阶深度学习记录】三十三 C语言中动态内存分配

如何在程序运行的时候动态给程序分配内存&#xff1f; 文章目录1 动态内存分配的意义1.1 C语言中如何动态申请内存空间1.2 malloc和free的用法1.3 calloc与realloc1.31 calloc和realloc的代码案例分析2 总结1 动态内存分配的意义 在C语言中&#xff0c;一切操作都是基于内存的…

java并发实战

推荐一个Java并发编程实战的学习专栏。此专栏为极客时间收费专栏。 学习交流加 个人qq&#xff1a; 1126137994个人微信&#xff1a; liu1126137994学习交流资源分享qq群 &#xff1a; 962535112 对于一个 Java 程序员而言&#xff0c; 能否熟练掌握并发编程是判断他优秀与否的…

a critical review of preetham skylight model 笔记

也是为了试用下Xmind。 上图是我用Xmind作的某篇文章的笔记。 感想&#xff1a; 1. 之以一直觉得这种东西没多大用处&#xff0c;回想起来大概是因为那时没有太多应用场景。 2. 如果留心&#xff0c;可以把许多事情做得更漂亮、更容易&#xff0c;这也是工具的用途。 贴一下软件…

【C语言进阶深度学习记录】三十四 C语言实现内存泄漏检测模块

上一篇文章学习了malloc系列的三个函数的使用。众所周知malloc的使用很容易导致内存泄漏。本文的目的就是使用C语言来实现内存泄漏检测模块&#xff0c;来帮忙自动检测我们写的程序中是否出现内存泄露。 文章目录1 内存泄露检测模块的实现原理1.1 各个函数模块的设计1.2 模块整…

重学前端----前端知识系统学习推荐专栏

推荐一个前端知识学习专栏。此专栏为极客时间收费专栏。 学习交流加 个人qq&#xff1a; 1126137994个人微信&#xff1a; liu1126137994学习交流资源分享qq群&#xff1a; 962535112 文章目录1 概述2 专栏内容2.1 模块一&#xff0c;JavaScript2.2 模块二&#xff0c;HTML 和 …

【C语言进阶深度学习记录】三十五 程序中的堆、栈以及静态存储区(数据区)

学习交流加 个人qq&#xff1a; 1126137994个人微信&#xff1a; liu1126137994学习交流资源分享qq群&#xff1a; 962535112 在我之前学习底层的知识的时候&#xff0c;也写过相关的内容。可以对比的学习&#xff1a;【软件开发底层知识修炼】二十 深入理解可执行程序的结构&a…

【C语言进阶深度学习记录】三十六 程序与进程的区别(程序的内存布局)

上一篇文章学了堆&#xff0c;栈以及静态存储区。它们实际上都是针对进程来说的。那么程序与进程有什么区别呢&#xff1f; 本文不细讲程序与进程。 1 程序与进程 1.1 什么是程序 写完的.c文件是源文件。也叫源代码。 将源代码编译后&#xff0c;会生成可执行文件程序&#…

使用Cucumber+Rspec玩转BDD(2)——邮件激活

使用CucumberRspec玩转BDD(2)——邮件激活 2009年3月2日 星期一 ### 温故知新 ###前面我们已经完成了新用户注册功能的开发&#xff0c;为了方便我们后面的开发工作且不扰乱之前的工作成果&#xff0c;我们先将这份源代码归档并做个标记。为了获得更好的阅读体验&#xff0c;读…

【C语言进阶深度学习记录】三十七 C/C++中造成程序内存错误的原因(野指针)

什么是野指针&#xff1f; 指针变量存的地址是一块非法内存地址。进而形成野指针。但是需要注意一点&#xff0c;野指针不是NULL指针。 文章目录1 野指针的概念1.1 野指针代码案例初探2 如何避免野指针2.1 野指针代码案例分析进阶3 总结1 野指针的概念 野指针变量中的值是非法…

算法补充 2011-9-12

设计一个算法将顺序表L中所有小于0的整数放前半部分&#xff0c;大于等于0的整数放在后半部分二叉树的删除设计一个算法将顺序表L中所有小于0的整数放前半部分&#xff0c;大于等于0的整数放在后半部分 思路:从左侧找出>0的元素&#xff0c;从右侧找出<0的元素,然后进行交…

【C语言进阶深度学习记录】三十八 C/C++语言中的函数声明与函数定义

文章目录1 函数的声明和定义1.1 代码分析2 总结1 函数的声明和定义 声明的意义在于告诉编译器程序单元的存在。只是告诉编译器它存在但是不在声明这里定义&#xff0c;有可能在当前文件中的其他地方或者其他文件中定义。如果在它还没有被定义之前就使用它&#xff0c;会导致编…