从未这么明白的设计模式(三):装饰器模式
本文原创地址:jsbintask的博客(食用效果最佳),转载请注明出处!
同系列文章: 从未这么明白的设计模式(二):观察者模式
从未这么明白的设计模式(一):单例模式
前言
装饰器模式是为了运行时动态的扩展一个类的功能。它谨遵开闭原则
,它实现的关键在于继承和组合的结合使用
,解耦对象之间的关系。 各种设计模式学习地址:
github.com/jsbintask22…
栗子
首先我们列举一个案例,并且按照面向对象的思想来对应实体之间的关系。
有一个咖啡店,销售各种各样的咖啡,拿铁,卡布奇洛,蓝山咖啡等,在冲泡前,会询问顾客是否要加糖,加奶,加薄荷等。这样不同的咖啡配上不同的调料就会卖出不同的价格。
V1
针对上面的栗子,我们很容易就抽象出对应的实现,如上图。接着,我们就要编写对应的类来实现对应的功能。在这个例子中,主题当然就是咖啡
,并且它有一个属性是名字
,一个行为 价格
,出于“面向对象”的思想,我们自然会设计出抽象类Coffee
:
public abstract class Coffee {/*** 获取咖啡得名字*/public abstract String getName();/*** 获取咖啡的价格*/public abstract double getPrice();
}
复制代码
接着,按照继承的思想,我们要开始设计出具体的实现类,因为拿铁,卡布奇洛,蓝山搭配上不同的调料(上面三种)会有不同的价格,名字,所以我们至少得设计出 3 X 3 = 9 个类来分别对应它们的名字和价格:
嗯!我想不用说这样设计得缺陷也很明显了! 由于不同的咖啡和不同的调料得各种任意组合,使得出现了类爆炸
的现象。既然有这么明显的缺陷,那我们当然得改! 我们可以考虑把各种调料当作属性加入到Coffee这个抽象类中,接着在实现类中计算价格和名字时,分别判断是否加入了各种调料包,得到不同的名字和价格!
按照上面的思想,我们的Coffee类现在变成了这样:
public abstract class Coffee {// 是否加了牛奶protected boolean addedMilk;// 是否加了糖protected boolean addedSugar;// 是否加了薄荷protected boolean addedMint;/*** 获取咖啡得名字*/public abstract String getName();/*** 获取咖啡的价格*/public abstract double getPrice();
}
复制代码
接着,我们实现一种咖啡,蓝山咖啡:
public class BuleCoffee extends Coffee {@Overridepublic String getName() {StringBuilder name = new StringBuilder();name.append("蓝山");if (addedMilk) {name.append("牛奶");}if (addedMilk) {name.append("薄荷");}if (addedSugar) {name.append("加糖");}return name.toString();}@Overridepublic double getPrice() {double price = 10;if (addedMilk) {price += 1.1;}if (addedMilk) {price += 3.2;}if (addedSugar) {price += 2.7;}return price;}
}
复制代码
嗯!现在似乎比上面愉快多了。其实不然!我们仔细分析这种设计,会发现它似乎不太符合”封装的思想“,比如说针对拿铁,对于加薄荷而言,对他总是多余的! 而对于蓝山而言,牛奶又显得很多余! 所以这种设计也并不合理。 另外,我们假设coffee,拿铁等实体类来自第三方类库,我们并不能改动这些类的实现, 又要怎么得到名字和价格呢?
这个时候,我们就得使用装饰器
模式来动态的扩展类行为! 所以我们设计出V3版本。
V3
开闭原则
首先,我们需要了解一个面向对象的一个基本设计原则:开闭原则
,它指的是类应该对修改关闭,对扩展开放
。
怎么理解呢? 就比如我们上方说的:假如cofee和它的一众实现拿铁,卡布奇洛,蓝山来自第三方类库,并且这个类库已经很”适合“,”实用“了。 而我们为了得到加入不同调料的咖啡的名字和价格,我们就得修改这些实现,而这样的修改,总是免不了稳定性
的改变。对原本的系统来说也是一种风险! 所以我们应该 对修改关闭,对扩展开放
;
继承和组合
遵循开闭原则,那我们就得对外扩展,那怎么对外扩展呢? 这也是装饰器模式实现的关键,利用继承和组合
的结合; 现在我们可以考虑设计出一个装饰类,它也继承自coffee,并且它内部有一个coffee的实例对象:
现在,我们多了一个咖啡装饰器
: CoffeeDecorator:
public abstract class CoffeeDecorator implements Coffee {private Coffee delegate;public CoffeeDecorator(Coffee coffee) {this.delegate = coffee;}@Overridepublic String getName() {return delegate.getName();}@Overridepublic double getPrice() {return delegate.getPrice();}
}
复制代码
接着,我们将牛奶,薄荷作为抽象出一个类,继承自CoffeeDecorator,所以,现在类图就成了这样:
我们实现一个MilkCoffeeDecorator
:
public class MilkCoffeeDecorator extends CoffeeDecorator {public MilkCoffeeDecorator(Coffee coffee) {super(coffee);}@Overridepublic String getName() {return "牛奶, " + super.getName();}@Overridepublic double getPrice() {return 1.1 + super.getPrice();}
}
复制代码
按同样的方法可以实现出MintCoffeeDecorator
,SugarCoffeeDecorator
。接着我们写一个测试类:
public class App {public static void main(String[] args) {// 得到一杯原始的蓝山咖啡Coffee blueCoffee = new BlueCoffee();System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());// 加入牛奶blueCoffee = new MilkCoffeeDecorator(blueCoffee);System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());// 再加入薄荷blueCoffee = new MintCoffeeDecorator(blueCoffee);System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());// 再加入糖blueCoffee = new SugarCoffeeDecorator(blueCoffee);System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());}
}
复制代码
从结果我们可以看出,随着不断加入各种调料,价格,名字都在改变! 这说明我们加入不同的调料,动态的改变了咖啡的名字和价格!
思考
从上面的最后的装饰器模式的实现来看,我们可以得出以下结论:
- 通过装饰器模式可以动态的将责任附加到原有的对象上,而不改变原有的code。
- 遵循
开闭原则
- 装饰者和被装饰者有相同的父类(如栗子中的Coffee)
- 可以用多个装饰器装饰同一个对象。(见运行类)
- 装饰者可以在被装饰者的行为之前或之后动态的加上自己的行为。(参考装饰实现)
- 组合比继承更加的灵活(上面的coffee代理)
扩展
到现在,我们已经实现了一个自己的装饰器,我们来看看jdk中用到的装饰器实现.
IO
我们可以查看FilterInputStream:
它的主要是实现者为BufferedInputStream
: 所以我们经常可以使用BufferedInputStream装饰一个InputStream,比如FileInputStream: new BufferedInputStream(FileInputStream);
这就是装饰器模式的典型应用。
tomcat
在tomcat的HttpServletRequest的内部实现代码中,RequestFacde
继承自HttpServlet,而它内部的实现也是通过代理Request
对象,而Request对象继承自HttpServlet,Request内部代理了org.apache.coyote.Request
来实现的。
总结
装饰器模式充分展示了组合的灵活。利用它来实现扩展。它同时也是开闭原则的体现。 如果相对某个类实现运行时功能动态的扩展。 这个时候你就可以考虑使用装饰者模式!
关注我,这里只有干货!
从未这么明白的设计模式(三):装饰器模式相关推荐
- 设计模式学习----装饰器模式
这两天本来是自在学习java collection Framework的Fail Fast底层机制,看到核心的部分时,突然意识到设计模式的问题,上大学到现在我还没有真正理解过设计模式的概念,于是用了大 ...
- 设计模式之装饰器模式详解
设计模式之装饰器模式详解 文章目录 设计模式之装饰器模式详解 一.什么是装饰器模式 二.装饰器模式的角色组成 三.装饰器模式通用写法示例 四.装饰器模式业务中的应用举例 五.装饰器模式优缺点 一.什么 ...
- 【设计模式】装饰器模式的使用
问题来源 我们在进行软件系统设计的时候,有一些业务(如下图,一些通用的非功能性需求)是多个模块都需要的,是跨越模块的.把它们放到什么地方呢? 最简单的办法就是把这些通用模块的接口写好,让程序员在实现业 ...
- go设计模式之装饰器模式
go设计模式之装饰器模式 再写这篇文章时,我已经看了很多其他人发表的类似文章,大概看了这么多吧. 亓斌的设计模式-装饰者模式(Go语言描述) jeanphorn的Golang设计模式之装饰模式 七八月 ...
- python中的装饰器、装饰器模式_python 设计模式之装饰器模式 Decorator Pattern
#写在前面 已经有一个礼拜多没写博客了,因为沉醉在了<妙味>这部小说里,里面讲的是一个厨师苏秒的故事.现实中大部分人不会有她的天分.我喜欢她的性格:总是想着去解决问题,好像从来没有怨天尤人 ...
- python 设计模式之装饰器模式 Decorator Pattern
#写在前面 已经有一个礼拜多没写博客了,因为沉醉在了<妙味>这部小说里,里面讲的是一个厨师苏秒的故事.现实中大部分人不会有她的天分.我喜欢她的性格:总是想着去解决问题,好像从来没有怨天尤人 ...
- 零基础学习设计模式之装饰器模式(配套视频)
零基础学习设计模式之装饰器模式 定义 在不改变目标结构的情况下,动态的给对象增加功能 举例 如房子装修.相片加相框等,都是装饰器模式. 基本组件 抽象构件(Component)角色:定义一个抽象接口以 ...
- 详解设计模式:装饰器模式
装饰器模式(Decorator Pattern)也称为包装模式(Wrapper Pattern),是 GoF 的 23 种设计模式中的一种结构型设计模式.装饰器模式 是指在不改变原有对象的基础之上,将 ...
- javascript设计模式之装饰器模式(结构型模式)
javascript设计模式之装饰器模式 js的设计模式分为创建型模式,结构型模式和行为模式 结构模式描述了如何组合对象以提供新的功能. 装饰器模式是一种常见的结构型模式,我们可以以一个基础对象为基础 ...
- 【设计模式】--- 装饰器模式、静态代理模式和动态代理模式
文章目录 1 引子 2 业务场景介绍 3 静态代理模式 4 装饰器模式 5 动态代理模式 5.1 Proxy --- 具体的代理对象生成组件 5.2 InvocationHandler --- 封装被 ...
最新文章
- 实现ABAP条件断点的三种方式
- 使用域超级管理员打开Exchange 2010发现没有权限
- Python3 循环
- Spring Cloud —— 链路追踪技术
- C++中固定长度短字符串比较是否相同,忽略大小写比对时的小技巧
- 动态规划 —— 线性 DP —— 序列问题
- STM32工作笔记0068---SPI同步通信Flash读写实验
- 130.C++经典面试题 52-100
- vb读取mysql数据库数据_VB读取ORACLE数据库的两种方法
- python 下载安装 教程
- oppor9splus科学计算机,oppo r9s plus手机驱动
- MongoDB下载安装教程(Windows)
- FireFox浏览器渗透测试插件
- 用spark实现单词统计
- 《计算广告》读书笔记——第一章 在线广告综述
- nrm 切换不同的源工具
- HC05蓝牙模块与手机APP连接
- ipmitool使用手册
- C语言的三种参数传递方式
- SuperMap之等高线制作
热门文章
- vue 默认加载某一子路由
- 与网友“阵春风”交流
- 沙漠求生十五选五实验
- 【马红“名师+”研修共同体】“课” 展风采,“研”无止境----教学交流活动(二)
- [网络安全自学篇] 七十五.Vulnhub靶机渗透之bulldog信息收集和nc反弹shell(三)
- 第二节 控制系统的数学模型——传递函数
- JAVA-多线程 三 {多线程状态}JAVA从基础开始 -- 3
- 上天入地无所不能的C语言(一)
- 计算机excel界面,2017《职称计算机》知识点:Excel用户界面
- matlab怎样编程形成软件_Matlab编程笔记之GUI程序转exe