1. 装饰者模式概述
装饰者模式(Decorator Pattern)是一种结构型设计模式,它允许向现有对象动态添加新功能而不改变其结构。这种模式创建了一个装饰类,用来包装原有类,并在保持类方法签名完整性的前提下,提供了额外的功能。
我第一次接触装饰者模式是在开发一个电商平台的促销系统时。当时需要为商品价格计算叠加多种优惠(会员折扣、满减、优惠券等),如果使用继承会导致类爆炸(比如会员满减商品、新客优惠商品等组合),而装饰者模式完美解决了这个问题。
装饰者模式的核心在于:
- 动态扩展功能:运行时添加或删除功能
- 避免继承爆炸:通过组合替代多重继承
- 保持开闭原则:对扩展开放,对修改关闭
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 模式结构与组件解析
2.1 标准UML类图结构
code复制[Component] <-|-- [ConcreteComponent]
[Component] <-|-- [Decorator]
[Decorator] o-- [Component]
[Decorator] <-|-- [ConcreteDecoratorA]
[Decorator] <-|-- [ConcreteDecoratorB]
2.2 核心角色说明
-
Component(抽象组件)
- 定义对象的接口
- 可以是接口或抽象类
- 示例代码:
java复制public interface Beverage { String getDescription(); double cost(); }
-
ConcreteComponent(具体组件)
- 实现Component接口的基本实现
- 示例:
java复制public class Espresso implements Beverage { public String getDescription() { return "Espresso"; } public double cost() { return 1.99; } }
-
Decorator(抽象装饰器)
- 持有Component引用
- 实现Component接口
- 关键代码:
java复制public abstract class CondimentDecorator implements Beverage { protected Beverage beverage; public CondimentDecorator(Beverage beverage) { this.beverage = beverage; } }
-
ConcreteDecorator(具体装饰器)
- 添加具体功能的装饰器
- 示例:
java复制public class Mocha extends CondimentDecorator { public Mocha(Beverage beverage) { super(beverage); } public String getDescription() { return beverage.getDescription() + ", Mocha"; } public double cost() { return 0.20 + beverage.cost(); } }
3. 模式实现与代码示例
3.1 Java实现案例
完整示例演示咖啡加调料场景:
java复制// 客户端代码
public class StarbuzzCoffee {
public static void main(String[] args) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription()
+ " $" + beverage.cost());
Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription()
+ " $" + beverage2.cost());
}
}
3.2 实现要点分析
-
装饰器构造方式
- 通过构造函数注入被装饰对象
- 装饰器链式调用:
java复制new Mocha(new Whip(new DarkRoast()))
-
方法调用流程
- 装饰器的cost()方法会先调用被装饰对象的cost()
- 然后加上自己的价格
- 形成递归调用链
-
与继承的区别
- 继承是编译时静态扩展
- 装饰是运行时动态组合
4. 模式应用场景分析
4.1 典型适用场景
-
动态扩展功能
- 需要在不影响其他对象的情况下动态添加职责
- 示例:Java I/O流体系
java复制new BufferedInputStream(new FileInputStream("test.txt"))
-
撤销功能场景
- 通过移除装饰器对象可以撤销功能
- 示例:游戏角色装备系统
-
避免子类膨胀
- 当扩展功能组合爆炸时
- 示例:UI组件边框/滚动条组合
4.2 实际项目案例
电商促销系统实现
java复制// 基础价格组件
public interface Price {
double calculate();
}
// 商品基础价格
public class BasePrice implements Price {
private double amount;
public BasePrice(double amount) {
this.amount = amount;
}
public double calculate() {
return amount;
}
}
// 折扣装饰器
public abstract class DiscountDecorator implements Price {
protected Price price;
public DiscountDecorator(Price price) {
this.price = price;
}
}
// 具体折扣类型
public class MemberDiscount extends DiscountDecorator {
private double rate;
public MemberDiscount(Price price, double rate) {
super(price);
this.rate = rate;
}
public double calculate() {
return price.calculate() * (1 - rate);
}
}
// 使用示例
Price finalPrice = new CouponDiscount(
new MemberDiscount(
new BasePrice(100), 0.1), 20);
5. 模式优缺点与注意事项
5.1 优势分析
-
比继承更灵活
- 动态添加/删除功能
- 避免继承体系的臃肿
-
符合开闭原则
- 扩展新功能无需修改现有代码
- 只需增加新的装饰器类
-
可以嵌套使用
- 多个装饰器可以组合使用
- 形成功能叠加效果
5.2 潜在问题
-
设计复杂度
- 大量小类增加系统复杂度
- 需要区分装饰器和组件
-
调试困难
- 多层装饰导致调用链较长
- 异常排查难度增加
-
过度使用问题
- 不是所有场景都适合
- 简单扩展可能更适合继承
5.3 使用建议
-
适用场景判断
- 当需要动态、透明地添加功能时
- 当不能采用继承或继承不现实时
-
设计注意事项
- 保持组件接口简洁
- 装饰器应透明化(不改变接口)
- 避免循环依赖
-
性能考量
- 多层装饰可能影响性能
- 对性能敏感场景需评估
6. 与其他模式的关系
6.1 与适配器模式对比
| 维度 | 装饰者模式 | 适配器模式 |
|---|---|---|
| 目的 | 增强功能 | 转换接口 |
| 包装对象 | 同类型对象 | 不同类型对象 |
| 调用方式 | 递归调用 | 直接转发 |
| 设计阶段 | 预先设计 | 后期补救 |
6.2 与策略模式配合
装饰者模式可以与策略模式结合使用:
- 装饰器持有策略对象
- 通过装饰器动态切换算法
- 示例代码:
java复制public class StrategyDecorator implements Component { private Component component; private Strategy strategy; public void operation() { strategy.execute(); component.operation(); } }
7. 经典框架中的应用
7.1 Java I/O流体系
Java的IO包是装饰者模式的经典实现:
- InputStream/OutputStream是抽象组件
- FileInputStream等是具体组件
- BufferedInputStream等是装饰器
java复制// 典型用法
InputStream in = new BufferedInputStream(
new GZIPInputStream(
new FileInputStream("test.gz")));
7.2 Spring框架中的应用
-
HttpInputMessage装饰
- 对HTTP请求消息的多次包装
- 示例:加密消息体处理
-
BeanDefinition装饰
- 动态增强Bean定义
- 示例:ScopedProxyFactoryBean
-
AOP中的装饰
- 通过拦截器增强方法
- 本质是动态装饰
8. 最佳实践与经验分享
8.1 开发中的实用技巧
-
简化装饰器创建
- 使用静态工厂方法:
java复制public class Decorators { public static Price withMemberDiscount(Price price) { return new MemberDiscount(price, 0.1); } }
- 使用静态工厂方法:
-
装饰器链构建
- 使用Builder模式:
java复制new PriceBuilder(basePrice) .withMemberDiscount(0.1) .withCoupon(20) .build();
- 使用Builder模式:
-
日志调试技巧
- 在装饰器中添加日志:
java复制public double calculate() { double result = price.calculate() * (1 - rate); logger.debug("Applied {} discount, result: {}", rate, result); return result; }
- 在装饰器中添加日志:
8.2 常见问题解决方案
-
装饰顺序问题
- 明确装饰器应用顺序规则
- 示例:先应用百分比折扣再减固定金额
-
循环引用检测
- 防止装饰器相互装饰
- 可在装饰器构造函数中添加检查
-
性能优化
- 缓存装饰结果
- 避免重复计算:
java复制private Double cachedResult; public double calculate() { if (cachedResult == null) { cachedResult = price.calculate() * (1 - rate); } return cachedResult; }
9. 扩展与变体
9.1 透明装饰与半透明装饰
-
透明装饰
- 完全遵循组件接口
- 客户端无需知道装饰存在
-
半透明装饰
- 提供额外方法
- 客户端需要知道装饰类型
- 示例:
java复制public class ScrollableWindow extends Window { public void scrollTo(int x, int y) { // 额外方法 } }
9.2 静态装饰(编译时)
通过注解处理器实现编译时装饰:
java复制@Decorator
public class LoggingDecorator {
@Delegate
private final Service delegate;
public void execute() {
System.out.println("Before execution");
delegate.execute();
System.out.println("After execution");
}
}
10. 实战:实现一个可扩展的文本处理器
10.1 需求分析
构建一个文本处理系统,支持动态添加以下功能:
- 拼写检查
- 语法检查
- 敏感词过滤
- 格式转换
10.2 类设计
java复制// 抽象组件
public interface TextProcessor {
String process(String text);
}
// 具体组件
public class PlainTextProcessor implements TextProcessor {
public String process(String text) {
return text;
}
}
// 抽象装饰器
public abstract class TextProcessorDecorator implements TextProcessor {
protected TextProcessor processor;
public TextProcessorDecorator(TextProcessor processor) {
this.processor = processor;
}
}
// 具体装饰器
public class SpellCheckDecorator extends TextProcessorDecorator {
public SpellCheckDecorator(TextProcessor processor) {
super(processor);
}
public String process(String text) {
String processed = processor.process(text);
// 实现拼写检查逻辑
return checkSpelling(processed);
}
private String checkSpelling(String text) {
// 实际拼写检查实现
return text;
}
}
10.3 使用示例
java复制TextProcessor processor = new SensitiveFilterDecorator(
new GrammarCheckDecorator(
new SpellCheckDecorator(
new PlainTextProcessor())));
String result = processor.process("原始文本内容");
在实际项目中,装饰者模式的价值在于它提供了一种比继承更灵活的功能扩展方式。我曾在多个项目中应用这种模式,特别是在需要动态组合功能的场景下,它显著减少了代码重复和类膨胀问题。一个实用的建议是:当发现自己在创建大量仅在某些小方面有区别的子类时,就应该考虑是否可以使用装饰者模式来重构。
