装饰器模式

装饰器模式的经典应用就是在IO类库的设计上,如下面的用例所示,实现对文件的读取功能,可以选择使用FileInputStream类也可以使用BufferedInputStream嵌套FileInputStream的方式来完成,在具体API的使用上都可以使用read(bytes)的方式。

从Java IO看装饰器模式

1
2
3
4
5
6
7
8
9
10
11
12
13
private static void testDataInputStream() {
File testFile = new File("/Users/ivonhoe/Workspace/Java/Ant/simlog/pom.xml");
try {
FileInputStream fileInputStream = new FileInputStream(testFile);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
char a = dataInputStream.readChar();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

虽然在API调用上两者表现几乎一致,但是在具体的实现上有较大的差别,BufferedInputStream增加了缓存buffer,通过先批量填充buffer再读取内存的方式提高了读取性能并且可以解决两端流速不匹配的问题。通过对象的组合设计对读取方式实现了增强,同样的,想要在buffer读取的基础上增加按照数据类型读取的能力,可以通过嵌套DataInputStream的方式实现。


yuque_diagram.png
Java IO类库在设计上选择了组合而不是继承的方式,在实现功能的同时减少了类的数量。通过IO类库可以简单总结下装饰器模式的特点。【通过向现有的对象添加新的功能,不改变其内部结构,并且在原有功能基础上对其进行增强】。就像同样是文件读取功能,Buffered能力和readChar能力可以组合使用,并没有改变被包装对象的内部实现,并且增强了不同类型的read能力。

装饰器模式定义

image.png
装饰器模式的角色有:

  • 抽象构建(Component):给出一个抽象接口,已规范准备接收附加责任的对象
  • 具体构建(ConcreteComponent):定义一个将要接收附加责任的类
  • 抽象装饰(Decorator):持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口
  • 具体装饰(ConcreteDecorator):负责给构件对象“贴上”附加的责任

对比Java IO库中的设计:

  • InputStream -> Component
  • FileInputStream -> ConcreteComponent
  • FilterInputStream -> Decorator
  • BufferedInputStream / GZIPInputStream / DataInputStream -> ConcreteDecorator

装饰器模式的优点:更灵活的扩展对象功能。相比较使用类继承,对象包装的方式更灵活。可以根据实际需要为一个对象任意组合嵌套多种装饰功能。
从代码结构的角度来说,装饰器模式和代理模式都使用了对象包装的方式,那么他们的的差异在哪里?在代理模式中,代理类附加的功能和原始类不相关,而在装饰器模式中,装饰器类附加的是和原始类相关的增强功能。

总结

装饰器模式可以解决继承关系过于复杂的问题,通过组合关系替代继承关系。装饰器模式的主要作用是给原始类增加增强功能。一个原始类可以嵌套多个装饰器类。装饰器类需要与原始类继承相同的抽象类或者接口。

参考文档

《王争:设计模式之美》
https://www.cnblogs.com/pluto-charon/p/16030199.html