继承可以在复用父类代码的情况下扩展父类的功能,但同时继承增加了对象之间的耦合度,所以要慎用继承。那么有没有既能扩展父类的功能,又能使对象间解耦的方法呢?答案是肯定的,这就是我们今天要学习的装饰者模式。待会你会看到我会用装饰者模式组装一台电脑。不过现在还是先把书上的例子学习一下。

学习书上的例子

Starbuzz咖啡店的系统需要更新一下,他们原来的系统是这样的:

可以看到,顾客购买饮料时有具体的子类提供并返回饮料的价格。购买咖啡时,可以在其中加入一些调料,比如蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha,也就是巧克力风味)或覆盖奶泡。Starbuzz会根据所加入的调料收取不同的费用。那么这怎么做呢?也许我们会想到这样的几种解决方法:

1.列出所有的饮料和调料的组合方式。好吧,我想没有人会这么做,这样组合情况太多,用书上的一种说法叫“类爆炸”。

2.在Beverage类中设置各种调料的boolean值以表示是否需要这种调料,如boolean milk, 然后用cost计算出加入各种调料后的价格,然后在子类的cost方法中调用父类的cost方法并加上饮料本身的价格。

分析第2中情况:听起来还不错,但一旦加入新的调料就得修改Beverage类。如果研究出了一种新型的饮料,里面的某些调料可能并不合适,这样导致了饮料拥有加入不合适的调料的方法,这样有什么后果,这样可能会出现一些不好的后果,我们在策略模式一章中就受到教训了(橡皮鸭会飞)。还有如果我想要双倍摩卡了,怎么办?

尝试解决问题

现在问题已经出现了,怎么解决呢?人们购买咖啡很自然的状态可能是这样的:先购买一杯咖啡,然后想要什么调料就购买什么调料,想要多少就购买多少。于是我们想这样是否能解决问题:先创建一杯咖啡,然后创建调料并和咖啡动态的组合在一起。通过动态地组合对象,可以写新的代码添加新功能,而无须修改现有代码。既然没有改变现有代码,那么引进bug或产生意外副作用的机会将大幅度减少。这就需要用到装饰者模式。

软件设计原则

开放-关闭原则:类应该对扩展开放,对修改关闭。

定义装饰者模式

动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

让Starbuzz的饮料符合装饰者模式

需要解释一个这个图:Beverage类是饮料的抽象类,所有的饮料都要继承自这个类,它有一个获得描述的方法(getDescription())和一个计算价格的抽象方法cost()。4个具体的咖啡类(如Espresso的)继承了Beverage类并重写了cost方法。CondimentDecorator类是一个抽象的装饰类,也继承了Beverage,Milk等是具体的装饰类,在计算价格时在饮料的价格上再加上调料的价格,在获得描述时在描述饮料的时候加上了调料的描述,所以说装饰者增加了行为到被装饰者的对象上。

前面说过要慎用继承,装饰者模式是通过动态的组合对象来添加新的功能,那么这里的CondimentDecorator类为什么继承了Beverage类呢?其实,这里使用继承并不是为了继承行为,而是为了保持类型匹配。也就是说在需要被装饰者类型的时候可以用装饰者类型替换。这样可能不太明白,我举个CondimentDecorator没有继承Beverage的例子:假如顾客点了一杯浓缩咖啡Espresso,需要加入的调料为牛奶Milk和摩卡Mocha,我们需要先创建一杯Espresso从而得到espresso对象,然后将espresso对象作为参数传入创建Milk对象,CondimentDecorator milk = new Milk(espresso); 这样就在浓缩咖啡中加入了牛奶,可是还需要加入摩卡啊,这样是不能同时加入牛奶和摩卡的,所以CondimentDecorator继承Beverage是为了保持类型匹配。

开始工作

首先需要抽象的饮料和具体的饮料

Beverage:

 View Code

浓缩咖啡Espresso:

 View Code

具体的饮料只具有自己的描述和价格,其他具体的饮料见下面附录A

接着添加抽象装饰者和具体装饰者,具体的调料装饰者将自己的价格和描述附加到饮料的价格和描述上。

抽象调料装饰者CondimentDecorator:

 View Code

