四个角色:
- Component (抽象组件) → 声明具体组件实现的业务方法,让客户端以一致的方式处理为修饰和修饰后的对象;
- ConcreteComponent (具体组件) → 抽象组件的具体实现;
- Decorator (抽象装饰类) → 包含对组件的引用,并重写抽象组件的方法;
- ConcreteDecorator (具体装饰类) → 抽象装饰类的具体实现,除了重写方法外,还可以添加附加功能;
适用场景:
- 在不影响其他对象的情况下,快速动态透明地为单个对象添加功能;
- 不支持继承扩展类的场景,如final关键字修饰的类;
优点
快速扩展对象功能,比继承灵活,不会导致类个数急剧增加,动态增删对象实例功能,按需按顺序组合功能;
缺点
- 顺序调用链装饰时,删除某个装饰器需要修改上下文代码;
- 容易增加很多装饰对象,增加代理理解难度;
- 和桥接模式一样,组合相比继承,更不容易找到对象间的调用关系;
附:装饰器模式应用示例 → Java IO类库
在网上找了张Java IO的继承树形图:
网络异常,图片无法展示
|
两个变化维度:字节流 or 字符流,输入流 or 输出流,组合出四个子类:
InputStream、OutputStream、Reader和Writer,针对不同的读写场景,从这四个父类的基础上,又扩充出很多子类。
在日常使用时,又涉及到了一个 低级流 和 高级流 的概念,高级流不能直接使用,需要传入低级流,裹好几层,如:
InputStream in = new FileInputStream("/user/jie/test.txt"); InputStream bin = new BufferedInputStream(in); DataInputStream din = new DataInputStream(bin); int data = din.readInt();
原理就是采用了装饰器模式,低级流可以按需进行功能增强,如上面的支持缓存,支持按照基本类型读取数据。可以打开这三个类看看:
网络异常,图片无法展示
|
可以看到,都继承了InputStream类,中间两个类的构造函数传递的InputStream的实例:
网络异常,图片无法展示
|
看下DataInputStream.readInt()方法:
网络异常,图片无法展示
|
int是4个字节,所以这里读了4次,而代码中传入的in是BufferedInputStream的,会调它的read()方法:
网络异常,图片无法展示
|
fill()填充缓冲区,最后返回一个字节数组。
另外,不知道眼尖的你有没有发现,这两个类不是直接继承InputStream,而是继承自FilterInputStream,这样做的原因是:
有些装饰器本身不需要真正处理read()等方法,但是装饰器模式的 链式传递,不用到也要实现这些方法。而每个这样的装饰器都重写方法的话,会存在大量重复代码。用一个装饰器父类FilterInputStream提供默认实现,以此减少这些重复代码。