设计模式 (十) 装饰者模式
一、定义
装饰者模式是一种比较常见的模式,其定义如下:
动态地给对象添加一些额外的职责,就增加功能来说,装饰者模式相比生成子类更为灵活。如果不用装饰者模式,我们想要扩展一个对象的功能,我们可能会采用继承该对象的方式,然后重写里面的方法来实现扩展原有功能,但当对象变化频繁的时候,这种子类会有很多,装饰者模式有效避免了这种创建大量子类的现象,动态地扩展对象的功能。
装饰者模式通用类图如下:
二、角色分析
装饰者模式有四个角色需要说明:
- Component抽象构件
抽象构建类,可以是抽象类或者接口,定义了被装饰者类的一些抽象方法等,即最基本的功能。
- ConcreteComponent具体构件
具体构建类,实现或者继承了抽象构建类,实现了具体的方法,真正被装饰的其实是它。
- Decorator装饰角色
一般是一个抽象类,实现接口或者抽象方法,它里面不一定有抽象的方法,在它的属性里面必然有一个private变量指向Component抽象构件。
- ConcreteDecorator具体装饰角色
真正在这里扩展原始对象的功能,针对不同的具体构建类,可以定义多个具体装饰角色,例如ConcreteDecoratorA和ConcreteDecoratorB是两个具体的装饰类。
三、装饰者模式案例分析
下面我们以一个日常生活中常见的例子来说明装饰者模式的使用。
冬天快到了,很多小伙伴都会喜欢打火锅,打火锅肯定要有一个汤底,然后我们可能还会点各种各样的配菜,在增加配菜的同时,我们需要动态计算吃这一顿的总价钱。
接下来我们就使用装饰者模式来把上面这种场景进行抽象。
首先,UML类图如下:
我们首先定义抽象构件角色,主要提供两个方法:获取价格的方法getPrice()、获取描述信息的方法getDesc(),代码如下:
/*** 抽象构件: 火锅类*/
public abstract class HotPotComponent {/*** 获取价格** @return*/protected abstract BigDecimal getPrice();/*** 获取描述信息** @return*/protected abstract String getDesc();
}
接下来定义一个具体构件角色:本例中就是火锅底料对象,真正装饰的也就是它。
/*** 具体构件类: 火锅底料*/
public class HotPotSeasoningComponent extends HotPotComponent {@Overrideprotected BigDecimal getPrice() {return BigDecimal.valueOf(20);}@Overrideprotected String getDesc() {return "火锅底料";}
}
接着,定义一个抽象装饰者类,它继承自抽象构件角色,内存持有一个指向抽象构件角色的引用:
/*** 抽象装饰者类*/
public abstract class AbstractDecorator extends HotPotComponent {private HotPotComponent hotPotComponent;public AbstractDecorator(HotPotComponent hotPotComponent) {this.hotPotComponent = hotPotComponent;}@Overrideprotected BigDecimal getPrice() {return hotPotComponent.getPrice().add(getSideDishPrice());}@Overrideprotected String getDesc() {return hotPotComponent.getDesc() + " + " + getSideDishDesc();}/*** 获取配菜的描述** @return*/protected abstract String getSideDishDesc();/*** 获取配菜的价格** @return*/protected abstract BigDecimal getSideDishPrice();}
可以看到, 除了实现抽象构件的抽象方法之外,抽象装饰者角色内存还定义了配菜的一些方法。并且重写了抽象构件的getPrice()方法和getDesc()方法,动态的计算所有配菜的总价格。
有了抽象装饰者角色,就需要定义具体的装饰者角色进行增强了:
/*** 具体装饰者角色: 肥牛*/
public class FatCattleDecorator extends AbstractDecorator {public FatCattleDecorator(HotPotComponent hotPotComponent) {super(hotPotComponent);}@Overrideprotected String getSideDishDesc() {return "肥牛";}@Overrideprotected BigDecimal getSideDishPrice() {return BigDecimal.valueOf(20);}
}/*** 具体装饰者角色: 小白菜*/
public class CabbageDecorator extends AbstractDecorator {public CabbageDecorator(HotPotComponent hotPotComponent) {super(hotPotComponent);}@Overrideprotected String getSideDishDesc() {return "小白菜";}@Overrideprotected BigDecimal getSideDishPrice() {return BigDecimal.valueOf(10);}}
最后,我们定义一个场景类进行测试:
public class Client {public static void main(String[] args) {//层层包装HotPotComponent hotPotComponent = new HotPotSeasoningComponent();//一份肥牛hotPotComponent = new FatCattleDecorator(hotPotComponent);//一份肥牛hotPotComponent = new FatCattleDecorator(hotPotComponent);//一份小白菜hotPotComponent = new CabbageDecorator(hotPotComponent);System.out.println(hotPotComponent.getDesc() + " = " + hotPotComponent.getPrice() + "元");}
}
运行结果:
火锅底料 + 肥牛 + 肥牛 + 小白菜 = 70元
可见,成功扩展原有对象的功能,这样我们以后如果需要扩展火锅底料的功能,只需要增加一个具体的装饰者角色即可实现扩展,而不需要改动原有的代码,这符合开闭原则。
四、装饰者应用分析
装饰者模式在我们的IO流中体现的最为显著,下面我们看一下IO流中InputStream相关的UML类图:
对应前面的通用类图应该很容易看出各个角色分别是谁:
- OutputStream和InputStream就对应于抽象构件角色(Component);
- FileInputStream和FileOutputStream就对应具体构件角色(ConcreteComponent);
- FilterOutputStream和FilterInputStream就对应着抽象装饰角色(Decorator);
- BufferedOutputStream,DataOutputStream等等就对应着具体装饰角色;
下面是关键源码:
//抽象类,抽象构建角色
public abstract class InputStream implements Closeable {//省略...
}//具体构建角色,也是具体被装饰的类
public class FileInputStream extends InputStream{//省略...
}//装饰者基类,该类持有一个抽象构建角色InputStream的引用
public class FilterInputStream extends InputStream {/*** The input stream to be filtered.*/protected volatile InputStream in;//省略...
}//具体装饰者角色
public class BufferedInputStream extends FilterInputStream {//省略...
}
五、总结
装饰者模式的优点:
- 装饰者类和被装饰者类互不影响,独立扩展,他们之间没有耦合关系;
- 装饰者模式是频繁继承的一个替换解决方案,可以重复包装,包装之后返回的还是抽象构建角色;
- 装饰者模式可以动态地扩展一个实现类的功能;
装饰者模式的缺点:
- 多层装饰之后会使系统更加复杂,后期不太方便维护和扩展,所以不建议嵌套太多层装饰,尽量减少装饰类的数量,降低系统的复杂度。
装饰者模式的使用场景:
- 需要动态扩展一个类的功能,或给一个类在增加附加功能;
- 需要频繁使用继承才能扩展功能时可以考虑使用装饰者模式,可以减少类的创建;
- 需要为一批的兄弟类进行改装或加装功能,当前是首选装饰者模式;
设计模式 (十) 装饰者模式相关推荐
- Java设计模式(装饰者模式-组合模式-外观模式-享元模式)
Java设计模式Ⅳ 1.装饰者模式 1.1 装饰者模式概述 1.2 代码理解 2.组合模式 2.1 组合模式概述 2.2 代码理解 3.外观模式 3.1 外观模式概述 3.2 代码理解 4.享元模式 ...
- 前端也要学系列:设计模式之装饰者模式
什么是装饰者模式 今天我们来讲另外一个非常实用的设计模式:装饰者模式.这个名字听上去有些莫名其妙,不着急,我们先来记住它的一个别名:包装器模式. 我们记着这两个名字来开始今天的文章. 首先还是上< ...
- 设计模式 ( 十八 ) 策略模式Strategy(对象行为型)
设计模式 ( 十八 ) 策略模式Strategy(对象行为型) 1.概述 在软件开发中也经常遇到类似的情况,实现某一个功能有多种算法或者策略,我们能够依据环境或者条件的不同选择不同的算法或者策略来完毕 ...
- 设计模式 之 装饰者模式
2019独角兽企业重金招聘Python工程师标准>>> 设计模式 之 装饰者模式 装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对 ...
- 【设计模式】装饰者模式 ( 概念 | 适用场景 | 优缺点 | 与继承对比 | 定义流程 | 运行机制 | 案例分析 )
文章目录 I . 装饰者模式概念 II . 装饰者模式适用场景 III . 装饰者模式优缺点 IV . 装饰者模式与继承对比 V . 装饰者模式相关设计模式 VI . 装饰者模式四个相关类 VII . ...
- 设计模式学习----装饰器模式
这两天本来是自在学习java collection Framework的Fail Fast底层机制,看到核心的部分时,突然意识到设计模式的问题,上大学到现在我还没有真正理解过设计模式的概念,于是用了大 ...
- 【设计模式】装饰器模式的使用
问题来源 我们在进行软件系统设计的时候,有一些业务(如下图,一些通用的非功能性需求)是多个模块都需要的,是跨越模块的.把它们放到什么地方呢? 最简单的办法就是把这些通用模块的接口写好,让程序员在实现业 ...
- 设计模式 ( 十四 ) 迭代器模式Iterator(对象行为型)
设计模式 ( 十四 ) 迭代器模式Iterator(对象行为型) 1.概述 类中的面向对象编程封装应用逻辑.类,就是实例化的对象,每个单独的对象都有一个特定的身份和状态.单独的对象是一种组织代码的有用 ...
- C#设计模式(9)——装饰者模式(Decorator Pattern)
一.引言 在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类).A ...
- go设计模式之装饰器模式
go设计模式之装饰器模式 再写这篇文章时,我已经看了很多其他人发表的类似文章,大概看了这么多吧. 亓斌的设计模式-装饰者模式(Go语言描述) jeanphorn的Golang设计模式之装饰模式 七八月 ...
最新文章
- COM 组件设计与应用(六)——用 ATL 写第一个组件(vc.net)
- 最新dnsmasq安装部署详解(centos6)
- 别再打字聊bug了,GitHub支持“视频留言”!手机也可以的那种
- Chapter34 创建主窗口/实现应用程序功能
- python2转python3代码_python2代码批量转为python3代码
- 【工具类】工具相关参考文档汇总
- oracle避免同一sql多次查询,Oracle SQL - 在一个查询中生成一行答案的最简单方法,因此我不必多次运行查询?...
- 产品经理,讲究的是说学逗唱。
- 最前线 | 斗鱼一季度月活用户超虎牙,但上市时间仍不明确
- 聚类技术---复杂网络社团检测_基于Plato高性能图计算框架的社团发现算法
- pageinfo对合并list进行分页_PageInfo实现分页
- FLEX APIs、Libs、Components
- vue组件制作专题 - (mpvue专用)在mpvue中纯自己写css实现简单左右轮播
- 传京东副总裁蒉莺春或将接管POP业务-搜狐IT
- 多个注解可以合并成一个,包括自定义注解
- efsframe java_EfsFrame(java开发框架)
- 移动应用开发常见技术比较
- 游戏修改服务器数据,修改游戏服务器数据的教程
- Jenkins自动集成
- 15种方法活力一整天
热门文章
- C/C++[codeup 1931]打印日期,一年的第n天是几月几号
- 自动驾驶 11-2: 激光雷达传感器模型和点云 LIDAR Sensor Models and Point Clouds
- Docker安装MySQL 8 for Mac(图文详解)
- iOS网络请求架构图URL Loading System
- 反地理编码 高德地图_由中文地址返回点位坐标-地理编码脚本分享
- 19范数理论及其应用
- 实战!Servlet简单实践,完成上次的任务
- mysql怎么添加第二行,如何在mysql中得到結果查詢中只有第二行?
- 杭电HDUacm2037
- 最近写mapreduce程序从hbase中抽取程序遇到的一些问题