具体调料装饰者摩卡Mocha:

 View Code

其他具体调料见下面附录B

其实可以看到,每个具体的调料类中都要Beverage对象的引用,既然这样可以把Beverage对象引用放到CondimentDecorator类中,大家可以自己调整一下,这里我就不做调整了。我会在后面组装电脑的例子中把被装饰类的引用放到抽象装饰类中。

测试一下DecoratorTest:

 View Code

装饰者该做什么:

通过例子可以看到装饰者该做的是:装饰者该做的事就是增加行为到被包装的对象上。

jdk中的装饰者:

jdk中也有用到装饰者模式的,那就是IO流中用到了。我想每个人学习IO流的时候都比较痛苦,可能不仅是要区分字节流和字符流,而且一些装饰用的流也企图混淆我们的视线。比如说BufferedInputStream就是一个装饰流,可以用它装饰FileInputStream,所以我们最常用的应该是这样的形式:new BufferedInputStream(new FileInputStream(new File(""))); 和我们上面讲的类似,装饰流也是增加一些行为到被装饰的对象上,比如BufferedInputStream通过缓冲数组来提高性能,提供一个读取整行的readLine方法来进行扩展。下面的图能让你更加了解IO流中的装饰者:

这图上列出的是字节流,字符流也是类似的。但是装饰流使IO中的类更多了,这有时会造成我们的困扰,如果非要说的话,这也算一个“缺点”;

自己写例子

学习完书上的例子,我总是想着自己举一个例子,可是要想一个符合的例子真的挺难的,这就是“书到用时方恨少”?哈哈,不扯了,看一下我自己想的例子:我要组装一台电脑,现在只有一个机箱,需要添加其他的配件,就是这里例子了。

动手组装电脑

首先需要一个电脑的抽象类Computer,有型号type和价格price2个属性,有获得组成部分comprise()和计算总价prices()2个抽象方法:

 View Code

假如现在只有一个先马的机箱作为被装饰者SAMAChassis:

 View Code

现在要网机箱中加入CPU,主板,内存等配件,将这些配件作为装饰者装饰到机箱上。需要一个装饰者的抽象类DiyDecorator:

 View Code

这里设计到上面提到的一个问题,我把Computer的引用放到了抽象类DiyDecorator中以增加代码的复用。同时也现实了comprise方法和prices方法,这样,子类只需要调用父类的构造方法即可。

说到这里,又想起来一个问题,我们知道不能创建抽象类的对象,那么,那么抽象类为什么有构造方法呢?其实,从这个例子就可以看出,抽象类的构造方法是用来实例化成员变量的。

具体的装饰类CPU:

 View Code

其他的具体装饰类我就不写了,也不写附录了,很简单。

测试一下DecoratorTest:

 View Code

小结一下

装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。装饰者模式符合开放-关闭原则:对扩展开放,对修改关闭。

