故事

古话说的好:人靠衣裳马靠鞍。下面先带大家来熟悉这句话的背景:

人靠衣装马靠鞍,狗配铃铛跑的欢出自沈自晋《望湖亭记》第十出:“虽然如此,佛靠金装,人靠衣装,打扮也是很要紧的。”《醒世恒言》卷一‧两县令竞义婚孤女:”常言道:’佛是金装,人是衣装,世人眼孔浅的多,只有皮相,没有骨相。’”俗语我们会说成人靠衣装马靠鞍。

这个经典故事,让我想起了一个设计模式:装饰器模式。

什么是装饰器模式呢?请听老田慢慢道来。

装饰器模式概述

装饰器模式(Decorator Pattern)也叫作包装器模式(Wrapper Pattern),指在不改变原有对象的基础上,动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活,属于结构型设计模式。

英文:

Attach additional responsibilities to an object dynamicallykeeping the same interface.Decorators provide a flexible alternativeto subclassing for extending functionality.

装饰器模式提供了比继承更有弹性的替代方案(扩展原有对象的功能)将功能附加到对象上。因此,装饰器模式的核心是功能扩展。使用装饰器模式可以透明且动态地扩展类的功能。

生活中的案例

一套毛坯房,没有装修之前,看起来非常难看,但只要稍微装修一番,那就漂亮多了,并且能洗澡、睡觉、做饭等,但本质还是房子。

一辆汽车,原本就是一辆代步的车,但是玛丽加大,配置提升,然后就成了豪车,但本质还是一辆代步的车。

一个女生,原本很平凡,长相一般,但是经过一番化妆,再穿点好看的衣服,然后就成了很多人心中的女神了。

总之,经过点装饰后,就是不一样了,功能增强了。

装饰器模式通用代码实现

我们还是用代码来实现一把,程序员都喜欢先搞个demo,然后再慢慢研究。

//抽象组件
public abstract class Component {public abstract void operation();
}
//具体组件
public class ConcreteComponent extends Component {@Overridepublic void operation() {System.out.println("ConcreteComponent operation");}
}
//装饰器抽象
public abstract class Decorator extends Component {protected Component component;public Decorator(Component component) {this.component = component;}@Overridepublic void operation() {component.operation();}
}
//具体装饰器
public class ConcreteDecorator extends Decorator {public ConcreteDecorator(Component component) {super(component);}@Overridepublic void operation() {System.out.println("开始前搞点事");super.operation();System.out.println("结束后搞点事");}
}
//测试
public class Client {public static void main(String[] args) {Component component = new ConcreteDecorator(new ConcreteComponent());component.operation();}
}

运行结果:

开始前搞点事
ConcreteComponent operation
结束后搞点事

以上便是装饰器模式的通用代码实现,下面我们来分析一下。

装饰器模式UML图


从UML途中可以看出,其中的角色

装饰器模式中的角色

  • 抽象组件(Component):可以是一个接口或者抽象类,充当被装饰类的原始对象,规定了被装饰对象的行为。

  • 具体组件(ConcreteComponent):实现/继承Component的一个具体对象,即被装饰对象。

  • 抽象装饰器(Decorator):通用的装饰ConcreteComponent的装饰器,其内部必然有一个属性指向Component;其实现一般是一个抽象类,主要为了让其子类按照其构造形式传入一个Component,这是强制的通用行为。如果系统中装饰逻辑单一,则并不需要实现许多装饰器,可以直接省略该类,而直接实现一个具体装饰器即可。

  • 具体装饰器(ConcreteDecorator):Decorator的具体实现类,理论上,每个ConcreteDecorator都扩展了Component对象的一种功能。

小结

装饰器模式角色分配符合设计模式的里氏替换原则、依赖倒置原则,从而使得其具备很强的扩展性,最终满足开闭原则。

装饰器模式的实现原理是,让装饰器实现与被装饰类(例如ConcreteComponent)相同的接口(例如Component),使得装饰器与被扩展类类型一致,并在构造函数中传入该接口对象,然后在实现这个接口的被包装类对象的现有功能上添加新功能。由于装饰器与被包装类属于同一类型(均为Component),且构造函数的参数为其实现接口类(Component),因此装饰器模式具备嵌套扩展功能,这样就能使用装饰器模式一层一层地对底层被包装类进行功能扩展了。

实战

在实际开发中,都会存在系统与系统之间的调用,假如说我们现在有个支付功能,现在一切都是没问题的,但是 我们此时需要对发起支付前的请求参数和支付后的相应参数。进行统一处理,原功能不变,只是在原功能上做了一点扩展(增强)。

老功能代码如下:

