一、定义

装饰者模式是一种比较常见的模式,其定义如下:

动态地给对象添加一些额外的职责,就增加功能来说,装饰者模式相比生成子类更为灵活。如果不用装饰者模式,我们想要扩展一个对象的功能,我们可能会采用继承该对象的方式,然后重写里面的方法来实现扩展原有功能,但当对象变化频繁的时候,这种子类会有很多,装饰者模式有效避免了这种创建大量子类的现象,动态地扩展对象的功能。

装饰者模式通用类图如下:

二、角色分析

装饰者模式有四个角色需要说明:

  • 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 {//省略...
}

五、总结

装饰者模式的优点:

  • 装饰者类和被装饰者类互不影响,独立扩展,他们之间没有耦合关系;
  • 装饰者模式是频繁继承的一个替换解决方案,可以重复包装,包装之后返回的还是抽象构建角色;
  • 装饰者模式可以动态地扩展一个实现类的功能;

装饰者模式的缺点:

  • 多层装饰之后会使系统更加复杂,后期不太方便维护和扩展,所以不建议嵌套太多层装饰,尽量减少装饰类的数量,降低系统的复杂度。

装饰者模式的使用场景:

  • 需要动态扩展一个类的功能,或给一个类在增加附加功能;
  • 需要频繁使用继承才能扩展功能时可以考虑使用装饰者模式,可以减少类的创建;
  • 需要为一批的兄弟类进行改装或加装功能,当前是首选装饰者模式;

设计模式 (十) 装饰者模式相关推荐

  1. Java设计模式(装饰者模式-组合模式-外观模式-享元模式)

    Java设计模式Ⅳ 1.装饰者模式 1.1 装饰者模式概述 1.2 代码理解 2.组合模式 2.1 组合模式概述 2.2 代码理解 3.外观模式 3.1 外观模式概述 3.2 代码理解 4.享元模式 ...

  2. 前端也要学系列:设计模式之装饰者模式

    什么是装饰者模式 今天我们来讲另外一个非常实用的设计模式:装饰者模式.这个名字听上去有些莫名其妙,不着急,我们先来记住它的一个别名:包装器模式. 我们记着这两个名字来开始今天的文章. 首先还是上< ...

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

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

  4. 设计模式 之 装饰者模式

    2019独角兽企业重金招聘Python工程师标准>>> 设计模式 之 装饰者模式 装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对 ...

  5. 【设计模式】装饰者模式 ( 概念 | 适用场景 | 优缺点 | 与继承对比 | 定义流程 | 运行机制 | 案例分析 )

    文章目录 I . 装饰者模式概念 II . 装饰者模式适用场景 III . 装饰者模式优缺点 IV . 装饰者模式与继承对比 V . 装饰者模式相关设计模式 VI . 装饰者模式四个相关类 VII . ...

  6. 设计模式学习----装饰器模式

    这两天本来是自在学习java collection Framework的Fail Fast底层机制,看到核心的部分时,突然意识到设计模式的问题,上大学到现在我还没有真正理解过设计模式的概念,于是用了大 ...

  7. 【设计模式】装饰器模式的使用

    问题来源 我们在进行软件系统设计的时候,有一些业务(如下图,一些通用的非功能性需求)是多个模块都需要的,是跨越模块的.把它们放到什么地方呢? 最简单的办法就是把这些通用模块的接口写好,让程序员在实现业 ...

  8. 设计模式 ( 十四 ) 迭代器模式Iterator(对象行为型)

    设计模式 ( 十四 ) 迭代器模式Iterator(对象行为型) 1.概述 类中的面向对象编程封装应用逻辑.类,就是实例化的对象,每个单独的对象都有一个特定的身份和状态.单独的对象是一种组织代码的有用 ...

  9. C#设计模式(9)——装饰者模式(Decorator Pattern)

    一.引言 在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类).A ...

  10. go设计模式之装饰器模式

    go设计模式之装饰器模式 再写这篇文章时,我已经看了很多其他人发表的类似文章,大概看了这么多吧. 亓斌的设计模式-装饰者模式(Go语言描述) jeanphorn的Golang设计模式之装饰模式 七八月 ...

最新文章

  1. COM 组件设计与应用(六)——用 ATL 写第一个组件(vc.net)
  2. 最新dnsmasq安装部署详解(centos6)
  3. 别再打字聊bug了,GitHub支持“视频留言”!手机也可以的那种
  4. Chapter34 创建主窗口/实现应用程序功能
  5. python2转python3代码_python2代码批量转为python3代码
  6. 【工具类】工具相关参考文档汇总
  7. oracle避免同一sql多次查询,Oracle SQL - 在一个查询中生成一行答案的最简单方法,因此我不必多次运行查询?...
  8. 产品经理,讲究的是说学逗唱。
  9. 最前线 | 斗鱼一季度月活用户超虎牙,但上市时间仍不明确
  10. 聚类技术---复杂网络社团检测_基于Plato高性能图计算框架的社团发现算法
  11. pageinfo对合并list进行分页_PageInfo实现分页
  12. FLEX APIs、Libs、Components
  13. vue组件制作专题 - (mpvue专用)在mpvue中纯自己写css实现简单左右轮播
  14. 传京东副总裁蒉莺春或将接管POP业务-搜狐IT
  15. 多个注解可以合并成一个,包括自定义注解
  16. efsframe java_EfsFrame(java开发框架)
  17. 移动应用开发常见技术比较
  18. 游戏修改服务器数据,修改游戏服务器数据的教程
  19. Jenkins自动集成
  20. 15种方法活力一整天

热门文章

  1. C/C++[codeup 1931]打印日期,一年的第n天是几月几号
  2. 自动驾驶 11-2: 激光雷达传感器模型和点云 LIDAR Sensor Models and Point Clouds
  3. Docker安装MySQL 8 for Mac(图文详解)
  4. iOS网络请求架构图URL Loading System
  5. 反地理编码 高德地图_由中文地址返回点位坐标-地理编码脚本分享
  6. 19范数理论及其应用
  7. 实战!Servlet简单实践,完成上次的任务
  8. mysql怎么添加第二行,如何在mysql中得到結果查詢中只有第二行?
  9. 杭电HDUacm2037
  10. 最近写mapreduce程序从hbase中抽取程序遇到的一些问题