一、从咖啡加料说起:什么是装饰器模式?
假设您走进咖啡馆点单:
- 基础款:美式咖啡(15元)
- 加料需求:加牛奶(+3元)、加焦糖(+5元)、加奶油(+4元)
如果为每种组合创建子类,将出现类爆炸:
装饰器模式(Decorator Pattern)
应运而生,通过动态包装对象的方式,实现功能的灵活扩展。
二、装饰器模式的核心结构
2.1 UML类图解析
2.2 关键角色说明
角色 | 职责 |
---|---|
Component | 定义基础功能接口 |
ConcreteComponent | 实现基础功能的具体组件 |
Decorator | 持有组件引用并实现相同接口 |
ConcreteDecorator | 具体装饰器,添加额外功能 |
三、装饰器模式实战:咖啡加料系统
3.1 基础组件定义
// 组件接口
public interface Coffee {
String getDescription();
double cost();
}
// 基础咖啡实现
public class American implements Coffee {
@Override
public String getDescription() {
return "美式咖啡";
}
@Override
public double cost() {
return 15.0;
}
}
3.2 抽象装饰器
public abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
@Override
public double cost() {
return decoratedCoffee.cost();
}
}
3.3 具体装饰器实现
// 牛奶装饰器
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + " + 牛奶";
}
@Override
public double cost() {
return super.cost() + 3.0;
}
}
// 焦糖装饰器
public class CaramelDecorator extends CoffeeDecorator {
public CaramelDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + " + 焦糖";
}
@Override
public double cost() {
return super.cost() + 5.0;
}
}
3.4 客户端使用示例
public class CoffeeShop {
public static void main(String[] args) {
Coffee order1 = new American();
System.out.println(order1.getDescription() + " 价格:" + order1.cost());
Coffee order2 = new MilkDecorator(new American());
System.out.println(order2.getDescription() + " 价格:" + order2.cost());
Coffee order3 = new CaramelDecorator(new MilkDecorator(new American()));
System.out.println(order3.getDescription() + " 价格:" + order3.cost());
}
}
/* 输出:
美式咖啡 价格:15.0
美式咖啡 + 牛奶 价格:18.0
美式咖啡 + 牛奶 + 焦糖 价格:23.0
*/
四、装饰器模式的优势分析
4.1 与传统继承对比
维度 | 继承方案 | 装饰器模式 |
---|---|---|
扩展方式 | 静态编译期扩展 | 动态运行时扩展 |
类数量 | 组合爆炸(O(2^n)) | 线性增长(O(n)) |
功能组合 | 固定组合 | 任意组合 |
维护成本 | 修改父类影响所有子类 | 独立扩展互不影响 |
4.2 核心优势总结
- 开闭原则:无需修改已有代码即可扩展功能
- 灵活组合:可以任意叠加装饰器
- 避免臃肿:将大类的功能分解为小装饰器
- 运行时扩展:动态增减对象功能
五、装饰器模式典型应用场景
5.1 Java IO流体系
// 多层装饰示例
InputStream input = new BufferedInputStream(
new GZIPInputStream(
new FileInputStream("data.gz")));
5.2 GUI组件装饰
JComponent textArea = new JScrollPane(
new BorderDecorator(
new ShadowDecorator(
new BasicTextArea())));
5.3 Web中间件开发
HttpServletRequest wrappedRequest = new LoggingRequestWrapper(
new CachingRequestWrapper(
originalRequest));
六、最佳实践与注意事项
6.1 实现建议
- 保持接口一致:装饰器必须实现组件接口
- 控制装饰层数:建议不超过5层装饰
- 明确文档说明:标注可组合的装饰器类型
- 性能监控:关注多层装饰的性能影响
6.2 常见误区
- 滥用装饰器:简单扩展直接使用继承
- 循环装饰:装饰器之间形成循环依赖
- 状态管理:装饰器修改组件内部状态
七、与相关模式对比
模式 | 核心区别 |
---|---|
适配器模式 | 改变接口,解决兼容性问题 |
代理模式 | 控制访问,可能不透明 |
组合模式 | 处理整体-部分层次结构 |
策略模式 | 替换算法实现 |
八、总结:何时选择装饰器模式?
适用场景判断:
- ✅ 需要动态/透明地扩展对象功能
- ✅ 不宜使用子类扩展(组合爆炸)
- ✅ 需要撤销或修改已添加的功能
不适用场景:
- ❌ 组件接口频繁变化
- ❌ 需要完全透明的对象(装饰器会改变类型)
- ❌ 简单的一次性扩展需求
扩展阅读:
- 《设计模式:可复用面向对象软件的基础》第四章
- Java I/O源码分析
- Spring Web中的装饰器应用
掌握装饰器模式,让您的代码像乐高积木一样灵活组合! 🧱