Decorator之前
在Java程序中,我们常会在IO的时候用到类似于如下的代码:
InputStream in = new BufferedInputStream(
new FileInputStream("test.txt"));
从代码本身的直观意义来说,这部分代码很好理解。
1.使用FileInputStream打开test.txt文件。使用BufferedInputStream类封装了这个类之后,返回的对象就具备了缓冲部分数据的功能。
2. 比较有意思的一点在于,虽然我们用BufferedInputStream封装了前面的对象,但是我们还是可以把它当成一个InputStream类的对象来使用。
粗粗看来,这种写法似乎也没有什么特殊的。假定我们自己从最初的角度来设计这个问题,可能会采用一个类似于继承的办法,如下图所示:
这种设想的一个基本点是,如果每次需要对原有的类做增强的时候,我们可以通过对在原有类的基础上派生出一个新的类来。这样既不会修改原来的类,也可以实现良好的可扩充。
回头看前面那个示例代码,我们会发现这样的设计其实会存在一个隐藏的问题的。
1.如前面所示,如果我们每次需要增加新的功能的时候,都继承一个新的类出来。如果我们需要一个既有FileInputStream类的特性同时又有BufferedInputStream的特性的类呢?这下问题就来了,如果我们继续继承这两个类的话,可能就需要定义一个新的类。或者我们也可以再从InputStream派生一个新的类,有两个类的特性。这样的话,我们可能就不可避免的在新的类中要重复部分FileInputStream和BufferedInputStream的代码。重复的代码显得比较讨厌,你懂的。
2. 如果我们需要一个类具有多种特性呢?比如说,我既要有BufferedInputStream的特性,然后还需要一些自定义的特性,比如说将里面某些字符的大小写变化一下。那该怎么办呢?总不能再去从多个类中间来继承吧?如果这样的话,我们在类继承结构中每增加一种新的特性,这个新的特性就可能和其他的类可能形成一种组合的关系。如果再考虑到组合的顺序和次数的话...显然,类会呈现出一个爆炸式的增长。
综上所述,问题的根源在于,我们要求类能够根据多个不同的特性部分进行灵活的组合时,采用继承的方法是不合适的。
Decorator的引申和推导
现在我们看看decorator模式的uml图:
这个图中间有几个比较有意思的地方,
1. ConcreteComponent是某一个具体的类,相当于前面的FileInputStream。它是作为一个封装的最核心部分。就好像是包包子的馅,没有它就不能成为包子了。
2. ConcreteDecoratorA,B这些类都有一个封装的对象,然后每个具体的类针对该封装的对象来添加新的特性。这就像是往包子馅外面裹皮,具体是裹成什么样的就看你要添加什么新特性。
3. 还有一个问题就是一直让人开始比较困惑的。既然前面的设计方式会带来类爆炸的问题,这个设计是怎么解决的呢?问题的关键点就在于Decorator类和ConcreteDecorator类的继承。通过Decorator类具体封装了一个ConcreteComponent对象。因为继承自同一个类,我们可以把它当成一个父类对待。同样,每个具体的ConcreteDecorator在添加新的特性时,就相当于在一个封装的对象里添加东西,而不需要去继承。而且比较有意思的是,我们这么封装后的对象,也可以当成一个抽象的父类进行进一步的封装。这种设计思想不是正好利用到了常用的一个设计原则么?优先考虑组合而不是继承。
Decorator模式在Java I/O包中的应用
Decorator模式可以说在java的IO包里头得到了重度的应用。一个典型的和InputStream类关联的类关系图就可见一斑:
从图中我们可以看到,InputStream就相当于最高的父类。而FileInputStream, StringBufferInputStream...这几个类相当于用来被包装核心。如最前面的代码所示,它们只能用在最里边。而那些用来包装的类如BufferedInputStream...这些都继承自FilterInputStream类。FilterInputStream相当于前面的Decorator类。
总结
如果把前面那部分弄明白之后,再来看java io包中间的类的时候就不会觉得很晕了。可以简单的做一个这样的归纳。
Java IO一般分为两类,一类是面向字节的,采用InputStream或者OutputStream。还有一类是面向字符的,采用Reader和Writer。而针对这些所有的类的结构都采用了Decorator的结构。所以,根据命名的约定,我们就可以很容易猜出里面哪些类是干什么的以及在这个类结构中起什么作用。用一句话来概括java io包中的类的话,就是这个包中基本上就是包含了InputStream, OutputStream,Reader和Writer这四大家族和他们的小喽罗们:)