设计模式系列之装饰模式(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 ...
随机推荐
- [工具-003]如何从ipa中提取info.plist并提取相应信息
最近公司的产品要进行一次批量的升级,产品中的一些配置存放在info.plist,为了保证产品的信息无误,我们必须要对产品的发布信息进行验证.例如:广告ID,umeng,talkingdata等等.那么 ...
- [JavaWeb基础] 004.用JSP + SERVLET 进行简单的增加删除修改
上一次的文章,我们讲解了如何用JAVA访问MySql数据库,对数据进行增加删除修改查询.那么这次我们把具体的页面的数据库操作结合在一起,进行一次简单的学生信息操作案例. 首先我们创建一个专门用于学生管 ...
- ReentrantLock解析及源码分析
本文结构 Tips:说明一部分概念及阅读源码需要的基础内容 ReentrantLock简介 公平机制:对于公平机制和非公平机制进行介绍,包含对比 实现:Sync源码解析额,公平和非公平模式的加锁.解锁 ...
- String类练习
1.模拟一个trim方法,去除字符串两端的空格 2.将一个字符串进行反转.将字符串中指定部分进行反转 3.获取一个字符串在另一个字符串中出现的次数 4.获取两个字符串中最大相同子串 5.对字符串中字符 ...
- 团队作业第五次——Alpha冲刺
这个作业属于哪个课程 软件工程 这个作业要求在哪里 团队作业第五次--Alpha冲刺 这个作业的目标 Alpha冲刺 作业正文 正文 github链接 项目地址 其他参考文献 无 一.代码规范与计划 ...
- Rocket - tilelink - toBools
https://mp.weixin.qq.com/s/UGMH8EoaVcFkkQW-l4HLWg 分析toBools在Intellij中显示为红色的问题. 1. 问题 在TLA ...
- SpringBoot 定制 starter 启动器
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 在实际项目开发中,我们常常会用到各种各样的 starter,这些starter 有的是有 springb ...
- 使用turtle库绘制一个叠加等边三角形
import turtle as t t.setup(600, 600, None,None) t.pu() t.fd(-120) t.pensize(5) t.width(5) t.pencolor ...
- Java实现 LeetCode 33 搜索旋转排序数组
33. 搜索旋转排序数组 假设按照升序排序的数组在预先未知的某个点上进行了旋转. ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] ). 搜索一个给定的目标值, ...
- java实现猜算式
题目:猜算式 你一定还记得小学学习过的乘法计算过程,比如: x 15 ------ 273 ------ 请你观察如下的乘法算式 *** x *** -------- *** *** *** ---- ...