装饰者模式详解:给对象动态"穿衣服"的艺术
一、生活场景理解
1.1 穿衣搭配的启示
- 基础服饰:T恤、牛仔裤
- 装饰配件:外套、围巾、帽子
- 自由组合:可以任意叠加不同装饰,不影响基础服饰
1.2 软件映射
- 核心对象:需要被装饰的组件
- 装饰器:提供额外功能的包装类
- 动态组合:运行时灵活添加功能
二、模式原理剖析
2.1 定义
装饰者模式(Decorator Pattern) 属于结构型设计模式,通过将对象放入包含行为的特殊封装类中来动态添加新行为
2.2 核心原则
- 开闭原则:对扩展开放,对修改关闭
- 组合优于继承:通过组合实现功能扩展
2.3 UML类图
+----------------+ +-----------------+
| Component |<|-------| Decorator |
|----------------| |-----------------|
| +operation() | | -component |
+----------------+ +-----------------+
^ ^
| |
+----------------+ +-----------------+
| ConcreteComponent | | ConcreteDecorator|
|----------------| |-----------------|
| +operation() | | +operation() |
+----------------+ +-----------------+
三、完整代码示例:咖啡店订单系统
3.1 基础组件定义
// 饮料抽象类(被装饰的组件)
public abstract class Beverage {
protected String description = "未知饮料";
public String getDescription() {
return description;
}
public abstract double cost();
}
// 具体组件:浓缩咖啡
public class Espresso extends Beverage {
public Espresso() {
description = "浓缩咖啡";
}
public double cost() {
return 1.99;
}
}
3.2 装饰器体系
// 调料装饰器抽象类
public abstract class CondimentDecorator extends Beverage {
protected Beverage beverage; // 持有一个被装饰对象
public abstract String getDescription();
}
// 具体装饰器:牛奶
public class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", 牛奶";
}
public double cost() {
return beverage.cost() + 0.5;
}
}
// 具体装饰器:摩卡
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", 摩卡";
}
public double cost() {
return beverage.cost() + 0.7;
}
}
3.3 客户端使用
public class CoffeeShop {
public static void main(String[] args) {
// 点一杯双倍摩卡加奶的浓缩咖啡
Beverage order = new Espresso();
order = new Mocha(order); // 第一次装饰
order = new Mocha(order); // 第二次装饰
order = new Milk(order); // 第三次装饰
System.out.println(order.getDescription()
+ " 总价:$" + order.cost());
}
}
3.4 运行结果
浓缩咖啡, 摩卡, 摩卡, 牛奶 总价:$3.89
四、模式优劣分析
4.1 核心优势(为什么选择装饰者模式?)
4.1.1 核心优点说明
优点 | 详细说明 | 生活案例类比 |
---|---|---|
灵活扩展 | 无需修改原有代码即可动态添加新功能 | 给手机加装手机壳不影响原有功能 |
避免类爆炸 | 替代多层继承结构(如咖啡×牛奶×糖 的组合只需3个类,继承方式需要6个子类) | 乐高积木式组合代替定制化零件 |
运行时组合 | 装饰顺序和组合方式可在运行时决定 | 穿衣时自由搭配外套、围巾顺序 |
符合开闭原则 | 新增装饰器不影响现有代码,扩展性好 | 新增咖啡配料无需修改咖啡机代码 |
// 扩展示例:新增一个装饰器
public class Sugar extends CondimentDecorator {
public Sugar(Beverage beverage) { this.beverage = beverage; }
public String getDescription() { return beverage.getDescription() + ", 糖"; }
public double cost() { return beverage.cost() + 0.3; }
}
// 使用新装饰器(无需修改原有代码)
Beverage coffee = new Espresso();
coffee = new Milk(coffee);
coffee = new Sugar(coffee); // 新增装饰器
4.2 潜在缺陷(什么时候慎用?)
4.2.1 核心缺点总结
缺点类型 | 问题表现 | 优化建议 |
---|---|---|
系统复杂度上升 | 大量小类增加维护成本 | 合并相似功能的装饰器 |
调试难度高 | 多层装饰导致调用链冗长 | 添加日志追踪装饰流程 |
设计门槛高 | 抽象层设计不当会导致结构混乱 | 提前定义清晰的装饰器规范 |
顺序依赖风险 | 装饰顺序不同可能引发结果差异 | 使用构建者模式控制装饰顺序 |
4.2.2 典型问题场景
// 错误示例:装饰顺序影响结果
Beverage drink = new Coffee();
drink = new Milk(drink); // 先加奶
drink = new Sugar(drink); // 后加糖(最终甜度不同)
// 正确控制方式
Beverage drink = new Coffee();
drink = new SugarDecorator(drink); // 先加糖
drink = new MilkDecorator(drink); // 后加奶
4.2.3 慎用场景
- 简单功能扩展:直接修改原类更高效时
- 严格顺序要求:装饰步骤存在强依赖关系时
- 高频调用场景:性能敏感的核心业务逻辑
- 装饰器数量爆炸:预期会产生大量装饰器类时
五、应用场景指南
5.1 推荐使用场景
- 动态添加/撤销功能:如游戏角色装备系统
- 不可用继承的场景:final类或需要多维度扩展
- 组合多种功能:如不同格式的文件输出
5.2 经典案例
- Java I/O流体系:
InputStream in = new FileInputStream("data.txt");
in = new BufferedInputStream(in); // 装饰缓冲功能
in = new GZIPInputStream(in); // 装饰压缩功能
- **GUI组件:**为可视化组件动态添加边框、滚动条等
- **Web安全:**为请求处理添加加密、验证等装饰层
六、开源框架应用
6.1 Java IO流体系
// 典型装饰链
Reader reader = new FileReader("data.txt");
reader = new BufferedReader(reader); // 添加缓冲功能
reader = new LineNumberReader(reader); // 添加行号统计
// 源码结构:
// FileReader(具体组件)
// BufferedReader(装饰器)
// LineNumberReader(装饰器的装饰器)
6.2 Spring Security
// 安全过滤器链装饰示例
HttpSecurity http = new HttpSecurity();
http.addFilter(new WebAsyncManagerIntegrationFilter())
.addFilter(new SecurityContextPersistenceFilter()) // 装饰安全上下文
.addFilter(new HeaderWriterFilter())
.addFilter(new CsrfFilter()); // 层层装饰安全功能
6.3 MyBatis缓存装饰
// 缓存装饰链示例
Cache cache = new PerpetualCache("default");
cache = new LruCache(cache); // 添加LRU淘汰策略
cache = new ScheduledCache(cache); // 添加定时刷新
cache = new SerializedCache(cache);// 添加序列化功能
七、常见问题解答
Q1:装饰者模式 vs 继承?
关键区别:
- **继承:**编译时静态扩展,所有子类具有相同行为
- **装饰者:**运行时动态组合,可灵活搭配
Q2:如何控制装饰顺序?
- 定义装饰优先级规则
- 使用构建者模式管理装饰顺序
- 通过不同装饰器类控制叠加逻辑
Q3:装饰器会影响性能吗?
- 每层装饰会带来少量性能损耗(通常可忽略)
- 避免创建过长的装饰链(建议不超过5层)
- 对性能敏感场景可结合享元模式优化