图1 装饰模式类图(图片来自StarUML)

意图:动态地给一个对象添加职责;

动机:希望给某个对象添加一些功能,而不影响类的实现;

别名:包装器(wrapper)

角色:Component、ConcreteComponent、Decorator、ConcreteDecorator;

协作:Decorator将请求转发给它的Component对象,并在转发请求前(后)有可能执行一些附加操作;

重点:正确理解Decorator类的功能、角色;

特点:可以作为继承的替代方案、动态添加职责,灵活,高弹性,消除子类爆炸问题,透明;

主要技术:组合、委托、递归、开-闭原则、里式替换原则、依赖倒置原则;

经典实现:Java I/O模块;

个人理解:把Decorator模式看做是组合技术的升级版(可以递归的组合),它的威力大部分来自组合技术和递归思想;

代码示例:https://blog.csdn.net/sinat_36817189/article/details/107795441

什么目的?有什么好处?组成结构是什么?什么时候用?注意事项!


1 什么目的?

Decorator是一种用于扩展代码的设计模式。GoF的《设计模式》一书中,对Decorator模式的定义为:

意图:动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。

从字面意思理解,Decorator模式的重点就在于“装饰”。我们可以简单地认为Decorator模式的目的(意图)就是动态地给对象增加包装层,以得到我们最终想要的对象。

举个例子,我们去玩具店给小外甥女买礼物,买了一个芭比娃娃,把它装进礼物盒里,然后在外面包上一层包装纸,精致的同学可能还会粘上一个蝴蝶结然后放在礼物袋里。整个礼物的最终效果就像这样

图2 礼物效果图(图片来自网络)

礼物的剖面图是这样的

图3 礼物剖面图

实际上只有最里面的芭比娃娃才是小外甥女最关注的东西,但是为了让小外甥女更喜欢这个礼物,我们通常会选择在芭比娃娃外面包装包装盒、包装纸、蝴蝶结、礼物袋这些装饰品。我们为小外甥女包装礼物的时候,给芭比娃娃动态地增加了一些装饰层。

怎么理解“动态”呢?动态是指运行时特性,静态是指编译时期的特性。

动态是指我们买到芭比娃娃后对它进行一系列的包装,至于怎么包装,只有在真正包装的时候才能确定。给芭比娃娃装不装包装盒、裹不裹包装纸、系不系蝴蝶结......这些细节在出厂的时候并不知道,只有在我们买到芭比娃娃后开始包装时才能知道。

相应的,静态是指如果商家对芭比娃娃这款礼物规定了包装方案,那么给芭比娃娃装不装包装盒、裹不裹包装纸、系不系蝴蝶结......这些细节在出厂的时候知道就已经知道了,我们在买到芭比娃娃后可以直接作为礼物送给小外甥女。

至此,我们知道Decorator模式是用来扩展代码的,而且这种扩展方式是动态、针对对象进行的。

2 有什么好处?

在面向对象编程中,更为常见的一种扩展代码的方式是继承,通过实现子类来对父类进行扩展。但是,使用继承的方式扩展代码有一些缺点

  • 继承是一种静态的扩展方式,不灵活;
  • 容易造成子类爆炸(衍生类激增)的问题;

还以上面的芭比娃娃礼物为例,如果商家对芭比娃娃礼物有规定的打包样式,那么我们在买礼物的时候只能买到固定包装的芭比娃娃礼物,不能根据小外甥女的喜好选择要不要裹包装纸、要不要系蝴蝶结。当然,我们可以向商家建议多出几种包装样式,最好有一款包装是完全符合小外甥女喜好的,像这样

图4 继承方式扩展方案

如果我们又想送小侄女一个芭比娃娃,但她不喜欢包装纸,希望把蝴蝶结直接系到包装盒上,怎么办?当然,我们可以建议商家出一款“样式4包装的芭比娃娃”来满足小侄女的喜好。

很容易看出来这种模式的缺点:1、不能灵活地组合礼物包装;2、要满足所有小朋友的喜好,就必须设计更多的样式,这对商家来说是一种很严重的负担。

但正常情况下我们包装礼物会使用文章开头提到的模式:买一个芭比娃娃,然后买一些装饰品,再用这些装饰品把芭比娃娃包装成小外甥女喜欢的礼物。这种模式下的结构图如下

图5 Decorator方式扩展方案

装饰品是用来装饰“礼物”的,我们认为包装了“礼物”“装饰品”也是“礼物”。这种模式下商家没有对芭比娃娃进行包装,需要我们自己用装饰品包装。我们可以很随意地使用装饰品,把芭比娃娃包装成小外甥女喜欢的礼物,或者包装成小侄女喜欢的礼物,甚至可以包装成被1个礼物盒、2层包装纸、3个蝴蝶结装饰的礼物。此时,在不需要商家做任何改变的情况我们就可以把芭比娃娃包装成任何需要的样式,来满足更多小朋友的喜好。

