装饰者模式
一、装饰者模式的相关概念和案例
1、装饰者模式概念
装饰者模式: 在已经有的功能上
,将新的功能附加到已有的功能上
,在对象功能拓展方面
,比继承更有弹性
,该模式体现了开闭原则(OCP)
。
提到装饰者模式,最经典的就是Java IO
包中的使用,也是我们学习Java语言中最先遇到的应用该模式的模块。
2、装饰者模式的案例
例如:为了快速的从OutputStream流写数据
,可以使用BufferedOutputStream
装饰该流,被它装饰过的流增加缓冲数据的功能
。
从上面的栗子来看,装饰者模式让我们可以在运行时动态增强该对象的功能
,不影响原来代码使用
。同时有拓展了新的功能。
所以将装饰者模式,我们就通过Java IO中的源码进行分析,充分了解装饰者模式
。
一言不合上代码:
package design.model.decorator;
import java.io.*;
public class FileCopyTest {
public static void main(String[] args) {
File file = new File("D:/text.txt");
File copyFile = new File("D:/test_copy.txt");
try (FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(copyFile);
BufferedInputStream bis = new BufferedInputStream(fis,1024);
BufferedOutputStream bos = new BufferedOutputStream(fos,1024)) {
byte[] bytes = new byte[1024];
int hasRead = 0;
while ((hasRead =bis.read(bytes))>0){
bos.write(bytes, 0,hasRead);
}
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
}
}
二、从BufferedOutputStream源码了解装饰者模式的使用
1、查看BufferedOutputStream继承体系
2、查看BufferedOutputStream的父类FilterOutputStream
从上面的途中我们知道BufferedOutputStream
继承自FilterOutputStream,现在进入FilterInputStream
中查看源码:
(这里删除英文注释)
package java.io;
public
class FilterOutputStream extends OutputStream {
/**
* The underlying output stream to be filtered.
*/
protected OutputStream out;
public FilterOutputStream(OutputStream out) {
this.out = out;
}
public void write(int b) throws IOException {
out.write(b);
}
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
public void write(byte b[], int off, int len) throws IOException {
if ((off | len | (b.length - (len + off)) | (off + len)) < 0)
throw new IndexOutOfBoundsException();
for (int i = 0 ; i < len ; i++) {
write(b[off + i]);
}
}
public void flush() throws IOException {
out.flush();
}
@SuppressWarnings("try")
public void close() throws IOException {
try (OutputStream ostream = out) {
flush();
}
}
}
代码思路解析:
从代码中我们发现FilterOutputStream
继承自OutputStream
,内部自己又封装了一个 OutputStream
类,重写的write(int b)
方法,使用成员变量out
的write(int b)
方法来读取数据的。
FilterOutputStream
类使用成员变量OutputStream
只是进行了装饰,没有拓展新的功能,实际使用了还是OutputStream
的write()
方法。
我们知道BufferedOutputStream
继承自FilterOutputStream
,说明该类对象内部也继承该成员变量,BufferedOutputStream
内部存有
OutputStream
对象的子类实例,在自身拓展了新的功能,增加了缓存的功能,但是最终还是通过OutputStream
的write(int b)
方法进行数据的写出。
3、进入BufferedOutputStream类,查看缓存功能的实现
现在我们进入BufferedOutputStream
类,查看BufferedOutputStream
在调用write(int b)
方法前都进行了哪些封装。
package java.io;
public
class BufferedOutputStream extends FilterOutputStream {
/**
* The internal buffer where data is stored.
* 内部封装一个一个byte数组
*/
protected byte buf[];
/**
*这个计数器用于记录当前 数组buf存放到了哪个位置
*/
protected int count;
/**
* 在声明的时候,可以指定缓存数据的大小,如果声明,就使用默认的
*/
public BufferedOutputStream(OutputStream out) {
this(out, 8192);
}
/**
*/
public BufferedOutputStream(OutputStream out, int size) {
super(out);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
/** Flush the internal buffer
* 将byte数组buf中的数据全部写出,然后重置计数器count = 0
*/
private void flushBuffer() throws IOException {
if (count > 0) {
out.write(buf, 0, count);
count = 0;
}
}
/**
* Writes the specified byte to this buffered output stream.
* 这里主要拓展了数据写出的功能,就是一开始先存放到成员变量byte数据buf中,
* 一旦count >= buf.length,说明数组的已经存满了,调用flushBuffer()方法,写到到目标文件中
*/
public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer();
}
buf[count++] = (byte)b;
}
/**
* @param b the data.
* @param off the start offset in the data.
* @param len the number of bytes to write.
* @exception IOException if an I/O error occurs.
*/
public synchronized void write(byte b[], int off, int len) throws IOException {
if (len >= buf.length) {
/* If the request length exceeds the size of the output buffer,
flush the output buffer and then write the data directly.
In this way buffered streams will cascade harmlessly. */
flushBuffer();
out.write(b, off, len);
return;
}
if (len > buf.length - count) {
flushBuffer();
}
System.arraycopy(b, off, buf, count, len);
count += len;
}
public synchronized void flush() throws IOException {
flushBuffer();
out.flush();
}
}
代码分析:
从上面的代码,我们可以看到,BufferedOutputStream
使用的装饰者的设计模式,它做了一下两个步骤:
- 通过继承
FilterOutputStream
,继承了FilterOutputStream
的变量out
。 - 在自身上
封装了一个byte数组
的成员变量buf
,在写数据的时候,先是将数据保存到buf,等到buf数组满了,再统一将数据从buf中写出到文件中
。
BufferedOutputStream
就是拓展了OutputStream
的功能,再写出数据前添加了buf作为缓存,增加了缓存功能
,动态的将新功能附加到OutputStream的对象上,符合OCP原则
。
从上面的代码:我们已经知道了装饰者模式应用,但是为什么要使用装饰者模式呢,下面我们就进行探讨。
三、使用装饰者模式的原因
1、前提条件和功能拓展需求。
条件:基于现在我们已经OutputStream的功能用于写数据的功能了
现在我们有一个新的需求:需要具备缓存的写数据的功能。
为了完成该功能,我们可能会有几个想法:
- 直接修改OutputStream类的代码
这种方式对现在类进行了修改,但是对之前使用了OutputStream的代码,就会可能导致这些代码会问题,为了保证以前使用OutputeStream的代码能够正常执行,必须重写做单元测试;
还有更麻烦的事,因为OutputStream的写方法非常基础,为了保证不影响其他的功能,还需要为所有受影响的其他类和方法以及他们的单元测试进行修改,这个工作量工作量那是相当的大。
对于第一个想法,我们觉得不可行,就想能不能不修改这些原来的代码,有拓展OutputStream的功能呢?
2、OCP原则(OpenClosePrinciple,开放-封闭原则)
在此之前我们需要看一看一条设计原则——OCP原则(OpenClosePrinciple,开放-封闭原则),就是软件开发中,应当对拓展开放,但是对修改关闭。
该原则要求使用OOP进行开发,我们只能修改已有类的错误(Bugs),但是要拓展他们的功能,我们不能修改他们,而是要通过添加新类来解决。
3、在遵守OCP原则下,新的实现想法
所以为了不破坏OCP原则,现在我们又有了两种想法
想法一:为了避免修改已有OutputStream功能,创建一个新的BufferedOutputStream来实现该功能。
问题:如果创建BufferedOutputStream的实现具备缓存的写功能,会有大量重复的代码。
虽然这样没有违反OCP原则,但是却违反了DRY原则。
想法二:继承OutputStream类,对OutputStream在的写功能功能前进行封装。
这个想法是就很好的契合装饰者模式的思想,于是我们会有会写出和BufferedOutputStream相似的类,不过现在不用我们写了,JDK已经帮我们提供了一个这样的类,但是它却可以很好的帮我们了解装饰者的思想、实现和使用。