【情景模式】
说实话,在第一次遇到Java IO
的时候,我是拒绝的,为了读写一个文件,需要一大堆代码,还有很多很多类,在不了解这些类的功能的情况下,很容器犯迷糊。之后在系统学习了IO
之后明白了其实这就是装饰者模式
那么,之前一直抵制这么多类的IO
,如果不用装饰者模式,应该怎么实现呢?
【不用装饰者模式分析】
我们的最终目的是一个功能只需要一个类来实现,比如读取文件就只需要new BufferedFileReader()
即可,不用再传FileReader()
对象参数。
在Java IO
中,根据应用场景不同需要:
– 根据字节流和字符流分为两个基本类
– 根据输入输出源不同分为CharArray
,File
,String
等等
– 根据功能不同需要有能缓存数据,能一次读取一行等等
因此,我们可以先实现两个基本类,分别是字符和字节,然后再根据源不同实现他们的功能类,比如CharArray
,File
等,最后再通过继承实现他们的辅助功能类, 比如BufferedFileOutputStream
,DataOutputStream
,LineNumberOutputStream
等等,为每一个类都包装上功能。这样看来,
但是,这是只需要一个辅助功能的情况,按照Java IO
的设计,有时候辅助功能是能叠加使用的,比如对于压缩文件来说,首先需要一个BufferedFileOutputStream
作为缓存增加性能,然后需要一个ZipoutputStream
来对最终的文件压缩输出。这样的组合每个可能都有,比如DataOutputStream
和BufferedOutputStream
等。。。
算一算排列组合,对于4个辅助功能类来说,理想组合情况大约都有十几种,这无疑来说是更大的灾难。
【代码分析】
因此,从这个角度来说,装饰者模式还算一个不错的选择,装饰者模式在这种有多种可组合选择的情况下,可以发挥出比继承更加灵活的功能。
以下代码为伪代码,和JDK
代码稍微有些不同
//核心类接口
public abstract class InputStream{
public abstract int read() throws IOException;
}
//底层功能类实现之一:输入源为byte array
public class ByteArrayInputStream extends InputStream {
public synchronized int read() {
return (pos < count) ? (buf[pos++] & 0xff) : -1;
}
}
//底层功能类实现之一:输入源为File
public class FileInputStream extends InputStream{
public int read() throws IOException {
return read0();
}
private native int read0() throws IOException;
}
//装饰类接口
public class FilterInputStream extends InputStream {
protected FilterInputStream(InputStream in) {
this.in = in;
}
public int read() throws IOException {
return in.read();
}
}
//实现具有装饰功能的类之一:具有缓存功能
public class BufferedInputStream extends FilterInputStream {
public BufferedInputStream(InputStream in, int size) {
super(in);
buf = new byte[size];
}
public synchronized int read() throws IOException {
if (pos >= count) {
fill();
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
}
}
//实现具有装饰功能的类之一:具有压缩功能
public class GZIPInputStream extends FilterInputStream{
public GZIPInputStream(InputStream in) throws IOException {
this(in, 512);
}
public int read(byte[] buf, int off, int len) throws IOException {
ensureOpen();
if (eos) {
return -1;
}
int n = super.read(buf, off, len);
if (n == -1) {
if (readTrailer())
eos = true;
else
return this.read(buf, off, len);
} else {
crc.update(buf, off, n);
}
return n;
}
}
可以看到,上面的类中,我们实现了两个基本的类和两个装饰的类,我们可以根据实际情况来选择所需要的类,比如对于需要读取大文件来说,就需要使用FileInputstream
以及BufferedInputsteam
来节约性能。
同样如果我们需要读取一个压缩文件中的文件,可以同时搭配FileInputstream
和BufferedInputsteam
以及GZIPInputStream
实现既有效率又能读取压缩文件的类。
通过以上代码就能发现,装饰模式主要是应用在具有多个功能相同的子类, 并且使用的时候可能会存在选择的情况。
装饰者模式
定义:动态的给一个对象增加一些额外的职责,对需要增加对象的功能来说,装饰者模式比生成子类更加灵活。
UML:
- Component:抽象组件,最核心的功能接口
- ConcreteComponent:具体功能的底层实现
- Decorator:装饰者接口,通常为一个抽象类,类中一般包含一个
Component
元素 - ConcreteDecoratorA:装饰者的实现类,用于实现具体的装饰者
应用场景:装饰模式一般应用在需要为现有的子类扩展功能的时候,装饰者模式是对组合优于继承的最好的诠释,我们可以通过装饰者模式,动态的扩展一个类的功能,并且能够将功能的扩展交给用户自己选择。
不过装饰者模式的确定也比较明显,那就是使用这个类的用户需要明白很多的类的作用,具有一定的学习成本,而且一般来说,最常使用的就几种组合,每次使用都会new
更多的对象。
优点:
- 装饰类和被装饰类可以独立扩展,不会相互耦合,修改被装饰的底层类,一般不会影响到装饰类,结合了继承的优点:功能复用,并且屏蔽了继承的缺点
- 装饰类可以实现动态的扩展底层类的实现,可以通过组合实现多个功能的组合类,而继承只要编译完成,类的功能就已经确定
- 装饰类的动态扩展的特点,可以很好的解决多个类需要配合使用的情况。
缺点
- 多层的装饰是比较复杂的
- 尽量减少装饰类的数量,以便降低系统的复杂度