设计模式系列三之装饰者模式(Decorator)

什么是装饰者模式:给爱用继承的人一个全新的设计眼界

装饰者模式,是面向对象编程领域中,一种动态地往一个类中添加新行为的设计模式。就功能而言,装饰者模式相比生成子类更加灵活,这样可以给某个对象而不是整个类添加一些功能。- 摘自维基百科

装饰者模式,动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。- 摘自《HeadFirst设计模式》

装饰者模式的类图

装饰者模式的优点与不足

优点

  • 装饰者模式与继承关系的目的都是为了扩展对象的功能,但是装饰者可以提供比继承更多的灵活性,比如可以在运行着扩展对象的功能,而继承在编译时就已经确定了;
  • 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出不同行为的组合;

不足

  • 比继承更灵活,也意味着更多的复杂性;
  • 装饰者模式会导致设计中出现很多小类,如果过度使用,会是程序变得很复杂;(Java的IO包就够复杂了)
  • 装饰者模式是针对抽象组件(Component)类型编程。但是,如果要针对具体组件编程时,就应该重新思考应用架构,以及装饰者是否合适。当然也可以改变Component接口,增加新的公开方法,实现”半透明”的装饰者模式。在实际项目中作出最佳选择。

装饰者模式的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.github.archerda.designpattern.decorator;
/**
* 抽象组件;
*
* 饮料类;
*
* Beverage 是一个抽象类,有两个方法:getDescription() 和 cost();
* getDescription() 已经在此实现了,但是 cost()必须在子类实现;
*
* Create by archerda on 2017/08/24.
*/
public abstract class Beverage {
String description = "Unknown Beverage";
public abstract double cost();
public String getDescription() {
return description;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.github.archerda.designpattern.decorator;
/**
* 抽象装饰者;
*
* 调料类;
*
* 必须让 CondimentDecorator 能够取代 Beverage, 所以将 CondimentDecorator 继承自 Beverage;
*
* Create by archerda on 2017/08/24.
*/
public abstract class CondimentDecorator extends Beverage {
// 所有的调料者都必须重新实现 getDescription;
public abstract String getDescription();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.github.archerda.designpattern.decorator;
/**
* 具体组件;
*
* 浓缩咖啡类;
*
* 首先,让 Coffee 继承自 Beverage类,因为 Coffee 是一种饮料;
*
* Create by archerda on 2017/08/24.
*/
public class Coffee extends Beverage {
public Coffee() {
description = "Coffee";
}
@Override
public double cost() {
return 10.00;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.github.archerda.designpattern.decorator;
/**
* 具体组件;
*
* Create by archerda on 2017/08/24.
*/
public class Tea extends Beverage {
public Tea() {
description = "Tea";
}
@Override
public double cost() {
return 8;
}
}
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
package com.github.archerda.designpattern.decorator;
/**
* 具体装饰者;
*
* Create by archerda on 2017/08/24.
*/
public class Milk extends CondimentDecorator {
Beverage beverage;
public Milk(Beverage beverage) {
this.beverage = beverage;
}
@Override
public double cost() {
return beverage.cost() + 2.0;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Milk";
}
}
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
package com.github.archerda.designpattern.decorator;
/**
* 具体装饰者;
*
* Create by archerda on 2017/08/24.
*/
public class Mocha extends CondimentDecorator {
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public double cost() {
return beverage.cost() + 1.5;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.github.archerda.designpattern.decorator;
/**
* Create by archerda on 2017/08/24.
*/
public class Main {
public static void main(String[] args) {
// 订一杯 Coffee,不需要调料;
Beverage beverage1 = new Coffee();
System.out.println(beverage1.getDescription() + " ¥" + beverage1.cost());
// 制造一个 Tea 对象;
Beverage beverage2 = new Tea();
beverage2 = new Mocha(beverage2); // 用 Mocha 装饰它;
beverage2 = new Mocha(beverage2); // 用第二个 Mocha 装饰它;
beverage2 = new Milk(beverage2); // 用 Milk 装饰它;
System.out.println(beverage2.getDescription() + " ¥" + beverage2.cost());
}
}

Java IO中装饰者模式的应用

Java I/O的组织

下面是一个典型的对象集合,用装饰者将功能结合起来,以读取文件数据:

其中BufferedInputStream和LineNumberInputStream都扩展自FilterInputStream,它是一个抽象的装饰类。

下面是java.io库的结构:

其实,”输出”流的设计方式也是一样的。此外,Reader/Writer流(作为基于字符数据的输入输出)和输入流/输出流相当类似。

但是,Java I/O也引出装饰者模式的一个”缺点”:利用装饰者模式,常常造成设计中有大量的小类,数量实在太多,可能会造成使用此API程序员的困扰。但是,如果你了解装饰者模式的工作原理,以后当使用别人的大量装饰API时,就可以很容易辨别出他们的装饰者类是如何组织的,以方便包装来取得想要的行为。

编写自己的Java I/O装饰者

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
29
30
31
32
package com.github.archerda.designpattern.decorator.jdk;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 具体装饰者;
*
* Create by archerda on 2017/08/24.
*/
public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int c = super.read();
return c == -1 ? c : Character.toLowerCase((char) c);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int result = super.read(b, off, len);
for (int i = off; i < off + result; ++i) {
b[i] = (byte) Character.toLowerCase((char)b[i]);
}
return result;
}
}
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
29
30
31
package com.github.archerda.designpattern.decorator.jdk;
import java.io.*;
import java.net.URL;
/**
* Create by archerda on 2017/08/24.
*/
public class Main {
public static void main(String[] args) throws Exception {
// txt内容是:I'm a TEXT.
URL resource = Main.class.getClassLoader().getResource("LowerCaseInputStreamTest.txt");
assert resource != null;
File file = new File(resource.toURI());
// 具体组件;
FileInputStream fileInputStream = new FileInputStream(file);
// 装饰者;
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
// 自定义装饰者;
LowerCaseInputStream lowerCaseInputStream = new LowerCaseInputStream(bufferedInputStream);
int c;
while ((c = lowerCaseInputStream.read()) > 0) {
System.out.print((char) c);
}
System.out.println("");
}
}

参考文档