1 概述
装饰设计模式属于23种设计模式之一,英文叫Decorator Pattern,又叫装饰者模式。装饰设计模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
装饰设计模式在Java中最经典的实践莫过于对Java IO库的设计。通过装饰设计模式在不使用继承关系的前提下,动态的给IO流对象提供加强的功能,其中常见的加强功能指的是缓冲技术,通过缓冲区提高流的读取和写入的效率。
2 装饰设计模式简介
2.1 应用场景
当想要对已有的对象进行功能增强时,可以定义一个新的类,将已有对象传入,并提供基于已有功能的加强功能,自定义的类称为装饰类。
装饰类通常会通过构造方法接收被装饰的对象。
一个简单的应用参考如下代码,其中被装饰类Phone提供了传统的语音打电话的功能,而装饰类MobilePhone通过构造函数传入一个Phone对象,提供一个加强的打电话功能,在原有的语音打电话的基础上提供增强的视频电话的功能。
/**
* Created by fubinhe on 16/10/22.
*/
public class DecoratorPattern {
public static void main(String[] args) {
MobilePhone mobilePhone = new MobilePhone(new Phone());
mobilePhone.newCall();
}
}
class Phone {
public void call() {
System.out.println("语音......");
}
}
class MobilePhone {
private Phone phone;
public MobilePhone(Phone phone) {
this.phone = phone;
}
public void newCall() {
phone.call();
System.out.println("视频......");
}
}
在实际应用中,被装饰类和装饰类继承同一个父类或者实现同一个接口,表示两个类属于同一个类体系中。
2.2 和继承的比较
上面的功能也可以通过继承来实现,装饰类可以继承被装饰类并复写其call函数。然而,继承是一种强依赖关系,整个体系会显得非常的臃肿。
而装饰设计模式通过将原有的对象传入并调用其方法,如果这个方法有问题可以将其删除而不影响其它的功能。因此,装饰设计模式通过将两个类进行解耦,使得整个体系变得灵活。
3 装饰设计模式在Java IO中的应用
Java IO的十分精巧,其中重要的一点是利用装饰设计模式提供缓冲技术,提高流的读写效率。而Java IO中用到的装饰设计模式也是该模式在Java中最有名的实践。
3.1 BufferedReader的源码解析
3.1.1 典型应用
FileReader提供读取字符文件的read方法,可以一次读取一个字符或一个字符数组。
而应用装饰设计模式,BufferedReader提供增强功能的readLine方法,一次读取文本中的一行字符。该方法和FileReader的读取一个字符数组的read方法的区别在于不用自己显式的定义字符数组,而且不用处理换行符的问题。
典型的应用该类读取字符文件中的每行数据的代码如下所示:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
/**
* Created by fubinhe on 16/10/22.
*/
public class BufferedReaderDemo {
public static void main(String[] args) {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("1.txt"));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3.1.2 构造函数
BufferedReader的类签名为:public class BufferedReader extends Reader
该类维护了一个Reader对象:private Reader in,该对象通过构造函数传入,上面的代码就是通过构造函数传入了一个FileReader对象,该对象是Reader的子类(准确的说是子类的子类)。而BufferedReader类的构造函数有两个实现,分别如下所示:
/**
* Creates a buffering character-input stream that uses a default-sized
* input buffer.
*
* @param in A Reader
*/
public BufferedReader(Reader in) {
this(in, defaultCharBufferSize);
}
/**
* Creates a buffering character-input stream that uses an input buffer of
* the specified size.
*
* @param in A Reader
* @param sz Input-buffer size
*
* @exception IllegalArgumentException If sz is <= 0
*/
public BufferedReader(Reader in, int sz) {
super(in);
if (sz <= 0)
throw new IllegalArgumentException("Buffer size <= 0");
this.in = in;
cb = new char[sz];
nextChar = nChars = 0;
}
其中,前者通过传入一个默认缓冲区大小参数给后者实现的,而在后者中,主要是通过传入的sz参数初始化缓冲区数组cb,并且初始化nextChar和nChars两个参数。
3.1.3 readLine方法
BufferedReader类的核心方法是一次读取一行字符的readLine方法,该方法的实现如下:
/**
* Reads a line of text. A line is considered to be terminated by any one
* of a line feed ('\n'), a carriage return ('\r'), or a carriage return
* followed immediately by a linefeed.
*
* @return A String containing the contents of the line, not including
* any line-termination characters, or null if the end of the
* stream has been reached
*
* @exception IOException If an I/O error occurs
*
* @see java.nio.file.Files#readAllLines
*/
public String readLine() throws IOException {
return readLine(false);
}
该方法其实是调用其重载函数实现的,该重载函数的源代码如下:
/**
* Reads a line of text. A line is considered to be terminated by any one
* of a line feed ('\n'), a carriage return ('\r'), or a carriage return
* followed immediately by a linefeed.
*
* @param ignoreLF If true, the next '\n' will be skipped
*
* @return A String containing the contents of the line, not including
* any line-termination characters, or null if the end of the
* stream has been reached
*
* @see java.io.LineNumberReader#readLine()
*
* @exception IOException If an I/O error occurs
*/
String readLine(boolean ignoreLF) throws IOException {
StringBuffer s = null;
int startChar;
synchronized (lock) {
ensureOpen();
boolean omitLF = ignoreLF || skipLF;
bufferLoop:
for (;;) {
if (nextChar >= nChars)
fill();
if (nextChar >= nChars) { /* EOF */
if (s != null && s.length() > 0)
return s.toString();
else
return null;
}
boolean eol = false;
char c = 0;
int i;
/* Skip a leftover '\n', if necessary */
if (omitLF && (cb[nextChar] == '\n'))
nextChar++;
skipLF = false;
omitLF = false;
charLoop:
for (i = nextChar; i < nChars; i++) {
c = cb[i];
if ((c == '\n') || (c == '\r')) {
eol = true;
break charLoop;
}
}
startChar = nextChar;
nextChar = i;
if (eol) {
String str;
if (s == null) {
str = new String(cb, startChar, i - startChar);
} else {
s.append(cb, startChar, i - startChar);
str = s.toString();
}
nextChar++;
if (c == '\r') {
skipLF = true;
}
return str;
}
if (s == null)
s = new StringBuffer(defaultExpectedLineLength);
s.append(cb, startChar, i - startChar);
}
}
}
注意该函数的签名签名没有任何权限修饰符,这就是表示我们只能显式调用前面的readLine方法,将参数ignoreLF传入为false。该参数的含义是是否跳过一行后面的换行符。false表示不跳过,那么如果一行只有换行符也会被读出来。
在上面的源代码中主要执行两层循环,其中外层循环bufferLoop主要是通过fill方法将字符文件中的字符读取到缓冲区中,而内层循环charLoop主要功能是将数据写到一个StringBuffer中。需要注意的是,每次调用readLine方法时,缓冲区是从上一次调用后的位置开始的,而并不是从数组的0角标开始,这是因为数组的大小是固定的,而每行数据的大小不是固定的。而标记读取的位置是通过nextChar和nChars两个参数实现的。
3.2 BufferedWriter应用
使用装饰设计模式的写操作的类BufferedWriter相较于BufferedReader比较简单,它和普通的FileWriter类的不同在于可以调用newLine方法实现换行,而不用显式的添加换行符来实现,这样可以实现跨平台性。
下面仅给出该类的典型应用,源代码可以自行查看JDK。
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
/**
* Created by fubinhe on 16/10/22.
*/
public class BufferedWriterDemo {
public static void main(String[] args) {
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter("2.txt"));
bw.write("11111");
bw.newLine();
bw.write("22222");
bw.newLine();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}