一 什么是装饰模式
装饰器模式
装饰器模式又称为包装(Wrapper)模式。装饰器模式以多客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
装饰器模式的结构
通常给对象添加功能,要么直接修改对象添加相应的功能,要么派生子类来扩展,抑或是使用对象组合的方式。显然,直接修改对应的类的方式并不可取,在面向对象的设计中,我们应该尽量使用组合对象而不是继承对象来扩展和复用功能,装饰器模式就是基于对象组合的方式的。
装饰器模式以对客户端透明的方式动态地给一个对象附加上了更多的责任。换言之,客户端并不会角色对象在装饰前和装饰后有什么不同。装饰器模式可以在不用创建更多子类的情况下,将对象的功能加以扩展。
装饰器模式中的角色有:
- 抽象构件角色 :给出一个抽象接口,以规范准备接受附加责任的对象
- 具体构件角色 :定义一个将要接受附加责任的类
- 装饰角色 :持有一个构建对象的实例,并定义一个与抽象构件接口一致的接口
- 具体装饰角色:负责给构建对象贴上附加的责任
二 例子说明
装饰模式,字面上理解给一个对象类进行修饰,就像我们生活中给新买的房子装潢一样。
2.1 定义一个房子类接口
package com.chp.decoratives;
public interface Buildings {
//定义一个装潢的方法
public void decoration();
}
2.2 定义该接口的具体实现类
package com.chp.decoratives;
public class House implements Buildings{
@Override
public void decoration() {
System.out.println("对普通房子进行装饰");
}
}
2.3 定义装饰器的抽象类
package com.chp.decoratives;
/**
* 定义一个装饰器抽象类
* @author Administrator
*
*/
public abstract class FilterBuildings implements Buildings{
protected Buildings buildings;
}
2.4 定义装饰器抽象类的具体类
package com.chp.decoratives;
public class Greening extends FilterBuildings{
public Greening(Buildings buildings){
this.buildings=buildings;
};
@Override
public void decoration() {
buildings.decoration();
System.out.println("对房子周围进行绿化装饰");
}
}
package com.chp.decoratives;
public class Flooring extends FilterBuildings {
public Flooring(Buildings buildings) {
this.buildings = buildings;
}
@Override
public void decoration() {
buildings.decoration();
System.out.println("在房子内部铺地板装饰");
}
}
2.5 测试
package com.chp.decoratives;
public class Test {
public static void main(String[] args) {
Buildings buildings=new Greening(new House());
Buildings buildings2=new Flooring(new House());
buildings.decoration();
System.out.println("------------");
buildings2.decoration();
}
}
在这里我们通过组合来更好的对代码进行复用,如果我们只通过继承,那么每次定义一个类都需要继承原始类并且进行对应的装饰,比如我如果再定义一个别墅类villa,我只需要定义一个装饰类,如下:
package com.chp.decoratives;
public class Villa implements Buildings{
@Override
public void decoration() {
System.out.println("对别墅进行装饰");
}
}
测试:
Buildings villa=new Greening(new Villa());
Buildings villa2=new Flooring(new Villa());
villa.decoration();
System.out.println("------------");
villa2.decoration();
三 JDK中的应用
装饰器模式在Java体系中的经典应用是Java I/O,下面先讲解字节输入流InputStream,再讲解字符输入流Reader,希望可以通过这两种输入流的讲解,加深对于装饰器模式的理解。
首先看一下字节输入流InputStream的类结构体系:
InputStream是一个顶层的接口,文章开头就说,装饰器模式是继承关系的一种替代方案,看一下为什么:
- InputStream假设这里写了两个实现类,FileInputStream,ObjectInputStream分别表示文件字节输入流,对象字节输入流
- 现在我要给这两个输入流加入一点缓冲功能以提高输入流效率,使用继承的方式,那么就写一个BufferedInputStream,继承FileInputStream,ObjectInputStream,给它们加功能
- 现在我有另外一个需求,需要给这两个输入流加入一点网络功能,那么就写一个SocketInputStream,继承继承FileInputStream,ObjectInputStream,给它们加功能
这样就导致两个问题:
- 因为我要给哪个类加功能就必须继承它,比如我要给FileInputStream,ObjectInputStream加上缓冲功能、网络功能就得扩展出2*2=4个类,更多的以此类推,这样势必导致类数量不断膨胀
- 代码无法复用,给FileInputStream,ObjectInputStream加入缓冲功能,本身代码应该是一样的,现在却必须继承完毕后把一样的代码重写一遍,多此一举,代码修改的时候必须修改多个地方,可维护性很差
所以,这个的时候我们就想到了一种解决方案:
- 在要扩展的类比如BufferedInputStream中持有一个InputStream的引用,在BufferedInputStream调用InputStream中的方法,这样扩展的代码就可以复用起来
- 将BufferedInputStream作为InputStream的子类,这样客户端只知道我用的是InputStream而不需要关心具体实现,可以在客户端不知情的情况下,扩展InputStream的功能,加上缓冲功能
这就是装饰器模式简单的由来,一切都是为了解决实际问题而诞生。下一步,根据UML图,我们来划分一下装饰器模式的角色。
1、InputStream是一个抽象构件角色:
public abstract class InputStream implements Closeable {
// SKIP_BUFFER_SIZE is used to determine the size of skipBuffer
private static final int SKIP_BUFFER_SIZE = 2048;
// skipBuffer is initialized in skip(long), if needed.
private static byte[] skipBuffer;
...
}
2、ByteArrayInputStream、FileInputStream、ObjectInputStream、PipedInputStream都是具体构建角色,比如FileInputStream,它的声明是:
public
class FileInputStream extends InputStream
{
/* File Descriptor - handle to the open file */
private FileDescriptor fd;
private FileChannel channel = null;
...
}
3、FilterInputStream无疑就是一个装饰角色,因为FilterInputStream实现了InputStream内的所有抽象方法并且持有一个InputStream的引用:
public
class FilterInputStream extends InputStream {
/**
* The input stream to be filtered.
*/
protected volatile InputStream in;
...
}
4、具体装饰角色就是InflaterInputStream、BufferedInputStream、DataInputStream,比如BufferedInputStream的声明就是:
public
class BufferedInputStream extends FilterInputStream {
private static int defaultBufferSize = 8192;
/**
* The internal buffer array where the data is stored. When necessary,
* it may be replaced by another array of
* a different size.
*/
protected volatile byte buf[];
...
}
搞清楚具体角色之后,我们就可以这么写了:
public static void main(String[] args) throws Exception
{
File file = new File("D:/aaa.txt");
InputStream in0 = new FileInputStream(file);
InputStream in1 = new BufferedInputStream(new FileInputStream(file));
InputStream in2 = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
}
我们这里实例化出了三个InputStream的实现类:
- in0这个引用指向的是new出来的FileInputStream,这里简单构造出了一个文件字节输入流
- in1这个引用指向的是new出来的BufferedInputStream,它给FileInputStream增加了缓冲功能,使得FileInputStream读取文件的内容保存在内存中,以提高读取的功能
- in2这个引用指向的是new出来的DataInputStream,它也给FileInputStream增加了功能,因为它有DataInputStream和BufferedInputStream两个附加的功能
同理,我要给ByteArrayInputStream、ObjectInputStream增加功能,也可以使用类似的方法,整个过程中,最重要的是要理解几个问题:
- 哪些是具体构建角色、哪些是具体装饰角色,尤其是后者,区分的关键就是,角色中是否持有顶层接口的引用
- 每个具体装饰角色有什么作用,因为只有知道每个具体装饰角色有什么作用后,才可以知道要装饰某个功能需要用哪个具体装饰角色
- 使用构造方法的方式将类进行组合,给具体构建角色加入新的功能
装饰器模式与Java字符输入流Reader
看完了上面的解读,相信大家对于装饰器模式应当有了一定的理解,那么再来看一下Java字符输入流Reader,来加深对于装饰器模式的印象。
简单看一下Reader的类体系结构:
根据UML,分析一下每个角色:
1、抽象构建角色
毫无疑问,由Reader来扮演,它是一个抽象类,没有具体功能
2、具体构建角色
由InputStreamReader、CharArrayReader、PipedReader、StringReader来扮演
3、装饰角色
由FilterReader来扮演,但是这里要提一下这个BufferedReader,它本身也可以作为装饰角色出现,看一下BufferedReader的继承关系:
public class BufferedReader extends Reader {
private Reader in;
private char cb[];
private int nChars, nextChar;
private static final int INVALIDATED = -2;
private static final int UNMARKED = -1;
private int markedChar = UNMARKED;
...
}
看到BufferedReader是Reader的子类,且持有Reader的引用,因此这里的BufferedReader是可以被认为是一个装饰角色的。
4、具体装饰角色
BufferedReader上面提到了扮演了装饰角色,但是也可以被认为是一个具体装饰角色。除了BufferedReader,具体装饰角色还有PushbackReader。FileReader尽管也在第三行,但是FileReader构不成一个具体装饰角色,因为它不是BufferedReader的子类也不是FilterReader的子类,不持有Reader的引用。
四 总结
装饰器模式的优缺点
优点
1、装饰器模式与继承关系的目的都是要扩展对象的功能,但是装饰器模式可以提供比继承更多的灵活性。装饰器模式允许系统动态决定贴上一个需要的装饰,或者除掉一个不需要的装饰。继承关系是不同,继承关系是静态的,它在系统运行前就决定了
2、通过使用不同的具体装饰器以及这些装饰类的排列组合,设计师可以创造出很多不同的行为组合
缺点
由于使用装饰器模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是另一方面,由于使用装饰器模式会产生比使用继承关系更多的对象,更多的对象会使得查错变得困难,特别是这些对象看上去都很像。
装饰器模式和适配器模式的区别
其实也是一种包装(Wrapper)模式,它们看似都是起到包装一个类或对象的作用,但是它们使用的目的非常不一样:
1、适配器模式的意义是要将一个接口转变成另外一个接口,它的目的是通过改变接口来达到重复使用的目的
2、装饰器模式不要改变被装饰对象的接口,而是恰恰要保持原有的借口哦,但是增强原有接口的功能,或者改变元有对象的处理方法而提升性能
所以这两种设计模式的目的是不同的。