读headFirst设计模式 - 装饰者模式相关推荐

  1. 设计模式——装饰者模式

    本文是阅读 Head First 设计模式--装饰者模式的总结. 这本书的教学模式很不错,个人很喜欢,由实际的案例由浅入深,循序渐进的让你明白良好的设计是多么的优雅迷人(回头看看自己的代码,WTF!) ...

  2. Python设计模式-装饰器模式

    Python设计模式-装饰器模式 代码基于3.5.2,代码如下; #coding:utf-8 #装饰器模式class Beverage():name = ""price = 0.0 ...

  3. [Head First设计模式]山西面馆中的设计模式——装饰者模式

    原文:[Head First设计模式]山西面馆中的设计模式--装饰者模式 引言 在山西面馆吃鸡蛋面的时候突然想起装饰者这个模式,觉得面馆这个场景跟书中的星巴兹咖啡的场景很像,边吃边思考装饰者模式.这里 ...

  4. 设计模式装饰者模式_装饰者模式如何拯救了我的一天

    设计模式装饰者模式 在工作中,我正在处理庞大的Java代码库,该代码库是由许多不同的开发人员在15年的时间里开发的. 并不是所有的事情都由书来完成,但是同时我通常无法重构遇到的每一个奇怪的事物. 尽管 ...

  5. 教你如何一篇博客读懂设计模式之—--原型模式

    教你如何一篇博客读懂设计模式之----原型模式 what:是什么 原型模式: 用于创建重复的对象,既不用一个属性一个属性去set和get,又不影响性能,原型模式产生的对象和原有的对象不是同一个实例,他 ...

  6. 教你如何一篇博客读懂设计模式之—--工厂模式

    一篇博客读懂设计模式之-工厂模式 工厂模式在我们日常开发的时候经常用到,相信大家都有了一定的了解,工厂模式是一种创建对象的设计模式,它提供一种创建对象的最佳方式. 主要过程是: 定义一个创建对象的接口 ...

  7. 一篇博客读懂设计模式之---委派模式

    一篇博客读懂设计模式之-委派模式 委派模式可能大家听起来不太熟悉,但是在代码开发的时候却很好用,下面从几个方面来介绍一下 what:是什么? 委派模式:顾名思义,委托其他对象或者实例来帮我们完成任务, ...

  8. 23种设计模式——装饰者模式

    文章目录 23种设计模式--装饰者模式 1.装饰者模式概述 2.装饰者模式的结构 3.装饰者模式的实现 4.装饰者模式的应用场景 23种设计模式--装饰者模式 1.装饰者模式概述 背景 有些人为了早上 ...

  9. 读书笔记---Head First 设计模式--- 装饰者模式

    读书笔记-Head First 设计模式- 装饰者模式 装饰者模式(Decorator Pattern) 装饰者模式--动态地将责任附加到对象上.若要扩展功能,装饰者提供了比继承更有弹性的替代方案. ...

  10. Go 设计模式 - 装饰器模式

    装饰模式使用对象组合的方式动态改变或增加对象行为.Go语言借助于匿名组合和非入侵式接口可以很方便实现装饰模式.使用匿名组合,在装饰器中不必显式定义转调原对象方法. 设计模式 装饰器模式 装饰器模式主要 ...

最新文章

  1. 阿里云发布第四代神龙架构云计算首次进入5微秒时延时代
  2. F5内网大二层负载均衡业务访问故障解析(CISCO OTV+LISP-MTU问题导致)
  3. yii2分页的基本使用及其配置详解
  4. SparkR:数据科学家的新利器
  5. Java一行一行的读文件和简单的写文件
  6. 深入理解JVM虚拟机(七):虚拟机字节码执行引擎
  7. mysql怎样查表的模式_mysql常用基础操作语法(四)--对数据的简单无条件查询及库和表查询【命令行模式】...
  8. python3 使用 pi3 安装软件时候,报错找不到 SSL 附解决方法
  9. 编写程序模拟“主人”喂养“宠物”的场景,利用多态的思想!!!
  10. python中的for else语句
  11. [转] Js获取 本周、本月、本季度、本年、上月、上周、上季度、去年时间段
  12. html绘制小球并跟随鼠标移动,Canvas跟随鼠标炫彩小球的实现
  13. pytorch之transforms
  14. 学生信息表 成绩表+12章练习
  15. 【六】【vlc-android】vlc的decoder控制层传输数据与ffmpeg视频解码模块decoder层的数据交互流程源码分析
  16. APPstore上架问题 ERROR ITMS-90096
  17. 计算机主板是cpu吗,主板和cpu是一起的吗
  18. python如何实现图像中特定颜色的种类识别及特定颜色的占比代码
  19. 灯哥开源ODRIVE FOC驱动器使用记录
  20. DeleteMapping GetMapping PutMapping

热门文章

  1. 微信开放平台认证资料填报
  2. 微信内置浏览器缓存清理 微信缓存清除 清除微信里的h5缓存
  3. 百度网盘/U盘,上传文件时提示超过4G限制如何解决
  4. macbook pro(m1) 安装JD-GUI
  5. 计算机英语阅读路线,计算机经典英语短文阅读
  6. 如何清理卸下应用的残余文件_怎么清理手机卸载残留 需要技巧
  7. win11安装更新错误0x800f081f怎么解决?
  8. 中宠股份第三季度营收7.55亿元:增速环比持续下滑,净利润转降
  9. java题电影院售票设计报告_基于Java的电影订票网站的设计实现 任务书.doc
  10. 转:普通继电器和自锁继电器的差别