20 设计模式——装饰模式

返回设计模式博客目录

介绍


装饰(Decorator)模式:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活

装饰模式也称为包装模式,结构型设计模式之一,其使用一种对客户端透明的方式来动态地扩展对象地功能,同时它也是继承关系的一种替代方案之一。

优点

  • 采用装饰模式扩展对象的功能比采用继承方式更加灵活。
  • 可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。

缺点

装饰模式增加了许多子类,如果过度使用会使程序变得很复杂。

使用场景

需要透明且动态地扩展类的功能。

结构与实现


通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰模式的目标。下面来分析其基本结构和实现方法。

模式包含以下主要角色。

  • Component(抽象组件):接口或者抽象类,被装饰的最原始的对象。具体组件与抽象装饰角色的父类。
  • ConcreteComponent(具体组件):实现抽象组件的接口。
  • Decorator(抽象装饰角色):一般是抽象类,抽象组件的子类,同时持有一个被装饰者的引用,用来调用被装饰者的方法;同时可以给被装饰者增加新的职责。
  • ConcreteDecorator(具体装饰类):抽象装饰角色的具体实现。

其结构图如下图所示。

代码如下:

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
public class DecoratorPattern {
public static void main(String[] args) {
Component p = new ConcreteComponent();
p.operation();
System.out.println("---------------------------------");
Component d = new ConcreteDecorator(p);
d.operation();
}
}
// 抽象构件角色
interface Component {
void operation();
}
// 具体构件角色
class ConcreteComponent implements Component {
public ConcreteComponent() {
System.out.println("创建具体构件角色");
}
public void operation() {
System.out.println("调用具体构件角色的方法 operation()");
}
}
// 抽象装饰角色
abstract class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
public void operation() {
component.operation();
}
}
// 具体装饰角色
class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
public void operation() {
super.operation();
addedFunction();
}
public void addedFunction() {
System.out.println("为具体构件角色增加额外的功能 addedFunction()");
}
}

程序运行结果如下:

1
2
3
4
5
创建具体构件角色
调用具体构件角色的方法 operation()
---------------------------------
调用具体构件角色的方法 operation()
为具体构件角色增加额外的功能 addedFunction()

示例


人总是要穿衣服,我们将人定义为一个抽象类,将其穿衣服的行为定义为一个抽象方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
public abstract class Person {
// Person 下有一个穿着的抽象方法
public abstract void dressed();
}
public class Boy extends Person {
@Override
public void dressed() {
System.out.println("穿了内衣内裤");
}
}

PersonCloth 用来装饰 Person:

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class PersonCloth extends Person {
protected Person mPerson; // 保持一个 Person 类的引用
public PersonCloth(Person person) {
mPerson = person;
}
@Override
public void dressed() {
mPerson.dressed();
}
}

下面两个是继承 PersonCloth 的实际装饰类:

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
public class CheapCloth extends PersonCloth {
public CheapCloth(Person person) {
super(person);
}
public void dressShorts() {
System.out.println("穿条短裤");
}
@Override
public void dressed() {
super.dressed();
dressShorts()
}
}
public class ExpensiveCloth extends PersonCloth {
public ExpensiveCloth(Person person) {
super(person);
}
/**
* 穿短袖
*/
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();
}
}

测试代码:

1
2
3
4
5
6
7
8
9
10
// 首先我们要有一个Boy
Person person = new Boy();
// 穿上便宜的衣服
CheapCloth cheapCloth = new CheapCloth(person);
cheapCloth.dressed();
// 穿上昂贵的衣服
ExpensiveCloth expensiveCloth = new ExpensiveCloth(person);
expensiveCloth.dressed();

ANDROID 源码中的实现


Context 类在 Android 中被称为“上帝对象”,它本质是一个抽象类,其在我们装饰者模式里相当于抽象组件,而在其内部定义了大量的抽象方法,比如我们经常会用到的 startActivity 方法。

1
2
3
4
5
6
7
8
public abstract class Context {
...
public abstract void startActivity(@RequiresPermission Intent intent);
...
public abstract void startActivity(@RequiresPermission Intent intent,
@Nullable Bundle options);
...
}

真正的实现是在 ContextImpl 中完成的,ContextImpl 继承自 Context 抽象类,并实现了 Context 中的抽象方法。

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
class ContextImpl extends Context {
...
@Override
public void startActivity(Intent intent) {
warnIfCallingFromSystemProcess();
startActivity(intent, null);
}
/** @hide */
@Override
public void startActivityAsUser(Intent intent, UserHandle user) {
startActivityAsUser(intent, null, user);
}
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
// Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
// generally not allowed, except if the caller specifies the task id the activity should
// be launched in. A bug was existed between N and O-MR1 which allowed this to work. We
// maintain this for backwards compatibility.
final int targetSdkVersion = getApplicationInfo().targetSdkVersion;
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
&& (targetSdkVersion < Build.VERSION_CODES.N
|| targetSdkVersion >= Build.VERSION_CODES.P)
&& (options == null
|| ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}
}

这里 ContextImpl 就相当于组件具体实现类,那么谁来承担装饰者的身份呢?

Activity 从类层次上来说本质是一个 Context。Activity 并非直接继承于 Context,而是继承于 ContextThemeWrapper。

1
2
3
4
5
6
7
8
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback, WindowControllerCallback,
AutofillManager.AutofillClient {
...
}

而这个 ContextThemeWrapper 又是继承于 ContextWrapper。

1
public class ContextThemeWrapper extends ContextWrapper {}

最终这个 ContextWrapper 才继承于 Context。

为什么类层次会这么复杂呢?其实这里就是一个典型的装饰模式,ContextWrapper 就是我们要找的装饰者,在 ContextWrapper 中有一个 Context 的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ContextWrapper extends Context {
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
/**
* Set the base context for this ContextWrapper. All calls will then be
* delegated to the base context. Throws
* IllegalStateException if a base context has already been set.
*
* @param base The new base context for this wrapper.
*/
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
...
}

ContextWrapper 类的 startActivity 方法如下:

1
2
3
4
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}

可以看出它调用了 ContextImpl 中对应的方法。

装饰模式应用的套路都是很相似的,对于具体方法的包装扩展则由 ContextWrapper 的具体子类完成,比如我们的 Activity、Service 和 Application。