/*** @author 田先生* @date 2021-06-02** 欢迎关注公众号:java后端技术全栈*/
public interface IOrderPayService {String payment(Long orderId, BigDecimal amount);
}
public class OrderPayServiceImpl implements IOrderPayService {@Overridepublic String payment(Long orderId, BigDecimal amount) {//先调用余额查询是否足够System.out.println("发起支付,订单号:" + orderId + ", 支付金额:" + amount.toString());//调用支付系统String result = "订单id=" + orderId + "支付完成";System.out.println("支付结果:" + result);return result;}
}
public class OrderClient {public static void main(String[] args) {IOrderPayService orderPayService = new OrderPayServiceImpl();orderPayService.payment(10001L,new BigDecimal("5000"));}
}

运行输出:

发起支付,订单号:10001, 支付金额:5000
支付结果:订单id=10001支付完成

新需求,需要把这些请求参数和相应结果进行单独搜集处理,此时为了不影响原有功能,于是我们可以对其进行功能增强。

/*** @author 田先生* @date 2021-06-02** 欢迎关注公众号:java后端技术全栈*/
public class OrderPayDecorator implements IOrderPayService {private IOrderPayService orderPayService;public OrderPayDecorator(IOrderPayService orderPayService) {this.orderPayService = orderPayService;}@Overridepublic String payment(Long orderId, BigDecimal amount) {System.out.println("把这个订单信息(发起支付)" + "订单id=" + orderId + "支付金额=" + amount.toString() + " 【发送给MQ】");String result = orderPayService.payment(orderId, amount);System.out.println("把订单支付结果信息" + result + " 【发送给MQ】");return result;}
}
public class OrderClient {public static void main(String[] args) {IOrderPayService orderPayService =new OrderPayDecorator(new OrderPayServiceImpl());orderPayService.payment(10001L,new BigDecimal("5000"));}
}

运行输出:

把这个订单信息(发起支付)订单id=10001支付金额=5000 【发送给MQ】
发起支付,订单号:10001, 支付金额:5000
支付结果:订单id=10001支付完成
把订单支付结果信息订单id=10001支付完成 【发送给MQ】

整个过程,大家有没有发现,我们并没动原有的代码,仅仅只是做了功能增强。

装饰器模式在新项目中基本上不会用到,通常都是在老项目中使用,因为已有的功能不变,只是做了一些功能增强。

大神们是怎么用的

装饰器设计模式在JDK源码、Spring源码以及Mybatis源码中都有。

JDK源码中

装饰器模式比较经典的应用就是 JDK 中的 java.io 包下,InputStream、OuputStream、Reader、Writer 及它们的子类。

以 InputStream 为例

  • FileInputStream 是 InputStream 的子类,用来读取文件字节流

  • BufferedInputStream 是 InputStream 的子类的子类,可缓存的字节流

  • DataInputStream 也是 InputStream 的子类的子类,可直接读取 Java 基本类型的字节流

UML图


DataInputStream 中构造器入参便是自己的父类(InputStream)。

如果希望提供一个可以读取文件 + 可缓存的字节流,使用继承方式,就需要派生 FileBufferedInputStream;

如果希望提供一个可以读取文件 + 直接读取基本类型的字节流,使用继承方式,就需要派生 FileDataInputStream。

字节流功能的增强还包括支持管道 pipe、字节数组 bytearray、字节对象 object、字节流字符流的转换 等维度,如果用继承方式,那类的层级与种类会多到爆炸。

为了解决问题,这边就使用了装饰器模式。

Spring源码中

在Spring中,我们可以尝试理解一下TransactionAwareCacheDecorator类,这个类主要用来处理事务缓存,代码如下。

public class TransactionAwareCacheDecorator implements Cache {private final Cache targetCache;//构造方法入参类型为自己的父类(接口类型)public TransactionAwareCacheDecorator(Cache targetCache) {Assert.notNull(targetCache, "Target Cache must not be null");this.targetCache = targetCache;}public Cache getTargetCache() {return this.targetCache;}//...
}

TransactionAwareCacheDecorator就是对Cache的一个包装,因此,这里也是使用了装饰器模式。

Mybatis源码中

MyBatis中关于Cache和CachingExecutor接口的实现类也使用了装饰者设计模式。Executor是MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;CachingExecutor是一个Executor的装饰器,给一个Executor增加了缓存的功能。此时可以看做是对Executor类的一个增强,故使用装饰器模式是合适的。

在CachingExecutor 中

public class CachingExecutor implements Executor {//持有组件对象private Executor delegate;private TransactionalCacheManager tcm = new TransactionalCacheManager();//构造方法,传入组件对象public CachingExecutor(Executor delegate) {this.delegate = delegate;delegate.setExecutorWrapper(this);}@Overridepublic int update(MappedStatement ms, Object parameterObject) throws SQLException {//转发请求给组件对象,可以在转发前后执行一些附加动作flushCacheIfRequired(ms);return delegate.update(ms, parameterObject);}//...}

总结

看完装饰器模式后,你是否有感觉,装饰器模式和代理模式非常的相像,下面我们就来做个对比。

1.装饰器模式可以理解为一种特殊的代理模式。

2.装饰器模式强调自身的功能扩展,透明的扩展(即用户想增强什么功能就增强什么功能),可动态定制的扩展。

3.代理模式强调的是代理过程的控制。

优点

  • 装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态地给一个对象扩展功能,即插即用。

  • 通过使用不同装饰类及这些装饰类的排列组合,可以实现不同效果。

