一、模式介绍
1.1 定义
装饰模式是一种结构型设计模式,它动态地将责任附加到对象上。装饰模式提供了比继承更有弹性的替代方案,用于扩展对象的功能。
1.2 核心思想
- 不改变原有对象的情况下,动态地给一个对象添加额外的职责
- 通过组合而非继承来扩展功能
- 遵循开闭原则(对扩展开放,对修改关闭)
1.3 适用场景
- 需要在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
- 需要扩展一个类的功能,但使用继承会导致类的数量急剧增加
- 需要为对象添加的功能可能被动态地撤销
- 当不能采用继承的方式对系统进行扩充时
二、结构组成
2.1 主要角色
- Component(抽象构件):定义对象的接口,可以给这些对象动态添加职责
- ConcreteComponent(具体构件):定义具体的对象,即被装饰者
- Decorator(抽象装饰类):继承Component,持有一个Component对象的引用,并定义与Component一致的接口
- ConcreteDecorator(具体装饰类):负责给构件对象添加额外的职责
2.2 UML类图
┌─────────────────┐
│ Component │
├─────────────────┤
│+ operation() │
└─────────────────┘
△
┌───────────┴───────────┐
│ │
┌───────────────┐ ┌─────────────────┐
│ConcreteComponent│ │ Decorator │
├───────────────┤ ├─────────────────┤
│+ operation() │ │- component │
└───────────────┘ │+ operation() │
└─────────────────┘
△
┌───────────┴───────────┐
│ │
┌───────────────┐ ┌───────────────┐
│ConcreteDecoratorA │ │ConcreteDecoratorB │
├───────────────┤ ├───────────────┤
│+ operation() │ │+ operation() │
│+ addedBehavior()│ │+ addedBehavior()│
└───────────────┘ └───────────────┘
三、Java代码示例
3.1 场景描述:咖啡店点餐系统
咖啡店有不同类型的咖啡(如浓缩咖啡、拿铁),并且可以添加各种调料(如牛奶、摩卡、奶泡)。使用装饰模式动态地为咖啡添加调料。
3.2 完整示例代码
java
/**
* 装饰模式示例:咖啡店点餐系统
* 模拟不同类型的咖啡和可添加的调料
*/
// 1. 抽象组件:饮料接口
interface Beverage {
String getDescription();
double getCost();
}
// 2. 具体组件:浓缩咖啡
class Espresso implements Beverage {
@Override
public String getDescription() {
return "浓缩咖啡";
}
@Override
public double getCost() {
return 1.99;
}
}
// 3. 具体组件:拿铁咖啡
class Latte implements Beverage {
@Override
public String getDescription() {
return "拿铁咖啡";
}
@Override
public double getCost() {
return 2.49;
}
}
// 4. 抽象装饰类:调料装饰器
abstract class CondimentDecorator implements Beverage {
protected Beverage beverage;
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
@Override
public abstract String getDescription();
}
// 5. 具体装饰类:牛奶
class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + " + 牛奶";
}
@Override
public double getCost() {
return beverage.getCost() + 0.30;
}
}
// 6. 具体装饰类:摩卡
class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + " + 摩卡";
}
@Override
public double getCost() {
return beverage.getCost() + 0.50;
}
}
// 7. 具体装饰类:奶泡
class Whip extends CondimentDecorator {
public Whip(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + " + 奶泡";
}
@Override
public double getCost() {
return beverage.getCost() + 0.40;
}
}
// 8. 客户端代码
public class DecoratorPatternDemo {
public static void main(String[] args) {
System.out.println("=== 咖啡店点餐系统 ===");
System.out.println();
// 1. 点一杯浓缩咖啡,不加调料
Beverage espresso = new Espresso();
System.out.println("订单1: " + espresso.getDescription());
System.out.println("价格: $" + espresso.getCost());
System.out.println();
// 2. 点一杯拿铁,加双份摩卡和奶泡
Beverage latte = new Latte();
latte = new Mocha(latte); // 第一次装饰:加摩卡
latte = new Mocha(latte); // 第二次装饰:再加一份摩卡
latte = new Whip(latte); // 第三次装饰:加奶泡
System.out.println("订单2: " + latte.getDescription());
System.out.println("价格: $" + latte.getCost());
System.out.println();
// 3. 点一杯浓缩咖啡,加所有调料
Beverage deluxeCoffee = new Espresso();
deluxeCoffee = new Milk(deluxeCoffee);
deluxeCoffee = new Mocha(deluxeCoffee);
deluxeCoffee = new Whip(deluxeCoffee);
System.out.println("订单3: " + deluxeCoffee.getDescription());
System.out.println("价格: $" + deluxeCoffee.getCost());
System.out.println();
// 4. 展示装饰过程的动态性
System.out.println("=== 动态添加调料演示 ===");
Beverage dynamicCoffee = new Latte();
System.out.println("初始: " + dynamicCoffee.getDescription() + ", 价格: $" + dynamicCoffee.getCost());
dynamicCoffee = new Milk(dynamicCoffee);
System.out.println("加牛奶后: " + dynamicCoffee.getDescription() + ", 价格: $" + dynamicCoffee.getCost());
dynamicCoffee = new Mocha(dynamicCoffee);
System.out.println("加摩卡后: " + dynamicCoffee.getDescription() + ", 价格: $" + dynamicCoffee.getCost());
}
}
3.3 输出结果
=== 咖啡店点餐系统 ===
订单1: 浓缩咖啡
价格: $1.99
订单2: 拿铁咖啡 + 摩卡 + 摩卡 + 奶泡
价格: $3.89
订单3: 浓缩咖啡 + 牛奶 + 摩卡 + 奶泡
价格: $3.19
=== 动态添加调料演示 ===
初始: 拿铁咖啡, 价格: $2.49
加牛奶后: 拿铁咖啡 + 牛奶, 价格: $2.79
加摩卡后: 拿铁咖啡 + 牛奶 + 摩卡, 价格: $3.29
四、Java I/O中的装饰模式应用
Java I/O库是装饰模式的经典应用。以下是一个简化的示例:
java
import java.io.*;
/**
* Java I/O中的装饰模式示例
*/
public class JavaIODecoratorExample {
public static void main(String[] args) {
String filePath = "test.txt";
try {
// 创建基础文件输出流
FileOutputStream fos = new FileOutputStream(filePath);
// 使用装饰器增强功能
// BufferedOutputStream 装饰 FileOutputStream,添加缓冲功能
BufferedOutputStream bos = new BufferedOutputStream(fos);
// DataOutputStream 装饰 BufferedOutputStream,添加基本数据类型写入功能
DataOutputStream dos = new DataOutputStream(bos);
// 写入数据
dos.writeUTF("Hello, Decorator Pattern!");
dos.writeInt(123);
dos.writeDouble(3.14);
dos.close();
System.out.println("数据写入完成");
// 读取数据:同样使用装饰模式
FileInputStream fis = new FileInputStream(filePath);
BufferedInputStream bis = new BufferedInputStream(fis);
DataInputStream dis = new DataInputStream(bis);
System.out.println("读取数据:");
System.out.println("字符串: " + dis.readUTF());
System.out.println("整数: " + dis.readInt());
System.out.println("浮点数: " + dis.readDouble());
dis.close();
// 清理测试文件
new File(filePath).delete();
} catch (IOException e) {
e.printStackTrace();
}
}
}
五、装饰模式 vs 继承
5.1 对比表格
| 特性 | 装饰模式 | 继承 |
|---|---|---|
| 扩展方式 | 动态扩展 | 静态扩展 |
| 灵活性 | 高,可运行时动态添加/移除功能 | 低,编译时确定 |
| 类数量 | 装饰类和组件类独立,组合使用 | 需要为每个组合创建子类 |
| 代码复用 | 装饰器可复用 | 父类功能可复用 |
| 设计原则 | 符合开闭原则 | 可能违反开闭原则 |
5.2 使用继承的问题
如果使用继承来实现咖啡店系统,对于3种咖啡和3种调料,需要创建:
- 基础咖啡类:3个
- 加一种调料:3 × 3 = 9个类
- 加两种调料:3 × 3 = 9个类(排列组合)
- 加三种调料:3 × 1 = 3个类
- 总计:24个类(类爆炸问题)
使用装饰模式只需要:3个具体咖啡类 + 3个装饰类 = 6个类
六、装饰模式优缺点
6.1 优点
- 灵活性高:比继承更灵活,可以动态添加或撤销功能
- 避免类爆炸:通过组合少量类,可以创建多种功能组合
- 符合开闭原则:无需修改原有代码即可扩展新功能
- 职责清晰:每个装饰类只关注自己的功能
6.2 缺点
- 增加系统复杂度:会产生许多小对象,增加理解难度
- 调试困难:多层装饰使得调试过程复杂
- 使用限制:对于不支持组合的对象无法使用装饰模式
- 初始化复杂:创建高度装饰的对象需要多层包装
七、实际应用场景
7.1 Java中的应用
- Java I/O流 :
BufferedReader,BufferedWriter等 - Java GUI:Swing组件边框
- Java Collections :
Collections.unmodifiableList()等 - Java Web :Servlet API中的
HttpServletRequestWrapper
7.2 Spring框架中的应用
- Spring Security:过滤器链中的各个安全过滤器
- Spring Web :
HandlerInterceptor的装饰 - Spring AOP:通过代理模式实现类似装饰的功能
7.3 实际业务场景
- 电商促销系统:基础价格 + 各种促销活动(满减、折扣、优惠券)
- 权限控制系统:基础权限 + 各种附加权限
- 日志系统:基础日志 + 各种格式化、过滤、输出方式
- 消息通知系统:基础通知 + 各种渠道(短信、邮件、推送)
八、扩展:透明装饰 vs 半透明装饰
8.1 透明装饰模式
java
// 所有装饰类都继承自同一抽象类/接口
// 客户端可以一致地使用所有对象
Beverage coffee = new Espresso();
coffee = new Milk(coffee); // 透明装饰,Milk也是Beverage类型
coffee.getDescription(); // 可以调用Beverage的所有方法
8.2 半透明装饰模式
java
// 装饰类可能添加新方法,需要客户端知道具体装饰类型
Beverage coffee = new Espresso();
Milk milkCoffee = new Milk(coffee); // 需要明确声明为Milk类型
// milkCoffee.getDescription(); // 可以调用Beverage方法
// milkCoffee.milkSpecificMethod(); // 还可以调用Milk特有的方法
九、总结
装饰模式提供了一种灵活的替代继承的方式,通过组合对象来扩展功能。它特别适用于以下情况:
- 需要动态、透明地给对象添加职责
- 需要避免使用继承导致的类爆炸问题
- 需要遵循开闭原则,在不修改现有代码的情况下扩展功能
在Java中,装饰模式被广泛应用于I/O流处理、GUI组件装饰等场景。正确使用装饰模式可以提高代码的灵活性和可维护性,但也需要注意避免过度使用导致的系统复杂性增加。