装饰模式
是什么?
装饰模式动态地给一个对象添加一些额外的职责。若要扩展功能,装饰模式提供了比继承更灵活的方案。
结构组成及作用
下图是wiki百科中对装饰模式定义的UML: 首先,从图中可以看出装饰模式的特性:
- ConcreteComponent会作为Decorator的属性。
- Decorator泛化(实现或继承)Component。
- Component与Decorator是聚合关系,所以一般Component不依赖Decorator,是可以独立存在的。
- 图中并没声明Component的类型,所以Component可以实现为接口,抽象类,甚至普通父类。
以上我觉得就已经清晰的解构了装饰模式。
那么,为什么要用装饰模式呢? 而不是用继承呢?
java.io包是一个广泛应用装饰模式的jdk实现,我直接用它来举例吧。 比如,我在使用FileInputStream读入某文本文件的时候,希望增加一个额外的功能:把输入流内的所有大写字符都转成小写,例如将ABc装成abc。代码实现
那么我没道理去修改FileInputStream代码吧?
使用继承重写FileInputStream?这个也不科学,因为
- 我只是想拓展某个功能(read),而继承这些InputStream去实现我的功能可能会得到了一些我不想要的细节或者东西。
- 再举个例子(例子可能牵强,暂时先用这个例子吧),比如我需要读文件的同时发送这些字符,我需要写个SendCharInputStream吧,那么后面的这个系统的另一开发者希望发送两次(为了说明结论我也是够拼了。。),难道让他再写一个DoubleSendCharInputStream吗?
所以,由上面的两个问题可以看出装饰模式的优点:
- 对拓展开放,对修改关闭。
- 避免待拓展类暴露一些与拓展该功能无关的细节。
当然,装饰模式的缺点也是有的:
- 会产生很多小对象。(记得刚学Java看到茫茫多Input和OutStream,当时就有点懵逼了)
示例实现
public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream in) {
super(in);
}
public int read() throws IOException {
int c = in.read();
return (c == -1 ? c : Character.toLowerCase((char)c));
}
public int read(byte[] b, int offset, int len) throws IOException {
int result = in.read(b, offset, len);
for (int i = offset; i < offset+result; i++) {
b[i] = (byte)Character.toLowerCase((char)b[i]);
}
return result;
}
}
效果及应用场景
我认为,当希望以一种透明的拓展性的功能添加给某个对象时,或者当存在大量的各种功能排列组合式的子类时,可以使用装饰模式。