封面来源:碧蓝航线 蝶海梦花 活动CG

本文参考:

Java 设计模式学习网站:Java设计模式:23种设计模式全面解析(超级详细)

菜鸟教程:装饰器模式

1. 模式的定义与特点

老规矩,咱们从实际场景出发:

相信大家应该都吃过汉堡,简单来说,汉堡由以下几部分组成:面包、肉饼、蔬菜、酱。商家为了吸引顾客往往会推出多种汉堡供顾客选择,这些汉堡也大同小异,基本都由上面这些部分组成,只不过某些部分的原材料可能发生改变,或增加某些部分的含量。比如,《海绵宝宝》中的蟹黄堡(Krabby patty)的配方[1]是这样的:面包,煎肉饼,番茄,酸黄瓜,腌椰菜,白芝麻,芥末酱,洋葱,芝士片,满满的爱。如果某位顾客十分钟爱蟹黄堡中的肉饼,他要求多加一份肉饼,那么海绵宝宝就会在基本的蟹黄堡的基础上多加一份肉饼;又如果某位顾客想多加一份培根,海绵宝宝也会在基本的蟹黄堡的基础上完成顾客的要求。假设这些顾客的奇怪要求被更多的顾客认可,他们也要试试这种改版的蟹黄堡,于是海绵宝宝推出了各种改版的蟹黄堡,但究其本质,这些改版的蟹黄堡依旧是蟹黄堡,只不过是“不太正常”的蟹黄堡。

我们可以将蟹黄堡的改版过程应用到软件开发过程中。假设在软件开发过程中,已经存在了一些现存的组件(基本的蟹黄堡),这些组件完成了一些核心功能,但是随着业务的变更,这些组件不能在满足使用,我们必须想办法在不改变其结构的情况下,对这些组件的功能进行拓展(改版的蟹黄堡)。

某些机智的小伙伴就要问了,为什么非得拓展呢?我再写一个组件不好吗?

确实是可以再写一个组件,但是新写的组件与旧组件之间可能会有重复代码。

那使用继承的方式呗。

那如果这样的组件很多呢?都新写一个子类吗?由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀,造成“类爆炸”的问题。为了实现这个需求,采用【装饰器模式】来解决是比较合适的。

定义

装饰器(Decorator)模式:在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

这种模式将创建一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

就增加功能来说,【装饰器模式】相比生成子类更为灵活。

优点

1、装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用;

2、通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效果;

3、装饰类和被装饰类可以独立发展,不会相互耦合;

4、装饰器模式完全遵守开闭原则。

缺点

1、装饰器模式会增加许多子类,过度使用会增加程序得复杂性。

2. 适用场景

1、当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。

2、当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰器模式却很好实现。

3、当对象的功能要求可以动态地添加,也可以再动态地撤销时。

在使用【装饰器模式】时,需要将具体功能职责划分,同时继承装饰者模式。

3. 模式的结构

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

【装饰器模式】的主要角色如下:

1、抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。相当于一种名为“汉堡”的食物。

2、具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责。一种具体的汉堡,名为“蟹黄堡”。

3、抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。“蟹黄堡”的改版方式。

4、具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。具体的改版“蟹黄堡”。

【装饰器模式】的结构图:

装饰器模式的结构图

不难看出,【装饰器模式】的关键代码是充当抽象构件角色、不应该具体实现的 Component 类,并且抽象装饰角色应当引用和继承 Component 类,而具体装饰角色重写父类方法。

4. 代码的实现

按照上述的结构图,不难使用 Java 来实现【装饰器模式】。

首先是编写抽象构件角色:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 抽象构件角色
*
* @author mofan
* @date 2021/8/29 15:29
*/
public interface Component {
/**
* 操作抽象方法,也是构件的核心方法
*/
void operation();
}

