设计模式系列之装饰模式(Decorator Pattern)——扩展系统功能
说明:设计模式系列文章是读刘伟
所著《设计模式的艺术之道(软件开发人员内功修炼之道)》
一书的阅读笔记。个人感觉这本书讲的不错,有兴趣推荐读一读。详细内容也可以看看此书作者的博客https://blog.csdn.net/LoveLion/article/details/17517213
模式概述
对新房进行装修并没有改变房屋用于居住的本质,但它可以让房子变得更漂亮、更温馨、更实用、更能满足居家的需求。在软件设计中,我们也有一种类似新房装修的技术可以对已有对象(新房)的功能进行扩展(装修),以获得更加符合用户需求的对象,使得对象具有更加强大的功能
。这种技术对应于一种被称之为装饰模式
的设计模式。
装饰模式可以在不改变一个对象本身功能的基础上给对象增加额外的新行为
,在现实生活中,这种情况也到处存在,例如一张照片,我们可以不改变照片本身,给它增加一个相框,使得它具有防潮的功能,而且用户可以根据需要给它增加不同类型的相框,甚至可以在一个小相框的外面再套一个大相框。
模式定义
如何让系统中的类可以进行扩展但是又不会导致类数目的急剧增加?根据“合成复用原则”,在实现功能复用时,我们要多用关联,少用继承。
装饰模式是一种用于替代继承的技术
,它通过一种无须定义子类的方式来给对象动态
增加职责,使用对象之间的关联关系
取代类之间的继承关系
。在装饰模式中引入了装饰类,在装饰类中既可以调用待装饰的原有类的方法,还可以增加新的方法,以扩充原有类的功能。
装饰模式(Decorator Pattern)
:动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。
模式结构图
在装饰模式中,为了让系统具有更好的灵活性和可扩展性,通常会定义一个抽象装饰类,而将具体的装饰类作为它的子类,装饰模式结构图如下所示:
在装饰模式结构图中包含如下几个角色:
Component
(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。ConcreteComponent
(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。Decorator
(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。ConcreteDecorator
(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。
由于具体构件类和装饰类都实现了相同的抽象构件接口,因此装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。
模式伪代码
装饰模式的核心在于抽象装饰类
的设计,其典型代码如下所示:
public interface Component {
void operation();
}
public class Decorator implements Component {
// 维持一个对抽象构件对象的引用
private Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
值得注意的是:
在
Decorator
中并未真正实现operation()
方法,而只是调用原有component
对象的operation()
方法,它没有真正实施装饰,而是提供一个统一的接口,将具体装饰过程交给子类完成。由于在抽象装饰类
Decorator
中注入的是Component
类型的对象,因此可以将一个具体构件对象注入其中,再通过具体装饰类来进行装饰;此外,我们还可以将一个已经装饰过的Decorator
子类的对象再注入其中进行多次装饰
,从而对原有功能的多次扩展。
在Decorator
的子类即具体装饰类中将继承operation()
方法并根据需要进行扩展,典型的具体装饰类代码如下:
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
@Override
public void operation() {
// 调用原有业务方法
super.operation();
// 调用新增业务方法
addedBehavior();
}
// 新增业务方法
public void addedBehavior() {
}
}
具体装饰类中可以调用到抽象装饰类的operation()
方法,同时可以定义新的业务方法,如addedBehavior()
透明装饰模式 vs 半透明装饰模式
上面介绍的装饰模式就是透明(Transparent)装饰模式
,也即标准装饰模式。
透明装饰模式,客户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该将对象声明为具体构件类型或具体装饰类型,而应该全部声明为抽象构件类型。对于客户端而言,具体构件对象和具体装饰对象没有任何区别。也就是应该使用如下代码:
// 透明装饰模式应当使用抽象构件类型定义对象
Component c, c1;
c = new ConcreteComponent();
c1 = new ConcreteDecorator(c);
// **注意:透明装饰模式不应该使用具体类型来声明对象,比如下面的代码**
// ConcreteComponent c = new ConcreteComponent();
// ConcreteDecorator c1 = new ConcreteDecorator(c);
但是在实际使用过程中,由于新增行为可能需要单独调用(即客户端想自己单独调用装饰类中新增的方法来控制是否以及如何增强
),因此这种形式的装饰模式也经常出现,这种装饰模式被称为半透明(Semi-transparent)装饰模式
半透明装饰模式
中的具体装饰类对应的实现,即变为如下:
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
//@Override
//public void operation() {
// 调用原有业务方法
// super.operation();
// 调用新增业务方法
//addedBehavior();
//}
// 新增业务方法
public void addedBehavior() {
}
}
注意 半透明装饰模式
中的ConcreteDecorator
类继承了父装饰类Decorator
的operation()
方法但并没有重写operation()
方法,并新增了业务方法addedBehavior()
,但这两个方法是完全独立的,没有任何调用关系。
即 半透明装饰模式
中的ConcreteDecorator
中的operation()
并没有对注入的Component
进行增强,只是增加了额外的方法addedBehavior()
,这样一来是否增强Component
就取决于客户端是否调用addedBehavior()
方法。
客户端想增强Component
,就需要分别调用这两个方法,代码片段如下:
// 原有构件
Component component = new ConcreteComponent();
// 用装饰器包装原有构件
ConcreteDecorator enhancedComponent = new ConcreteDecorator(component);
// 调用原有业务方法
enhancedComponent.operation();
// 调用新增业务方法,从而实现增强
enhancedComponent.addedBehavior();
显然半透明装饰模式
最大的缺点在于不能实现对同一个对象的多次装饰,而且客户端需要有区别地对待装饰之前的对象和装饰之后的对象。
模式应用
模式在JDK中的应用
Java
中的java.io
包下io流的设计就充分使用了装饰模式。举个具体的例子,BufferedInputStream
就是一个具体装饰者,它能为一个原本没有缓冲(buffer
)功能的InputStream
增加缓冲的功能。下面的代码应该司空见惯。
BufferedInputStream reader = new BufferedInputStream(new FileInputStream(new File("/tmp/1.txt")));
FileInputStream
本没有缓冲功能,每次调用read
方法,都会发起系统调用读数据。用BufferedInputStream
来装饰它,那么每次调用read
方法,会向操作系统多读一定量数据进入内存的buf[]
,这样就提高了读的效率,避免频繁发起系统调用。
BufferedInputStream
构造器中注入了InputStream
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
BufferedInputStream
继承了父装饰器FilterInputStream
,维持了对InputStream
的引用
public class FilterInputStream extends InputStream {
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
}
BufferedInputStream
重写了read()
从而实现对引用的InputStream
增强。有兴趣可以读读相关源码。
模式在开源项目中的应用
mybatic
中org.apache.ibatis.session.Configuration#newExecutor
有这样一段代码
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
上面的代码可以看到,如果开启了二级缓存则装饰原先的Executor
,其实org.apache.ibatis.executor.CachingExecutor
就是一个装饰器
public class CachingExecutor implements Executor {
private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
}
模式总结
装饰模式降低了系统的耦合度,可以动态增强对象的功能。
主要优点
(1) 对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。
(2) 可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。
(3) 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合“开闭原则”。
适用场景
(1) 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
(2) 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种扩展或者扩展之间的组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类已定义为不能被继承(如Java
语言中的final
类)。
设计模式系列之装饰模式(Decorator Pattern)——扩展系统功能的更多相关文章
- 设计模式系列之装饰模式(Decorator Pattern)
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装.这种模式创建了一个装饰类,用来包装原 ...
- 乐在其中设计模式(C#) - 装饰模式(Decorator Pattern)
原文:乐在其中设计模式(C#) - 装饰模式(Decorator Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 装饰模式(Decorator Pattern) 作者:weba ...
- 二十四种设计模式:装饰模式(Decorator Pattern)
装饰模式(Decorator Pattern) 介绍动态地给一个对象添加一些额外的职责.就扩展功能而言,它比生成子类方式更为灵活.示例有一个Message实体类,某个对象对它的操作有Insert()和 ...
- 设计模式-09装饰模式(Decorator Pattern)
1.模式动机 一般有两种方式可以实现给一个类或对象增加行为: 继承机制:使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法.但是这种方法是 ...
- 设计模式-装饰模式(Decorator Pattern)
装饰模式(Decorator Pattern):动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活
- 浅谈设计模式--装饰者模式(Decorator Pattern)
挖了设计模式这个坑,得继续填上.继续设计模式之路.这次讨论的模式,是 装饰者模式(Decorator Pattern) 装饰者模式,有时也叫包装者(Wrapper),主要用于静态或动态地为一个特定的对 ...
- 装饰模式(Decorator pattern)
装饰模式(Decorator pattern): 又名包装模式(Wrapper pattern), 它以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案. 装饰模式以对客户透明的方式动态的给 ...
- 设计模式系列之单例模式(Singleton Pattern)——确保对象的唯一性
模式概述 模式定义 模式结构图 饿汉式单例与懒汉式单例 饿汉式单例 懒汉式单例 模式应用 模式在JDK中的应用 模式在开源项目中的应用 模式总结 主要优点 适用场景 说明:设计模式系列文章是读刘伟所著 ...
- 设计模式 - 装饰者模式(Decorator Pattern) Java的IO类 用法
装饰者模式(Decorator Pattern) Java的IO类 用法 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26716 ...
随机推荐
- 前端星计划笔记-day1
前端 功能,美观,安全,无障碍,性能,兼容,体验 前端编程思想 WA doctype: 文档版本 浏览器决定渲染模式 语义化: 所有的标签都有自己的含义,属性 可读性 前端规范 whatwg css显 ...
- 应小姐姐要求,整理常用Git操作命令,她都学会了,你确定不收藏
前言 因为个人原因,转化了部门之后已经很久没有接触过开发层级的东西了,好多东西基本都忘记了,但是新的部门有时候会用到相应的研发部的代码和文档手册,所以耳边就充斥这一句话 这个为什么下载不了?这个为什么 ...
- python中的数据存储认识
声明:本人是一个初学者,博客内容基本也是一些基础的东西,如果说的有什么问题欢迎纠正. 前言 许多人初学python之前应该也学习过其他的语言,比如博大精深的c语言,笔者在学习python之前就学习过c ...
- Beta冲刺 —— 6.1
这个作业属于哪个课程 软件工程 这个作业要求在哪里 Beta冲刺 这个作业的目标 Beta冲刺 作业正文 正文 github链接 项目地址 其他参考文献 无 一.会议内容 1.讨论并解决每个人存在的问 ...
- Rocket - util - Annotations
https://mp.weixin.qq.com/s/7C8ZmPpwAqFqyKjL9K40Fg 介绍util中定义的注解(Annotations). 1. Annotation ...
- golang内存逃逸
golang程序变量会携带油一组校验数据,用来证明它的整个生命周期是否在运行时完全可知.如果变量通过了这些校验,它就可以在栈上分配.否则就说它逃逸了,必须在堆上分配 能引起变量逃逸到堆上的典型 ...
- (Java实现) 子集和问题
回溯算法也叫试探法,它是一种系统地搜索问题的解的方法.回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试.用回溯算法解决问题的一般步骤为: 1.定义一个解空间,它包含问题的解 ...
- Java中那些烦人的位运算(&,|...)
& 和 && 相同点: 都表示"与"操作.这里的"与"和数学中的"与或非"中的"与"意义相同,都 ...
- Java实现 LeetCode 126 单词接龙 II
126. 单词接龙 II 给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列.转换需遵循如下规则: ...
- Java实现 洛谷 P1583 魔法照片
import java.util.*; class Main{ public static void main(String[] args) { Scanner in = new Scanner(Sy ...