装饰模式(Decorator Pattern)也称为包装模式(Wrapper Pattern),使用一种对客户端透明的方式来动态地扩展对象的功能,同时它也是继承关系的一种替代方案之一。在现实生活中你也可以看见很多装饰模式的例子,或者可以大胆地说装饰模式无处不在,就拿人来说,人需要各式各样的衣着,不管你穿着怎样,但是,对于个人的本质来说是不变的,充其量只是在外面披上一层遮羞物而已,这就是装饰模式,装饰物也许各不相同但是装饰的对象本质是不变的。

1 装饰模式 UML

图片来源于《Android 源码设计模式解析与实战》

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//抽象类, 被装饰的原始对象
public abstract class Component {
//返回类型可以改变,从而实现装饰不同的属性
public abstract void operate();
}

//组件的具体实现
public class ConcreteComponent extends Component {
@Override
public void operate() {
//最基础的实现方法
}
}

//抽象装饰者
public abstract class Decorator extends Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}

@Override
public void operate() {
component.operate();
}
}

//具体实现类
public class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}

@Override
public void operate() {
//装饰基础方法,可以在在调用前后
//如果父类有返回类型也可以接着处理返回对象,最后返回最终昂对象
operateA();
super.operate();
operateB();
}
public void operateA() {
//装饰 A
}
public void operateB() {
//装饰 B
}
}


//具体调用,初始化基础类
Component component = new ConcreteComponent();
//基础类进行装饰
Decorator decorator = new ConcreteDecoratorA(component);
decorator.operate();

2 简单实现

开头说过人需要各式各样的衣着,不管你穿着怎样,但是,对于个人的本质来说是不变的,充其量只是在外面披上一层遮羞物而已,这就是装饰模式。那就用穿衣举例。

1
2
3
public abstract class Person {
public abstract void dressed();
}

这个类就是抽象类, 被装饰的原始对象,然后我们需要一个基础实现类

1
2
3
4
5
6
public class Boy extends Person {
@Override
public void dressed() {
System.out.println("穿平角内裤");
}
}

这里是一个正常男生,实现了最基础的方法,当然你可以新增加一个屌丝男士,穿个丁字的 ~。然后我们需要对这个男生进行包装!

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//抽象装饰者,抽象方法过多时,可以统一重写子类不需要重写的方法,避免代码重复
public class PersonCloth extends Person {
protected Person mPerson;
public PersonCloth(Person mPerson) {
this.mPerson = mPerson;
}

@Override
public void dressed() {
mPerson.dressed();
}
}

public class ExpensiveCloth extends PersonCloth {
public ExpensiveCloth(Person mPerson) {
super(mPerson);
}
private void dressShirt() {
System.out.println("穿件短袖");
}
private void dressLeather() {
System.out.println("穿件皮衣");
}
private void dressJean() {
System.out.println("穿条牛仔裤");
}

@Override
public void dressed() {
super.dressed();
dressShirt();
dressLeather();
dressJean();
}
}

public class CheapCloth extends PersonCloth {
public CheapCloth(Person mPerson) {
super(mPerson);
}
private void dressShorts() {
System.out.println("穿个短裤");
}

@Override
public void dressed() {
super.dressed();
dressShorts();
}
}

这是两种不同价位的包装(装饰),两者都是为原本 Boy 类中的 dressed 方法提供功能扩展,不过这种扩展并非是直接修改原有的方法逻辑或结构,更恰当地说,仅仅是在另一个类中将原有方法和新逻辑进行封装整合而已。

1
2
3
4
5
6
7
Person person = new Boy();

PersonCloth clothCheap = new CheapCloth(person);
clothCheap.dressed();

PersonCloth clothExpensive = new ExpensiveCloth(person);
clothExpensive.dressed();

3 Java 中的装饰模式(Java IO)

图片来源于极客时间

在初学 Java 的时候,曾经对 Java IO 的一些用法产生过很大疑惑。我们打开文件 test.txt,从中读取数据。其中,InputStream 是一个抽象类,FileInputStream 是专门用来读取文件流的子类。BufferedInputStream 是一个支持带缓存功能的数据读取类,可以提高数据读取的效率。

1
2
3
4
5
6
InputStream in = new FileInputStream("/test.txt");
InputStream bin = new BufferedInputStream(in);
byte[] data = new byte[128];
while (bin.read(data) != -1) {
//...
}

这里的装饰模式有个特殊的使用场景(之前的例子代码上也可以,只是场景不合适),装饰器类和原始类继承同样的父类,这样我们可以对原始类 “嵌套” 多个装饰器类。

FileInputStream 嵌套了两个装饰器类:BufferedInputStreamDataInputStream,让它既支持缓存读取,又支持按照基本数据类型来读取数据。

1
2
3
4
InputStream in = new FileInputStream("/test.txt");
InputStream bin = new BufferedInputStream(in);
DataInputStream din = new DataInputStream(bin);
int data = din.readInt();

其实 BufferedInputStreamDataInputStream 继承自 FilterInputStreamJava IO 抽象出的一个装饰器父类,他就类似于 抽象装饰者(PersonCloth) ,因为这两个装饰类只是对部分方法进行装饰,还有一部分方法没必要重写 FilterInputStream 就是统一重写方法,避免代码重复。

具体可以看 JDK 源码,和上面例子基本类似。

4 Android 源码中的装饰模式(Context)

理解Android Context

感谢

设计模式之美

《Android 源码设计模式解析与实战》

以及上文中的链接