装饰模式指的是在不必改变原类文件和继承关系的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰者来包裹真实的对象,以达到装饰的目的。
使用场景
Java I/O 系统的设计是典型的装饰者模式
装饰者模式的初衷是让各个装饰类有一个共同的父类,并构成一个装饰链,达到互相装饰的功能。例如:
- BufferedReader装饰了FileReader的功能,在其内部维护一个8Kb的字节数组,以提高字符流读取速率
装饰者模式实现方式
现要设计一个自定义的字符流,需要每次输出文件的一行数据并带有行号。可以考虑对BufferedReader中readLine()方法进行装饰。
文件样例:1
2
3
4
5
6
7
8
9
10
11
12
13/**
* Creates a new <tt>FileReader</tt>, given the name of the
* file to read from.
*
* @param fileName the name of the file to read from
* @exception FileNotFoundException if the named file does not exist,
* is a directory rather than a regular file,
* or for some other reason cannot be opened for
* reading.
*/
public FileReader(String fileName) throws FileNotFoundException {
super(new FileInputStream(fileName));
}
通过继承BufferedReader,并重写readLine方法来实现
1 | package com.xiaopeng.decorator; |
输出:1
2
3
4
5
6
7
8
9
10
11
12
131 /**
2 * Creates a new <tt>FileReader</tt>, given the name of the
3 * file to read from.
4 *
5 * @param fileName the name of the file to read from
6 * @exception FileNotFoundException if the named file does not exist,
7 * is a directory rather than a regular file,
8 * or for some other reason cannot be opened for
9 * reading.
10 */
11 public FileReader(String fileName) throws FileNotFoundException {
12 super(new FileInputStream(fileName));
13 }
假设现在需要在每一行输出后面追加 “$” 符号,便需要再编写一个BufferedReader的子类,完成该装饰效果。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class BufferedReaderWith$ extends BufferedReader {
public BufferedReaderWith$(Reader in) {
super(in);
}
public String readLine() throws IOException {
String line = super.readLine();
if(line == null)
{
return null; //读至文件末尾并返回
}
line = line + " $"; //在每一行输出的末尾追加 $
return line;
}
}
可以发现,使用继承的方式实现装饰效果,代码简单、结构清晰,但是一旦要装饰的“效果”很多,就需要编写多个子类,且逻辑冗余!
通过内部维护一个被装饰者(BufferedReader)的引用
通过内部维护一个被装饰者(BufferedReader)的引用,并由构造函数传入引用,可以避免上述编写过多子类的缺点,达到互相装饰的效果。
SuperBufferedReaderWithLineNum 同时输出行号:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29package com.xiaopeng.decorator;
import java.io.BufferedReader;
import java.io.IOException;
public class SuperBufferedReaderWithLineNum extends BufferedReader {
//内部维护一个被装饰者的引用
private BufferedReader bufferedReader;
private int count = 1;
public SuperBufferedReaderWithLineNum(BufferedReader bufferedReader) {
super(bufferedReader);
this.bufferedReader = bufferedReader;
}
public String readLine() throws IOException {
String line = null;
line = bufferedReader.readLine();
while(line == null) // 文件末尾判断
{
return null;
}
line = count + " " + line; //输出行号
count ++;
return line;
}
}
SuperBufferedReaderWith$ 同时在文件末尾输出$:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26package com.xiaopeng.decorator;
import java.io.BufferedReader;
import java.io.IOException;
public class SuperBufferedReaderWith$ extends BufferedReader{
//内部维护一个被装饰者的引用
private BufferedReader bufferedReader;
public SuperBufferedReaderWith$(BufferedReader bufferedReader) {
super(bufferedReader); //BufferedReader没有无参构造方法,需要指定调用父类的构造方法
this.bufferedReader = bufferedReader;
}
public String readLine() throws IOException {
String line = bufferedReader.readLine();
if (line == null) {
return null; //读至文件末尾并返回
}
line = line + " $"; //添加$
return line;
}
}
客户端调用:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package com.xiaopeng.decorator;
import java.io.BufferedReader;
import java.io.FileReader;
public class Client {
public static void main(String[] args) throws Exception {
String line = "";
//sr$可以实现在每一行末尾同时输出 $
SuperBufferedReaderWith$ sr$ = new SuperBufferedReaderWith$(new BufferedReader(new FileReader("./src/data.txt")));
//使用 SuperBufferedReaderWithLineNum 装饰sr$ ,可以同时输出行号和 $
SuperBufferedReaderWithLineNum srLineNum = new SuperBufferedReaderWithLineNum(sr$);
while ((line = srLineNum.readLine()) != null) {
System.out.println(line);
}
}
}
输出:1
2
3
4
5
6
7
8
9
10
11
12
131 /** $
2 * Creates a new <tt>FileReader</tt>, given the name of the $
3 * file to read from. $
4 * $
5 * @param fileName the name of the file to read from $
6 * @exception FileNotFoundException if the named file does not exist, $
7 * is a directory rather than a regular file, $
8 * or for some other reason cannot be opened for $
9 * reading. $
10 */ $
11 public FileReader(String fileName) throws FileNotFoundException { $
12 super(new FileInputStream(fileName)); $
13 } $
总结
- 装饰对象和真实对象有相同的接口或父类
- 装饰者类维护一个被装饰者的引用
- 装饰者接收所有来自客户端的请求,并把这些请求转发给被装饰者,实现大多数功能
设计原则
- 多用组合,少用继承
- 类应该设计为对扩展开放,对修改关闭
本文链接: http://www.xiaopeng.pro/articles/a708a60d.html
版权声明: 本原创文章采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!