  • 装饰器模式完全遵守开闭原则。

缺点

  • 会出现更多的代码、更多的类,增加程序的复杂性。

  • 动态装饰在多层装饰时会更复杂。

3年工作必备 装饰器模式相关推荐

  1. 面向对象程序设计(OOP设计模式)-结构型模式之装饰器模式的应用与实现

    课程名称:程序设计方法学 实验4:OOP设计模式-结构型模式的应用与实现 时间:2015年11月18日星期三,第3.4节 地点:理1#208 一.实验目的 加深对结构型设计模式的理解以及在开发中的实际 ...

  2. java 装饰器模式

    http://eneasy.iteye.com/blog/174840 http://www.cnblogs.com/ikuman/archive/2013/01/29/2877913.html 1. ...

  3. 设计模式之: 装饰器模式

    什么是装饰器模式 作为一种结构型模式, 装饰器(Decorator)模式就是对一个已有结构增加"装饰". 适配器模式, 是为现在有结构增加的是一个适配器类,.将一个类的接口,转换成 ...

  4. Java设计模式12:装饰器模式

    装饰器模式 装饰器模式又称为包装(Wrapper)模式.装饰器模式以多客户端透明的方式扩展对象的功能,是继承关系的一个替代方案. 装饰器模式的结构 通常给对象添加功能,要么直接修改对象添加相应的功能, ...

  5. 装饰器模式(讲解+应用)

    转载自 设计模式(5)装饰器模式(讲解+应用) 目录 装饰器模式 为什么使用装饰器模式 应用实例 装饰器模式 看到装饰器是在看<Thinking in Java>一书的时候,看到文件读写那 ...

  6. java中装饰器_Java设计模式12:装饰器模式

    装饰器模式 装饰器模式又称为包装(Wrapper)模式.装饰器模式以多客户端透明的方式扩展对象的功能,是继承关系的一个替代方案. 装饰器模式的结构 通常给对象添加功能,要么直接修改对象添加相应的功能, ...

  7. IOS设计模式之二(门面模式,装饰器模式)

    本文原文请见:http://www.raywenderlich.com/46988/ios-design-patterns. 由 @krq_tiger(http://weibo.com/xmuzyq) ...

  8. java设计模式之 装饰器模式

    装饰器模式 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构. 这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装 ...

  9. java-IO流(5)-IO流中的设计模式(装饰器模式和适配器模式)的介绍

    目录 1装饰器模式 1.1定义 1.2代码实现 1.3装饰器特点 1.4装饰器在IO流中的使用 2配适器模式 2.1Adapter适配器 2.2代码实例 2.3适配器特点 2.4适配器优缺点 2.5适 ...

最新文章

  1. 【微信小程序】:小程序,新场景
  2. 这一年,宇宙“面目”愈发清晰
  3. 我国数字出版发展尚存三大难题
  4. PostgreSQL ODBC问题与探索SQLSpecialColumns
  5. Jupidator 0.8.0 发布,Java 应用自动更新框架
  6. Dockerfile的ONBUILD指令
  7. GVIM编辑器的配置
  8. 从fork-join /线程池调用的Singelton bean中的访问spring请求范围缓存
  9. linux shell运行脚本命令行参数,shell脚本命令行参数简介
  10. python正则表达式03--字符串中匹配数字
  11. 好玩的抖音、快手无水印视频下载工具
  12. 文档损坏了怎么修复?文档修复方法分享
  13. java栈里储存的是什么_JVM中的堆和栈到底存储了什么
  14. 直线端点画垂线lisp_AutoCAD中利用AutoLISP开发小程序,实现快速画直线对称中心线...
  15. 【行业应用案例】区块链+珠宝应用案例分析
  16. JAVA 面向对象和集合知识点总结
  17. 超五类屏蔽双绞线和计算机电缆区别,超五类单屏网线和六类单屏蔽网线的区别是什么?...
  18. WordPress二次元Kratos-pjax主题模板源码+
  19. 福州大学计算机学院拟录取名单,2017年福州大学数学与计算机学院学术型硕士研究生拟录取名单...
  20. 正面管教之PHP_正面管教家长互助(PHP)体验课1

热门文章

  1. python输入文字字符串、如何提取某个汉字_python提取字符串中的汉字数字字母
  2. 解析postgresql 删除重复数据案例
  3. 使用Python查询国内 COVID-19 疫情
  4. C语言----求解N以内的素数的两种典型方法以及其优化
  5. html图片跟随鼠标,跟随鼠标的图片 - 网页特效代码|网页特效观止 - 让你的网页靓起来!...
  6. php rfc3986规范,「PSR 规范」PSR-7 HTTP 消息接口规范
  7. python 线性回归_用Python实现线性回归算法
  8. BZOJ 2157 「国家集训队」旅游(树链剖分,线段树,边权转点权)【BZOJ计划】
  9. 入职地府后我成了人生赢家_【200811推文】闪闪而恋作者:钦点废柴+入职地府后我成了人生赢家作者:有只胖头鱼...
  10. php 开启mysql_php如何开启mysqli扩展