很容易发现图5图1的结构一模一样,没错,这就是Decorator模式的类图。就扩展代码、增加功能而言,我们可以把Decorator看做是继承的替代方案,而且Decorator具有非常大的优势

  • 灵活,各种特性随意组合,同一特性重复添加;
  • 递归嵌套(PS:其实还是想说灵活);
  • 高弹性;
  • 消除了子类爆炸问题;
  • 即用即付,高层类特性单一,职责单一;
  • 透明,从外部改变组件,组件无须了解装饰,客户类也不了解装饰层;

灵活、消除子类爆炸问题这2个特点,大家应该能从前面的例子很清晰地体会到,就不多说了。高弹性其实也是因为灵活,但应该从另一个层面来理解,虽然目前只有四种装饰品可用,但是商家可以提供更多的装饰品或者减少装饰品,而这种变化的代价也非常小。

即付即用怎么理解呢?其实很简单,对于图5中的各个装饰品,我们只要在需要的时候创建实例拿过来用就好了。

透明又怎么理解呢?也很简单,无论是对于图5中的各个装饰品而言还是对于小外甥女而言,他们看到的都是礼物,至于礼物内部是什么东西,他们是不关心的(实际上小外甥女可能会更关心礼物里面的芭比娃娃,但她在收礼物的时候知不知道礼物里面是什么都没关系),所以礼物内部对于他们来说就是透明的。

3 组成结构是什么?

Decorator的类图结构见图1,包含的角色主要有

  • Component
    defines the interface for objects that can have responsibilities added to them dynamically.
  • ConcreteComponent
  • Decorator(这个角色是理解Decorator模式的关键点)
    maintains a reference to a Component object and defines an interface that conforms to Component's interface.
  • ConcreteDecorator
    adds responsibilities to the component.

结合图5的各个角色,对应关系如下

表1 图1和图5各角色对应关系
Decorator角色 芭比娃娃礼物角色
Component 礼物
ConcreteComponent 芭比娃娃
Decorator 装饰品
ConcreteDecorator 礼物盒
ConcreteDecorator 包装纸
ConcreteDecorator 蝴蝶结
ConcreteDecorator 礼物袋

Component的主要功能是定义接口、提供职责,限制类型描述(本人更倾向于在类图中把Component定义为Interface在上面的例子中,礼物的作用就是限制芭比娃娃和装饰品的类型描述限定为礼物这个例子中礼物定义接口、提供职责的作用体现得不是很明显,不过在把芭比娃娃和装饰品限定为礼物时,它们就具备了礼物应有的职责)。

ConcreteComponent是实际处理业务的类,它提供了具体的业务逻辑。送给小外甥女的礼物最终产生作用的是芭比娃娃,其他装饰品都是为了让这个礼物更好看而附加的。实际上,如果没有芭比娃娃就没有办法构成礼物的实例(对象)。

Decorator是对所有装饰器的抽象。装饰器持有一个Component实例,而这个持有Component实例的Decorator实际上就是一个Component。前面例子里的装饰品扮演的就是Component这个角色,每个装饰品里面实际上都会包裹着一个礼物,而这个包裹着礼物的装饰品实际上就是礼物,它可以被其他装饰品包裹或者被送给小外甥女。

特别注意:

  • 其实在图5中装饰品严格意义上的定义应该是:包装了礼物的装饰品,而这个包装了礼物的装饰品就是一个礼物;
  • 这里Decorator继承/实现Component的目的是为了保证类型匹配正确,而不是获得Component的行为;

ConcreteDecorator不用多说,主要作用就是实现具体的装饰逻辑(实现需要新增职责)。包装盒、包装纸、蝴蝶结、礼物袋等装饰品都以不一样的方式给礼物添加职责或功能。

4 什么时候用?

  • 不影响其他对象的情况下,以动态、透明的方式给对象添加职责;
  • 处理可以撤销的职责;
  • 不能通过继承实现扩展时:
    1、子类爆炸式增长;
    2、现有类禁止被继承;

其他场景比较容易理解,这里就不多说了。

对于“处理可以撤销的职责”,可以理解为在运行时需要剥离被附加职责的场景。在前面提到的例子中,礼物送给小外甥女后,小外甥女要想看到里面的芭比娃娃,必须把蝴蝶结、包装纸、包装盒一层层拆掉,这个过程就是在撤销装饰器增加的职责。

注意事项!

  • 接口一致性;
  • 省略抽象的Decorator类(当附加职责较少时);
  • 保持Component的简单性;
  • 注意理解Decorator的作用,是一种透明的包装,改变外壳(strategy改变内核);
  • 小对象多,系统难以学习;