然后围绕抽象构件角色编写具体的构件角色:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 具体构件角色
*
* @author mofan
* @date 2021/8/29 15:30
*/
public class ConcreteComponent implements Component{

public ConcreteComponent() {
System.out.println("创建了具体构件角色...");
}

@Override
public void operation() {
System.out.println("调用具体构件角色的 operation() 方法...");
}
}

为了实现拓展,得编写抽象装饰角色:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 抽象装饰角色
*
* @author mofan
* @date 2021/8/29 15:32
*/
public class Decorator implements Component {
/**
* 某一具体的构件角色
*/
private final Component component;

public Decorator(Component component) {
this.component = component;
}

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

抽象装饰角色实现抽象构件接口,并在内部引用一个构件角色,重写 operation() 方法,在此方法中调用引用的构件角色的 operation() 方法,使具体构件角色的核心方法不被丢失。

围绕抽象装饰角色编写具体装饰角色:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 具体装饰角色
*
* @author mofan
* @date 2021/8/29 15:34
*/
public class ConcreteDecorator extends Decorator{

public ConcreteDecorator(Component component) {
super(component);
}

@Override
public void operation() {
super.operation();
addedFunction();
}

public void addedFunction() {
System.out.println("为具体构件角色增加额外的 addedFunction() 功能...");
}
}

最后编写一个测试方法,看看如何使用具体的装饰角色:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author mofan
* @date 2021/8/29 15:37
*/
public class DecoratorTest {
@Test
public void testDecorator() {
Component component = new ConcreteComponent();
component.operation();
System.out.println("---------------------------------");
Component decorator = new ConcreteDecorator(component);
decorator.operation();
}
}

运行上述代码后,控制台打印出:

创建了具体构件角色...
调用具体构件角色的 operation() 方法...
---------------------------------
调用具体构件角色的 operation() 方法...
为具体构件角色增加额外的 addedFunction() 功能...

5. 模式的拓展

5.1 模式的变种

在上述代码中,我们发现抽象构件角色和抽象装饰角色似乎都可以省略,因为具体构件角色和具体装饰角色都只有一个。

因此,【装饰器模式】所包含的 4 个角色并不是任何时候都要存在的,在某些情况下是可以简化的,比如:

只有一个具体构件角色

如果只有一个具体构件,可以省略抽象构件,并让抽象装饰角色继承具体构件角色。其结构图如下:

只有一个具体构件的装饰器模式

只有一个具体装饰角色

如果只有一个具体装饰,可以让抽象装饰和具体装饰合并。其结构图如下:

只有一个具体装饰的装饰器模式

5.2 在 JDK 中的应用

我们发现像【装饰器模式】这种在类中一层套一层的方式好像在哪见过,是的,【装饰器模式】在 Java 中的最著名的应用莫过于 Java I/O 标准库的设计了。

例如,字节输入流 InputStream 的子类 FilterInputStream,字节输出流 OutputStream 的子类 FilterOutputStream,字符输入流 Reader 的子类 BufferedReader 以及 FilterReader,还有字符输出流 Writer 的子类 BufferedWriter 等,它们都是抽象装饰类。

比如下面代码是为 FileInputStream 增加缓冲区而采用的装饰类 BufferedInputStream 的例子:

1
2
BufferedInputStream buffer = new BufferedInputStream(new FileInputStream("fileName.txt"));
buffer.read();

查看源码可以发现,BufferedInputStream 作为具体装饰角色,其抽象装饰角色是 FilterInputStreamFileInputStream 作为具体构件角色,其抽象构件角色是 InputStream

查看 FilterInputStream 的源码可以发现它继承了抽象构件角色 InputStream,并在其内部引用了 InputStream

1
2
3
4
5
6
7
8
9
10
11
public class FilterInputStream extends InputStream {
protected volatile InputStream in;

protected FilterInputStream(InputStream in) {
this.in = in;
}

public int read() throws IOException {
return in.read();
}
}

很明显,与我们前面讲的抽象装饰角色的结构基本一致。


  1. 百度百科-蟹黄堡 ↩︎