在面向对象的编程中,装饰器模式(Decorator Pattern)是一种结构型设计模式,允许动态地为对象添加功能。这种模式比继承更为灵活,因为它能够在运行时进行功能扩展,而不需要创建大量的子类。
装饰器模式通过将功能封装到装饰器类中,能够使得对象在运行时不断地增加新的职责,而不会改变原始对象的结构。这个设计理念在很多实际的应用中都非常有效,尤其是当我们面对需要增加多种功能的情况时。
2.1 装饰器模式与IO流
在Java标准库中,IO流的实现本身就遵循了装饰器模式。InputStream
是一个抽象类,它的多个子类(如 FileInputStream
、ServletInputStream
、Socket.getInputStream()
)负责提供数据源。如果我们需要对这些数据源添加新的功能(如缓冲、加密、解压等),传统的做法是通过继承来创建多个子类。然而,当数据源或功能的种类增加时,子类数量会迅速暴涨,维护起来非常困难。
装饰器模式解决了这一问题。我们可以通过将不同的装饰器逐层包装在数据源对象周围,实现所需的附加功能,而不需要修改原始类。
2.2 示例:文件流与装饰器模式
假设我们有一个 FileInputStream
,并且希望为它动态地增加缓冲和解压缩功能。在不使用装饰器模式的情况下,我们可能需要为每一种数据源和每一种功能创建多个子类。但使用装饰器模式,我们只需创建装饰器类,逐步为原始流添加功能。
以下是装饰器模式的代码示例:
java
// 创建原始的数据源:
InputStream fis = new FileInputStream("test.gz");
// 增加缓冲功能:
InputStream bis = new BufferedInputStream(fis);
// 增加解压缩功能:
InputStream gis = new GZIPInputStream(bis);
或者我们可以把它写成一行:
java
InputStream input = new GZIPInputStream( // 第二层装饰
new BufferedInputStream( // 第一层装饰
new FileInputStream("test.gz") // 核心功能
));
在这里,BufferedInputStream
和 GZIPInputStream
都是从 FilterInputStream
类继承的,而 FilterInputStream
充当了装饰器的角色。通过逐层包装,我们可以为流对象添加多个功能,而无需修改原始的数据源类。
2.3 装饰器模式的类图
装饰器模式的类图如下所示:
markdown
┌───────────┐
│ Component │
└───────────┘
▲
┌────────────┼─────────────────┐
│ │ │
┌───────────┐┌───────────┐ ┌───────────┐
│ComponentA ││ComponentB │... │ Decorator │
└───────────┘└───────────┘ └───────────┘
▲
┌──────┴──────┐
│ │
┌───────────┐ ┌───────────┐
│DecoratorA │ │DecoratorB │...
└───────────┘ └───────────┘
Component
是顶层接口或抽象类,对应于IO流中的 InputStream
。
ComponentA
和 ComponentB
是具体的数据源类,对应 FileInputStream
、ServletInputStream
等。
Decorator
是抽象装饰器类,对应于 FilterInputStream
。
从 Decorator
派生出的具体装饰器类,如 BufferedInputStream
和 GZIPInputStream
,提供具体的功能扩展。
接下来,我们来看一个通过装饰器模式实现文本效果的例子。假设我们需要动态地为HTML文本添加加粗、斜体、下划线等效果。我们可以通过定义一个顶层接口 TextNode
和多个装饰器类来实现这个需求。
3.1 定义顶层接口
首先,定义一个顶层接口 TextNode
,用于设置和获取文本内容:
java
public interface TextNode {
void setText(String text); // 设置文本内容
String getText(); // 获取文本内容
}
3.2 实现核心节点:SpanNode
SpanNode
作为一个简单的文本节点,实现了 TextNode
接口,返回基本的HTML标签 :
java
public class SpanNode implements TextNode {
private String text;
public void setText(String text) {
this.text = text;
}
public String getText() {
return "" + text + "";
}
}
3.3 定义抽象装饰器类:NodeDecorator
接下来,我们定义一个抽象装饰器类 NodeDecorator
,它实现了 TextNode
接口并持有一个 TextNode
实例,所有的具体装饰器都会从这个类继承:
java
public abstract class NodeDecorator implements TextNode {
protected final TextNode target;
protected NodeDecorator(TextNode target) {
this.target = target;
}
public void setText(String text) {
this.target.setText(text);
}
}
3.4 实现具体装饰器类:BoldDecorator
我们可以创建一个加粗装饰器 BoldDecorator
,它会将原始文本内容包裹在 标签中:
java
public class BoldDecorator extends NodeDecorator {
public BoldDecorator(TextNode target) {
super(target);
}
public String getText() {
return "" + target.getText() + "";
}
}
3.5 客户端使用装饰器
客户端可以自由组合不同的装饰器,动态地为文本添加各种效果:
java
TextNode n1 = new SpanNode();
TextNode n2 = new BoldDecorator(new UnderlineDecorator(new SpanNode()));
TextNode n3 = new ItalicDecorator(new BoldDecorator(new SpanNode()));
n1.setText("Hello");
n2.setText("Decorated");
n3.setText("World");
System.out.println(n1.getText()); // 输出 Hello
System.out.println(n2.getText()); // 输出 Decorated
System.out.println(n3.getText()); // 输出 World
3.6 练习
请实现一个装饰器,给文本添加
标签表示删除效果。
下载练习:[下载链接]
装饰器模式是一种非常灵活且强大的设计模式。它可以独立地增加核心功能和附加功能,并且允许我们在运行时动态地组合功能。与传统的继承方式相比,装饰器模式避免了子类数量爆炸的问题,使得系统更加灵活、可扩展。
通过装饰器模式,我们可以清晰地分离核心功能和附加功能,使得系统的功能扩展更加简单,并且能够根据需要灵活组合各种功能,极大地提高了代码的可维护性和可扩展性。