接口一致性:Decorator模式需要保证Decorator的类型与Component保持一致。反观前面的例子,如果装饰品不是礼物类型,那么当芭比娃娃被包装过一次之后,它就不是礼物了,因为从最外层看到的只是装饰品,那么这个装饰品就不能被其他装饰品装饰了(因为我们的系统里没有装饰装饰品的装饰品),而且这个装饰品也不能送给小外甥女了。

省略抽象的Decorator类:当装饰器较少时,我们可以选择省略Decorator类,以简化代码的复杂度。在上面的例子中,如果商家提供的装饰品只有一个礼品袋,那么我们就可以省略装饰品这个角色(实际上应该是用礼品袋替代装饰品,然后把原来“装饰品的子类都删掉),让代码更简洁。

保持Component的简单性:参考职责单一原则、接口分离原则。面向对象设计原则

小对象多,系统难以学习:Decorator模式可能会产生大量的Decorator子类作为装饰器,这样的结构让系统学习、维护起来很困难。

[GoF] 装饰模式-Decorator相关推荐

  1. 装饰模式(Decorator)简介

    装饰模式是第三个介绍的模式了. 这个模式没有前面两个那么好理解., 一, 装饰模式(decorator)的定义. 教材里是这样写的: 动态第给1个对象添加1写额外的职责(功能), 就增加的功能来讲, ...

  2. 二十四种设计模式:装饰模式(Decorator Pattern)

    装饰模式(Decorator Pattern) 介绍 动态地给一个对象添加一些额外的职责.就扩展功能而言,它比生成子类方式更为灵活. 示例 有一个Message实体类,某个对象对它的操作有Insert ...

  3. 设计模式(13):结构型-装饰模式(Decorator)

    设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. 毫无疑问,设计模式于 ...

  4. 七、装饰模式(Decorator Pattern)

    一.介绍 意图:动态地给一个对象添加一些额外的职责.就增加功能来说,装饰器模式相比生成子类更为灵活. 主要解决:使用继承实现类的功能的扩展,有时子类会过多的问题. 应用实例: 1.一幅照片,将它放入玻 ...

  5. 装饰模式Decorator

                                                               装饰模式Decorator /*** 抽象构件角色* @author InJava ...

  6. 设计模式之装饰模式(Decorator)摘录

    23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而 ...

  7. 装饰模式(Decorator)

    1.概念 装饰模式动态地给一个对象添加一些额外的职责.就扩展功能而言,它比生成子类方式更为灵活,属于结构性模式一种. 2.模式结构 抽象组件角色(Component):定义一个对象接口,以规范准备接受 ...

  8. c语言装饰,C++设计模式之装饰模式(Decorator)

    装饰模式是一种经典的类功能扩展模式,其精髓在装饰类使用继承加聚合的方式获得接口和要实现对象,然后通过自己实现扩展接口 作用装饰模式通过装饰类动态地将责任附加到对象上,若要扩展功能,无需通过继承增加子类 ...

  9. 《研磨设计模式》chap22 装饰模式Decorator(4)AOP+总结

    1. AOP面向方面编程 共性功能 AOP调用示意图 public class SaleModel { private String goods; //销售的商品 public String getG ...

最新文章

  1. SQL语句优化技术分析
  2. 炸裂!Google这波操作,预警了什么?
  3. 云服务器木马文件该如何应对,云服务器被入侵如何处理
  4. opencv 罗曼滤波_勒罗曼杜罗伊
  5. [读书笔记]Effective Java 第四章
  6. JSP技术模型(五)JSP隐含变量
  7. 使用Jersey来创建RESTful WebService
  8. 小米4刷魅族系统后无服务器,小米4线刷魅族Flyme OS系统的教程_小米4 Flyme OS刷机包...
  9. pentaho资源库迁移-MySQL
  10. Android自定义View、ViewGroup
  11. android WebView加载淘宝天猫页面报找不到网址的错误
  12. 「目前全网唯一2万字长文」从JS上下文到Chromium源码的极限拉扯!!兄弟姐妹们接好了!!...
  13. Pro/E产品设计:电风扇扇叶的设计方法
  14. win10笔记本使用ipad作为扩展屏
  15. swiper.js横向轮播插件
  16. vue3的setup的使用和原理解析
  17. mysql中vlookup函数_VLOOKUP函数的使用方法(入门级)
  18. SNMP、MIB和OID概述
  19. 微信支付通用支付接口
  20. JQuery 多选下拉列表左右移动

热门文章

  1. HTML+CSS鲜花静态网页设计
  2. 曼昆《经济学原理》第一二章部分
  3. 如何做个直播应用建设?
  4. 钉钉API调试工具使用
  5. PHP生成带参数的小程序码
  6. 【学生管理系统】权限管理之角色管理
  7. xp系统电脑如何链接宽带连接服务器地址,电脑如何查看连接路由器的登录地址?...
  8. java实现pdf修改,或者在Java中使用iText pdf更改pdf页面的颜色
  9. 给我一个web前端的登录界面代码
  10. 超级完整的Maya2019版本功能介绍