1.装饰者模式能解决啥问题?
在讨论这个问题之前,我们先来复习下装饰者模式的定义:
装饰者模式动态的将责任附加到对象上。 如果要扩展功能,装饰者提供了比继承更有弹性的替代方案。
上面的定义中的关键是: 动态的将责任附加到对象上。 我们该如何理解这句话?
我们可以这样理解:有一个对象A,它有一个方法sayHello,这个方法已经实现用英文打印 hello。
下面需求来了,客户让你在不改变源代码的情况下让这个sayHello方法还要能打印中文 你好。
在我们没有接触装饰者模式情况下,我们可能想到新增一个类继承这个对象A,然后重写sayHello这个方法,让其能满足我们的需求,但是当有新的需求时,我们有不得不再建一个新类继承…,这样无穷无尽。
但是有了装饰者模式,我们可以将变化的部分抽象出来,将其作为原子装饰者,用组合的形式对对象A进行修饰增强sayHello的功能,这样我们就能以有限的设计应对无限的变化!!
装饰者赋予我们动态的给指定对象附加责任的能力。
1.1 复习装饰者模式
带着上面的理解,我们来复习下装饰者模式博客中的实例(看不懂的同学请移步:装饰者模式)
我们首先来分辨上面的图中那些是装饰者,那些是被装饰者:
DarkRoast, HouseBlend, Decaf是被装饰者。 Milk,Soy,Mocha,Whip是装饰者。
首先通过UML我们可以看到,装饰者和被装饰者都会继承同一个接口。 为什么会这样呢? 因为我们保证装饰者在装饰完对象后,有一个引用能接住它,而不是用其他引用来接住(面对抽象的好处),这样客户端就无需做出改变了。
综上,我们可以得出结论,要使用装饰者模式,首先装饰者和被装饰者要是同宗的,其次我们要考虑使用了装饰者模式是否能提高系统的开发效率。切记,不要为了设计而设计。
1.2 真实项目中什么地方会用到装饰者模式呢?
使用装饰者模式,我们应该问自己:是否有动态的为对象增加责任(能力)的场景?如果有,那么我们用装饰者模式来进行设计能为我们节省多少时间。
1.2.1 应用场景
1.2.1.1 JDK IO流功能的设计
其实在JDK中,已经用装饰者模式来对IO流进行设计了,下面我们从装饰者的视角来学习一下JDK的IO流。
首先,我们要知道什么是流?
在java中,输入/输出流 就是具备向文件,网络,内存等目的地读写数据能力的对象。
(1)抽象类InputStream
这个类在装饰者模式中就属于装饰者和被装饰者都需要继承的父类,它里面定义了一个输入流应该有的基本的方法。
下面来了解一下:
//从源中读取数据的下一个字节
public abstract int read() throws IOException;
//从源中读取一些字节,并将其存入字节数组b中
public int read(byte b[]) throws IOException
//从源中读取最多len个字节的数据到字节数组b中,从b的off位置开始存储
public int read(byte b[], int off, int len) throws IOException
//跳过并丢弃来自源的n字节数据
public long skip(long n) throws IOException
//返回从该输入流中可以读取(或跳过)的字节数的估计值,
//而不会被下一次调用此输入流的方法阻塞。
public int available() throws IOException
//关闭此输入流并释放与流相关联的任何系统资源。
public void close() throws IOException {}
//关闭此输入流并释放与流相关联的任何系统资源。
public synchronized void mark(int readlimit) {}
//将此流重新定位到上次在此输入流上调用 mark方法时的位置。
public synchronized void reset() throws IOException
//测试这个输入流是否支持 mark和 reset方法。
public boolean markSupported()
FileInputStream使用学习
public class InputStreamStudy {
public static void main(String[] args) throws IOException {
//BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream("text.txt"));
InputStream fileInputStream = new FileInputStream("FileInputStreamStudy.txt");
//从流中读取一个字节
System.out.println((char) fileInputStream.read());
//从流中读取一组字节
byte[] b = new byte[5];
fileInputStream.read(b);
for(byte a:b){
System.out.print((char)a);
}
System.out.println();
//从流中读取指定数量的字节
byte[] c = new byte[6];
fileInputStream.read(c,2,2);
for(byte a:c){
System.out.print((char)a);
}
System.out.println();
//丢弃一个字节
fileInputStream.skip(1);
System.out.println((char) fileInputStream.read());
fileInputStream.close();
}
}
(2)IO流中使用的装饰者模式
JDK 的IO流中,为了减少对文件操作的次数,提供了BufferdInputStream装饰者对FileInputStream进行装饰,使我们能高效的使用输入流。
UML图如下:
我们就以read方法为例来进行学习,上图中,装饰者BufferedInputStreram可以为被装饰者FileInputStream动态的附加减少对文件操作次数的责任(在第一次读取文件的时候一次性取出大量数据缓存在内存中)。
具体的操作就是,在使用BufferedInputStream装饰FileInputStream后,其read的操作变为如下:
public synchronized int read() throws IOException {
if (pos >= count) {
//将文件中的数据读取出来缓存在buf中
fill();
if (pos >= count)
return -1;
}
//直接返回内存中缓存的字节
return getBufIfOpen()[pos++] & 0xff;
}
操作实例如下:
public class InputStreamStudy {
public static void main(String[] args) throws IOException {
InputStream inputStream = new BufferedInputStream(new FileInputStream("FileInputStreamStudy.txt"));
System.out.println(inputStream.read());
inputStream.close();
}
}