装饰者模式 Decorator
装饰者模式 Decorator
装饰者模式(Decorator):动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
The Decorator Pattern is a structural design pattern that allows behavior to be added to individual objects, either statically or dynamically, without affecting the behavior of other objects from the same class.
装饰者模式角色如下:
⨳ Component
(抽象组件): 定义一个对象接口,可以给这些对象动态地添加新的职责。
⨳ ConcreteComponent
(具体组件): 实现Component
接口的具体对象,即被装饰的对象。
⨳ Decorator
(抽象装饰者): 维护一个指向Component
对象的引用,并实现和Component
接口一致的接口。
⨳ ConcreteDecorator
(具体装饰者): 对Decorator
进行具体的扩展和装饰,可以添加额外的行为或功能。
由UML
类图可以很清楚的看到装饰者模式角色之间的关系
⨳ 实现关系:装饰者与具体组件都实现抽象组件,也就是说具体组件和装饰者具有同一套规范;
⨳ 关联关系:装饰者内部持有抽象组件的引用,这样才可以对真实组件进行增强。
可以看到装饰者模式和代理模式一样,都和目标对象(具体主题、具体组件)实现同一个接口,而且装饰者和代理者一样都持有目标对象的引用。
装饰者角色和具体组件的关系,就像手套与手的关系,二者具有同样的形状(实现同一个接口),都可以握手、竖中指,但是真正完成这个动作的还得是具体组件,手套只是让握手更舒适(附加功能)。
装饰者模式,旨在将 Component
(被装饰者)和 Decorator
(装饰者)分离,Component
不需要知道 Decorator
的存在,而每个Decorator
也只关心自己的装饰功能即可,至于要装饰哪个 Component
,客户端决定,即使装饰其他Decorator
也无所谓(手套上再套手套),毕竟大家都是 Component
家族的。
基本实现
抽象组件 Component
Component
是定义一个对象接口,可以给这些对象动态地添加职责。
public interface Component {
void operation();
}
具体组件 ConcreteComponent
ConcreteComponent
是定义了一个具体的对象,也可以给这个对象添加一些职责。
public class ConcreteComponent implements Component{
@Override
public void operation() {
System.out.println("Concrete Component do something");
}
}
抽象装饰者 Decorator
Decorator
,装饰抽象类,继承了 Component
,从外类来扩展 Component
类的功能,但对于 Component
来说,是无需知道 Decorator
的存在的。
public abstract class Decorator implements Component{
private Component component;
public Decorator(Component component) {
this.component = component;
}
// 可以将具体装饰者公共的代码提取出来,不同的代码留给子类实现。
public abstract void before();
public abstract void after();
// 将执行动作转发给组件本身执行,可以在转发前后做装饰
@Override
public void operation() {
before();
component.operation();
after();
}
}
具体装饰者 ConcreteDecorator
具体的装饰对象,起到给 Component
添加职责的功能。每个Decorator
也只关心自己的装饰功能即可。
▪ 具体装饰者A
public class ConcreteDecoratorA extends Decorator{
public ConcreteDecoratorA(Component component){
super(component);
}
// 具体组件动作执行前的装饰
@Override
public void before() {
System.out.println("Concrete Decorator A do something ahead ");
}
// 具体组件动作执行后的装饰
@Override
public void after() {
System.out.println("Concrete Decorator A do something after ");
}
}
▪ 具体装饰者A
public class ConcreteDecoratorB extends Decorator{
public ConcreteDecoratorA(Component component){
super(component);
}
// 具体组件动作执行前的装饰
@Override
public void before() {
System.out.println("Concrete Decorator B do something ahead ");
}
// 具体组件动作执行后的装饰
@Override
public void after() {
System.out.println("Concrete Decorator B do something after ");
}
}
客户端 Client
//创建需要被装饰的组件
Component component = new ConcreteComponent();
//装饰者增强组件的代码
Decorator decorator = new ConcreteDecoratorA(component);
decorator.operation();
System.out.println("--------------");
//装饰者也可以装饰装饰者
decorator = new ConcreteDecoratorB(decorator);
decorator.operation();
输入结果如下:
Concrete Decorator A do something ahead
Concrete Component do something
Concrete Decorator A do something after
--------------
Concrete Decorator B do something ahead
Concrete Decorator A do something ahead
Concrete Component do something
Concrete Decorator A do something after
Concrete Decorator B do something after
源码赏析
JDK 之 IO 流
Java IO
类库有几十个的负责 IO 数据的读取和写入类。如果对 Java IO
类做一下分类,我们可以从字节/字符、输入/输出将它们划分为四类:
⨳ 字节输入流(InputStream
):如 ByteArrayInputStream
,FileInputStream
、ObjectInputStream
...
⨳ 字节输出流(OutputStream
):如 ByteArrayOutputStream
,FileOutputStream
、ObjectOutputStream
...
⨳ 字符输入流(Reader
):如 CharArrayReader
、FileReader
...
⨳ 字符输出流(Writer
):如 CharArrayWriter
、FileWriter
,PrintWriter
...
以字符输入流 (InputStream
) 为例,看看谁是装饰者,谁是被装饰者。
通过 UML
类图看,还是挺明显的:
⨳ Component
是 InputStream
,定义了 read
方法。
⨳ ConcreteComponent
是 FileInputStream
和 ByteArrayInputStream
是 ,其中 FileInputStream
可以读取文件,ByteArrayInputStream
可以读取字节数组。
⨳ Decorator
是 FilterInputStream
,内有具体装饰者的重复代码的抽象整合。
⨳ ConcreteDecorator
是 BufferedInputStream
和 BufferedInputStream
,其中 BufferedInputStream
支持带缓存功能的数据读取,DataInputStream
支持按照基本数据类型来读取数据。
下面通过客户端代码使用,来更好的理解装饰者对被装饰者的增强。假设是从文件中读基本数据类型:
FileInputStream in = new FileInputStream("/test.txt");
// 使用BufferedInputStream 包装
BufferedInputStream bin = new BufferedInputStream(in);
// 使用DataInputStream 包装 DataInputStream
din = new DataInputStream(bin);
// 取出int数据
int data = din.readInt();
BufferedInputStream
和 FileInputStream
都对 FileInputStream
完成了功能上的增强,而且这种增强可以叠加下去。
MyBatis 之 Cache
MyBatis
对缓存 Cache
的装饰者非常多,如上 UML
类图只节选了几个装饰者。
这里的具体组件就是 PerpetualCache
表示持久缓存,本质就是一个 HashMap
。其余几个 Cache
都是装饰者:
⨳ FifoCache:先进先出(FIFO)缓存装饰者,用于控制缓存的大小。当缓存满时,根据先进先出的原则淘汰最早放入的缓存项。
⨳ LruCache:最近最少使用(LRU)缓存装饰者,根据缓存项的访问顺序来进行淘汰,保留最近使用的缓存项,以提高缓存命中率。
⨳ LoggingCache:用于记录缓存操作日志的装饰者,可以在查询、插入、更新、删除等操作前后记录日志,用于调试和监控缓存的使用情况。
⨳ ...
// CacheBuilder 节选
private final List<Class<? extends Cache>> decorators;
public CacheBuilder addDecorator(Class<? extends Cache> decorator) {
if (decorator != null) {
this.decorators.add(decorator);
}
return this;
}
最后的建造,就会使用这些装饰者创建被包装的 Cache
:
/**
* 构建缓存实例的方法
* 使用装饰者模式动态地添加缓存的功能
* @return 构建好的缓存实例
*/
public Cache build() {
// 设置缓存的默认实现
setDefaultImplementations();
// 根据指定的实现和缓存 ID 创建基础的缓存实例
Cache cache = newBaseCacheInstance(implementation, id);
// 设置缓存实例的属性
setCacheProperties(cache);
// 如果缓存类型是 PerpetualCache,则应用装饰者模式增强缓存功能
if (PerpetualCache.class.equals(cache.getClass())) {
// 遍历预定义的装饰器列表
for (Class<? extends Cache> decorator : decorators) {
// 创建装饰器实例并将当前缓存实例作为参数传入,动态扩展缓存功能
cache = newCacheDecoratorInstance(decorator, cache);
// 为新装饰的缓存实例设置属性
setCacheProperties(cache);
}
// 执行标准的装饰器设置操作
cache = setStandardDecorators(cache);
}
// 如果缓存不是 LoggingCache 类型,则添加日志记录功能
else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
// 创建 LoggingCache 装饰器,包装当前缓存实例,以添加日志记录功能
cache = new LoggingCache(cache);
}
// 返回构建好的缓存实例
return cache;
}
可以看到 Mybatis
会使用遍历建造过程中添加的 Decorator
装饰 Cache
,而且 LoggingCache
是必须要加上的装饰者。
总结
装饰者模式允许你在运行时动态地将新功能添加到对象中,而无需修改其代码。
装饰者模式优点如下:
⨳ 符合单一职责原则:装饰者模式使得每个类都只负责一件事情,即原始对象的功能,而装饰器类负责添加额外的功能。
⨳ 符合开闭原则:装饰者模式遵循开放-封闭原则,即对扩展是开放的,对修改是封闭的。这意味着你可以在不修改现有代码的情况下扩展对象的行为。
⨳ 避免类爆炸:通过装饰者模式,你可以通过组合少量的装饰器类来创建大量的组合,而不是创建大量的子类。
这些优点代理模式也有。
缺点如下:
⨳ 增加复杂性:装饰者模式可能会导致许多小对象的产生,从而增加了系统的复杂性。
⨳ 可能引入性能问题:如果装饰器的嵌套层次很深,可能会导致性能问题,因为每一层装饰都需要执行额外的逻辑。
⨳ 调试困难:由于装饰者模式的灵活性,当系统中存在大量的装饰器类时,可能会增加调试的难度,特别是在追踪对象的行为时。
转载自:https://juejin.cn/post/7356055073585856563