这是一个在 Java I/O 库中广泛使用的经典设计模式,理解它对于提升代码的可扩展性和灵活性非常有帮助。

什么是装饰器模式?
装饰器模式,也叫包装器模式,是一种结构型设计模式,它允许你向一个对象动态地添加新的行为,而无需修改该对象的类或使用继承。
核心思想: 将功能包装在对象中,而不是通过继承来扩展功能。
打个比方: 想象一下你有一杯咖啡(这是你的基础对象)。
- 你可以加牛奶,这就给咖啡增加了“加奶”的功能。
- 你可以再加糖,这就又增加了“加糖”的功能。
- 你也可以先加糖,再加奶。
这里的“牛奶”和“糖”就是装饰器,它们包装了咖啡,为它增加了新的特性,你可以任意组合这些装饰器,而咖啡本身的核心代码(Coffee类)完全不需要改变。

为什么需要装饰器模式?(解决的问题)
装饰器模式主要解决了以下问题:
- 避免“类爆炸”问题:如果使用继承来扩展功能,
MilkCoffee,SugarMilkCoffee,WhippedCreamCoffee... 类的数量会呈指数级增长,难以管理。 - 动态添加功能:继承是在编译时确定好的,而装饰器模式允许你在运行时动态地为对象添加或移除功能。
- 遵循“开闭原则” (Open/Closed Principle):软件实体应该对扩展开放,对修改关闭,装饰器模式完美地体现了这一点,你可以通过增加新的装饰器来扩展功能,而无需修改原有组件的代码。
- 替代继承:当“是一个”的关系(is-a)不成立时,继承就不合适了。“加糖的咖啡”不是一种咖啡,而是一个被装饰的咖啡,装饰器模式更符合这种“有一个”的关系(has-a)。
装饰器模式的结构
装饰器模式包含以下几个核心角色:
-
组件:定义一个对象接口,可以动态地添加职责,这是所有真实对象和装饰器的共同接口或抽象类。
Coffee接口。
-
具体组件:定义一个对象,可以给这个对象添加一些职责,这是被装饰的原始对象。
(图片来源网络,侵删)SimpleCoffee(普通咖啡)。
-
装饰器:持有一个组件的引用,并实现与组件相同的接口,它的核心作用是:
- 将请求转发给被包装的组件。
- 在转发请求之前或之后,可以执行一些额外的操作(即添加新的功能)。
CoffeeDecorator(抽象装饰器)。
-
具体装饰器:负责给组件添加新的职责。
MilkDecorator(牛奶装饰器),SugarDecorator(糖装饰器)。
结构图:
+-------------------+
| Component | <-----------------------------+
| + operation() | |
+-------------------+ |
^ |
| (实现) |
v |
+-------------------+ |
| ConcreteComponent| |
| + operation() | |
+-------------------+ |
^ |
| (被包装) |
v |
+-------------------+ |
| Decorator | |
| + component | |
| + operation() | |
+-------------------+ |
^ |
| (继承/组合) |
v |
+-------------------+ +-------------------+ |
| ConcreteDecoratorA| | ConcreteDecoratorB| |
| + operation() | | + operation() | |
+-------------------+ +-------------------+ |
Java 代码示例
我们用咖啡的例子来实现这个模式。
步骤 1: 定义组件接口
// Component.java
public interface Coffee {
double getCost(); // 获取价格
String getDescription(); // 获取描述
}
步骤 2: 创建具体组件
// SimpleCoffee.java
public class SimpleCoffee implements Coffee {
@Override
public double getCost() {
return 10.0; // 一杯普通咖啡10元
}
@Override
public String getDescription() {
return "Simple Coffee";
}
}
步骤 3: 创建抽象装饰器
// CoffeeDecorator.java
public abstract class CoffeeDecorator implements Coffee {
// 持有一个被装饰的Coffee对象的引用
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
// 默认将操作委托给被装饰的对象
@Override
public double getCost() {
return decoratedCoffee.getCost();
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
}
步骤 4: 创建具体装饰器
// MilkDecorator.java
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
// 在原有价格基础上增加2元
return super.getCost() + 2.0;
}
@Override
public String getDescription() {
// 在原有描述基础上增加内容
return super.getDescription() + ", with Milk";
}
}
// SugarDecorator.java
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
// 在原有价格基础上增加1元
return super.getCost() + 1.0;
}
@Override
public String getDescription() {
// 在原有描述基础上增加内容
return super.getDescription() + ", with Sugar";
}
}
步骤 5: 客户端使用
// Client.java
public class CoffeeShop {
public static void main(String[] args) {
// 1. 点一杯普通咖啡
Coffee simpleCoffee = new SimpleCoffee();
System.out.println("Order: " + simpleCoffee.getDescription());
System.out.println("Price: " + simpleCoffee.getCost());
System.out.println("--------------------");
// 2. 点一杯加牛奶的咖啡
// 用 MilkDecorator 包装 SimpleCoffee
Coffee milkCoffee = new MilkDecorator(simpleCoffee);
System.out.println("Order: " + milkCoffee.getDescription());
System.out.println("Price: " + milkCoffee.getCost());
System.out.println("--------------------");
// 3. 点一杯加糖又加牛奶的咖啡
// 用 SugarDecorator 包装 MilkDecorator
// (也就是用 SugarDecorator 包装了 SimpleCoffee)
Coffee sugarAndMilkCoffee = new SugarDecorator(milkCoffee);
// 或者一步到位: Coffee sugarAndMilkCoffee = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
System.out.println("Order: " + sugarAndMilkCoffee.getDescription());
System.out.println("Price: " + sugarAndMilkCoffee.getCost());
System.out.println("--------------------");
}
}
输出结果:
Order: Simple Coffee
Price: 10.0
--------------------
Order: Simple Coffee, with Milk
Price: 12.0
--------------------
Order: Simple Coffee, with Milk, with Sugar
Price: 13.0
--------------------
装饰器模式在 Java I/O 中的应用
装饰器模式最经典的例子就是 Java 的 I/O 流库。
- 组件:
InputStream,OutputStream等抽象类。 - 具体组件:
FileInputStream,FileOutputStream,ByteArrayInputStream等,它们是底层的、最原始的流。 - 装饰器:
FilterInputStream,FilterOutputStream,它们持有一个InputStream或OutputStream的引用,并扩展了它们的功能。 - 具体装饰器:
BufferedInputStream: 为输入流添加缓冲功能,提高读取效率。DataInputStream: 允许应用程序以与机器无关的方式从底层输入流中读取基本 Java 数据类型。BufferedOutputStream: 为输出流添加缓冲功能。GZIPOutputStream: 为输出流添加压缩功能。
使用示例:
// 读取一个文件,并使用缓冲和字符转换功能
// 1. 底层节点流 (具体组件)
FileInputStream fis = new FileInputStream("test.txt");
// 2. 装饰器1: 缓冲流 (BufferedInputStream装饰了FileInputStream)
BufferedInputStream bis = new BufferedInputStream(fis);
// 3. 装饰器2: InputStreamReader (装饰了BufferedInputStream)
// 它将字节流转换为字符流
InputStreamReader isr = new InputStreamReader(bis);
// 4. 装饰器3: BufferedReader (装饰了InputStreamReader)
// 它为字符流添加了缓冲和按行读取的功能
BufferedReader reader = new BufferedReader(isr);
String line = reader.readLine();
System.out.println(line);
reader.close();
在这个例子中,BufferedReader 装饰了 InputStreamReader,InputStreamReader 又装饰了 BufferedInputStream,而 BufferedInputStream 最终装饰了 FileInputStream,你可以像叠俄罗斯套娃一样,灵活地组合这些功能,而 FileInputStream 本身的代码完全不需要改变。
优点与缺点
优点:
- 高度灵活和可扩展:可以任意组合装饰器来创建复杂的功能。
- 遵循开闭原则:无需修改现有代码即可扩展新功能。
- 避免继承的弊端:避免了由于使用继承而产生的类数量爆炸问题。
缺点:
- 增加复杂性:相比简单的继承,装饰器模式需要创建更多的类,可能会使系统设计变得复杂。
- 排他性:装饰器模式通常只能用于具有相同接口的场景,如果需要装饰的对象接口差异很大,装饰器模式就不适用了。
- 调试困难:由于对象被多层包装,在调试时追踪对象的实际行为和来源可能会比较困难。
装饰器模式是一个非常强大且实用的设计模式,它提供了一种比继承更灵活、更优雅的方式来扩展对象的功能,它的核心在于组合和委托,通过将对象包装在装饰器中,动态地为其添加新的行为。
记住它的关键点:
- 组件接口:定义共同契约。
- 具体组件:被装饰的原始对象。
- 装饰器:实现组件接口,持有一个组件引用,并可以添加新行为。
- 具体装饰器:实现具体的装饰逻辑。
下次当你需要为一个类添加新功能,但又不想通过继承来创建一大堆子类时,就应该考虑使用装饰器模式。
