在使用Java中的IO类库的时候,是不是快要被它那些功能相似,却又绝对可称得上庞杂的类搞得要发疯了?或许你很不明白为什么要做这么多功能相似的几十个类出来,这就是Decorator模式将要告诉你的了。
在IO处理中,Java将数据抽象为流(Stream)。在IO库中,最基本的是InputStream和OutputStream两个分别处理输出和输入的对象(为了叙述简便起见,这儿只涉及字节流,字符流和其完全相似),但是在InputStream和OutputStream中之提供了最简单的流处理方法,只能读入/写出字符,没有缓冲处理,无法处理文件,等等。它们只是提供了最纯粹的抽象,最简单的功能。
如何来添加功能,以处理更为复杂的事情呢?你可能会想到用继承。不错,继承确实可以解决问题,但是继承也带来更大的问题,它对每一个功能,都需要一个子类来实现。比如,我先实现了三个子类,分别用来处理文件,缓冲,和读入/写出数据,但是,如果我需要一个既能处理文件,又具有缓冲功能的类呢?这时候又必须在进行一次继承,重写代码。实际上,仅仅这三种功能的组合,就已经是一个很大的数字,如果再加上其它的功能,组合起来的IO类库,如果只用继承来实现的话,恐怕你真的是要被它折磨疯了。
图八:JDK中IO流的类层次
Decorator模式可以解决这个问题。Decorator字面的意思是装饰的意思,在原有的基础上,每添加一个装饰,就可以增加一种功能。这就是Decorator的本意。比如,对于上面的那个问题,只需要三个Decorator类,分别代表文件处理,缓冲和数据读写三个功能,在此基础上所衍生的功能,都可以通过添加装饰来完成,而不必需要繁杂的子类继承了。更为重要的是,比较继机制承而言,Decorator是动态的,可以在运行时添加或者去除附加的功能,因而也就具有比继承机制更大的灵活性。
上面就是Decorator的基本思想,下面的是Decorator模式的静态结构图:
图九:Decorator模式的类图
可以看到,一个Decorator与装饰的Subject对象有相同的接口,并且除了接口中给出的方法外,每个Decorator均有自己添加的方法,来添加对象功能。每个Decorator均有一个指向Subject对象的引用,附加的功能被添加在这个Subject对象上。而Decorator对象本身也是一个Subject对象,因而它也能够被其他的Decorator所修饰,提供组合的功能。
在Java IO操作中,经常可以看到诸如如下的语句:
2
3 FilterInputStream myStream=new LineNumberInputStream
4
5 ( new BufferInputStream( new StringBufferInputStream( myStringBuffer)));
6
7 myStream.read();
8
9 myStream.line();
10
11
多个的Decorator被层叠在一起,最后得到一个功能强大的流。既能够被缓冲,又能够得到行数,这就是Decorator的威力!
不仅仅如此,Java中的IO还允许你引入自定义的Decorator,来实现自己想要的功能。在良好的设计背景下,这做起并不复杂,只需要4步:
创建两个分别继承了FilterInputStream和 FilterOutputStream的子类
重载read()和write()方法来实现自己想要的功能。
可以定义或者重载其它方法来提供附加功能。
确定这两个类会被一起使用,因为它们在功能上是对称的。
就这样,你就可以无限的扩展IO的功能了。
在了解了IO中的Decorator后,我们再来看一个Decorator模式应用的具体的例子。这个例子原本是出现在GOF书中的,这儿稍作改动,引来示例。
在一个图形用户界面(GUI)中,一个组件有时候需要用到边框或者滚动条,而有时候又不需要,有时候可能两者都要用到。当需要动态的去处或者添加职能的时候,就可以考虑使用Decorator模式了。这儿对于一个VisualComponent组件对象,我们引入了两个Decorator类:BoderDecorator和ScrollDecorator,分别用来为组件添加边框和处理滚动。程序类图如下:
图十:Decorator模式的应用例子
程序写得很简单,没有包括具体的代码,只是有一个可以运行的框架以供参考。代码如下:
2
3 class Client{
4
5 public static void main (String[] args ){
6
7 Window window = new Window ();
8
9 TextView textView = new TextView ();
10
11 window.setContents (
12
13 new BorderDecorator (
14
15 new ScrollDecorator (textView, 500), 1));
16
17 }
18
19 }
20
21 //Windows类用来容纳组件对象
22
23 class Window{
24
25 VisualComponent contents;
26
27 public Window () {}
28
29 public void setContents (VisualComponent vc){
30
31 contents = vc;
32
33 }
34
35 }
36
37 //VisualComponent类定义了组件的接口
38
39 class VisualComponent{
40
41 public VisualComponent (){}
42
43 public void draw (){}
44
45 public void resize (){}
46
47 }
48
49 //TextView类是一个显示文本的具体的组件
50
51 class TextView extends VisualComponent{
52
53 public TextView (){}
54
55 public void draw (){
56
57 …
58
59 }
60
61 public void resize (){
62
63 …
64
65 }
66
67 }
68
69 //Decorator类继承于VisualComponent,定义所有Decorator的缺省方法实现
70
71 class Decorator extends VisualComponent{
72
73 private VisualComponent component;
74
75 public Decorator (VisualComponent vc) {
76
77 this.component=vc;
78
79 }
80
81 public void draw () {
82
83 component.draw ();
84
85 }
86
87 public void resize () {
88
89 component.resize ();
90
91 }
92
93 }
94
95 //BorderDecorator类为组件提供边框
96
97 class BorderDecorator extends Decorator{
98
99 private int width;
100
101 public BorderDecorator (VisualComponent vc, int borderWidth){
102
103 super (vc);
104
105 width = borderWidth;
106
107 }
108
109 public void draw (){
110
111 super.draw ();
112
113 drawBorder (width);
114
115 }
116
117 private void drawBorder (int width){
118
119 …
120
121 }
122
123 }
124
125 //ScrollDecorator类为组件提供滚动条
126
127 class ScrollDecorator extends Decorator{
128
129 private int scrollSize;
130
131 public ScrollDecorator (VisualComponent vc, int scrSize){
132
133 super (vc);
134
135 scrollSize = scrSize;
136
137 }
138
139 public void draw (){
140
141 scroll();
142
143 super.draw ();
144
145 }
146
147 private void scroll (){
148
149 …
150
151 }
152
153 }
154
Decorator确实能够很好的缓解当功能组合过多时子类继承所能够带来的问题。但是在得到很大的灵活性的同时,Decorator在使用时也表现得较为复杂。看看仅仅为了得到一个IO流,除了要创建核心的流外,还要为其加上各种各样的装饰类,这使得代码变得复杂而难懂。有几个人一开始时没有被Java的IO库吓一跳呢?
Bridge模式用来分离抽象和实现,使得这两个部分能够分别的演化而不必修改另外一部分的内容。通常的,可以在实现部分定义一些基本的原子方法,而在抽象部分则通过组合定义在实现层次中的原子方法来实现系统的功能。Decorator模式通过聚合机制来为对象动态的添加职责,解决了在子类继承中容易引起的子类爆炸的问题。