装饰器模式

如果大家觉得文章错误内容,欢迎留言或者私信讨论~

  Java IO 类库非常庞大和复杂,有几十个类,负责 IO 数据的读取和写入。如果对 Java IO类做一下分类,我们可以从下面两个维度将它划分为四类。具体如下所示:

在这里插入图片描述


  针对不同的读写和写入常见, Java IO 又在四个父类基础只是,扩展了很多子类,具体如下所示:

在这里插入图片描述


  在我最初接触 java IO 的时候,就对 Java IO 的一些用法产生了很大的疑惑,比如要实现这样一个功能:打开文件 test.txt,从中读取数据。其中InputStream一个抽象类,FileInputStream是专门用来读取文件流的子类。BufferedInputStream一个支持带缓存功能的数据读取类,可以提高数据读取的效率,代码看起来是这样:

InputStream in = new FileInputStream("/user/wangzheng/test.txt");
InputStream bin = new BufferedInputStream(in);
byte[] data = new byte[128];初看上面的代码,我们会觉得 Java IO 的用法比较麻烦,需要先创建一个 FileInputStream
while (bin.read(data) != -1) {
	//...
}

  这最开始让我非常疑惑,为什么 Java IO 要做这么麻烦的操作,难道不能设计一个继承 FileInputStream 并且支持缓存的 BufferedFileInputStream 的类呢?这样我们就可以像下面的代码一样,直接创建一个 BufferedFileInputStream 类对象,使用起来不是更加简单吗?

InputStream bin = new BufferedFileInputStream("/user/wangzheng/test.txt");
byte[] data = new byte[128];
while (bin.read(data) != -1) {
	//...
}

  这样基于继承的解决方案, 如果 InputStream 只有一个子类FileInputStream的话,那么我们在它的继承上再设计一个子孙类,那么也还是可以接受的。但实际上,继承InputStream 的子类有很多,那么我们需要给这些子类都派生支持缓存读取的类。
  除了支持缓存读取之外,如果我们还需要对功能进行其他方面的增强,比如下面的DataInputStream类,支持按照基本数据类型(int、boolean、long 等)来读取数据:

FileInputStream in = new FileInputStream("/user/wangzheng/test.txt");
DataInputStream din = new DataInputStream(in);
int data = din.readInt();

  在这种情况下,如果我们继续按照继承的方式来实现的话,就需要再继续派生出DataFileInputStreamDataPipedInputStream 等类,这样慢慢的开发下去,一旦出现“破窗效应”,那就会组合爆炸,类继承结构变得无比复杂,代码既不好扩展,也不好维护。

基于装饰器模式的解决方

  装饰器模式的思想是组合大于继承, 使用组合来替代继承。针对刚刚的继承结构过于复杂的问题,我们可以通过将继承关系改为组合关系来解决。下面的代码展示了 Java IO 的这种设计思路。不过,我对代码做了简化,只抽象出了必要的代码结构,如果你感兴趣的话,可以直接去查看 JDK 源码:

public abstract class InputStream {
	//...
	public int read(byte b[]) throws IOException {
		return read(b, 0, b.length);
	} 
	public int read(byte b[], int off, int len) throws IOException {
		//...
	} 
	public long skip(long n) throws IOException {
		//...
	} 
	public int available() throws IOException {
		return 0;
	} 
	public void close() throws IOException {}
	public synchronized void mark(int readlimit) {}
	public synchronized void reset() throws IOException {
		throw new IOException("mark/reset not supported");
	} 
	public boolean markSupported() {
		return false;
	}
}

public class BufferedInputStream extends InputStream {
	protected volatile InputStream in;
	protected BufferedInputStream(InputStream in) {
	this.in = in;
	} 
	//...实现基于缓存的读数据接口...
} 

public class DataInputStream extends InputStream {
	protected volatile InputStream in;
	protected DataInputStream(InputStream in) {
	this.in = in;
	}
	 //...实现读取基本类型数据的接口
}

  从上面的 Java io 设计来看,装饰器模式对于简单的组合关系,还有两个比较特殊的地方。一个比较特殊的地方是:装饰器类和原始类继承同样的父类,这样我们可以对原始类“嵌套”多个装饰器类。 比如,下面这样一段代码,我们对 FileInputStream 嵌套了两个装饰器类:BufferedInputStream 和 DataInputStream,让它既支持缓存读取,又支持按照基本数据类型来读取数据:

InputStream in = new FileInputStream("/user/wangzheng/test.txt");
InputStream bin = new BufferedInputStream(in);
DataInputStream din = new DataInputStream(bin);
int data = din.readInt();

  第二个比较特殊的地方是:装饰器类是对功能的增强,这也是装饰器模式应用场景的一个重要特点。 相较于代理模式,装饰器模式的用途在于附加跟原始类相关的增强功能;而代理类模式则是附加跟原始类无关的功能。清晰两者的区分就能把握住如何去运用两种设计模式。

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...