装饰器模式是"组合优于继承"的最佳示范,Java 的 IO 流、Spring 里到处是它的影子。这篇把它讲清楚:为什么不用继承、装饰器怎么"层层包装"、以及它和代理模式这对"孪生兄弟"到底差在哪。
一、解决什么问题
装饰器模式解决的是**"给对象动态加功能,又不改原类、不搞继承爆炸"**的问题。
假设你要卖咖啡,有基础咖啡,可以加牛奶、加糖、加奶泡。如果用继承:
Coffee
├─ MilkCoffee
├─ SugarCoffee
├─ MilkSugarCoffee ← 每种组合都要一个子类
├─ MilkFoamCoffee
└─ ...(组合爆炸)每多一种配料,子类就成倍增长,而且运行期没法动态组合(不能"先加奶再加糖"这种任意顺序)。
装饰器模式用组合 + 包装解决:把"加料"做成一个个装饰器,像洋葱一样一层层包上去。
二、装饰器模式怎么落地
java
// ① 抽象组件
public interface Coffee {
double cost();
String desc();
}
// ② 具体组件:基础咖啡
public class SimpleCoffee implements Coffee {
public double cost() { return 10; }
public String desc() { return "咖啡"; }
}
// ③ 装饰器基类:实现同一接口 + 持有被装饰对象
public abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) { this.coffee = coffee; }
}
// ④ 具体装饰器:加牛奶、加糖
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) { super(coffee); }
public double cost() { return coffee.cost() + 2; } // 在原来基础上加钱
public String desc() { return coffee.desc() + "+牛奶"; } // 在原来描述上追加
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) { super(coffee); }
public double cost() { return coffee.cost() + 1; }
public String desc() { return coffee.desc() + "+糖"; }
}
// 使用:层层包装,任意顺序组合
Coffee coffee = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
System.out.println(coffee.desc()); // 咖啡+牛奶+糖
System.out.println(coffee.cost()); // 13核心三要素:
- 装饰器和被装饰对象实现同一个接口(所以能层层嵌套)
- 装饰器持有一个被装饰对象的引用(组合)
- 在调用被装饰对象方法前后,附加新功能(加钱、追加描述)
三、实际应用
Java IO 流是装饰器模式的教科书:
java
// FileInputStream 是最基础的"具体组件"
// BufferedInputStream 是装饰器,加缓冲
// DataInputStream 是装饰器,加类型化读取
DataInputStream in = new DataInputStream(
new BufferedInputStream(
new FileInputStream("data.bin")
)
);每一层 new 都是套一层装饰器,各管各的功能(缓冲、类型化读取),组合出不同能力。这就是为什么 IO 流能"套娃"的原因。
四、装饰器 vs 代理(易混,重点)
两者代码结构几乎一模一样(都持有对象 + 实现同接口),但目的不同:
| 维度 | 装饰器模式 | 代理模式 |
|---|---|---|
| 目的 | 增强功能(加新能力) | 控制访问(延迟加载/权限/远程) |
| 关注 | 让对象"能做更多事" | 让对象"能不能被访问" |
| 创建 | 通常由使用方层层包装 | 通常由框架自动生成 |
| 典型 | IO 流加缓冲、加类型 | Spring AOP、延迟加载 |
一句话:装饰器是"锦上添花",代理是"门前守卫"。代理模式的详解见《代理模式》(文章整理中)。
小结
- 装饰器解决"动态加功能 + 继承爆炸",核心是"组合优于继承"
- 三要素:同接口 + 持有被装饰对象 + 前后附加功能
- 典型:Java IO 流套娃、Collections 装饰器
- 与代理区别:装饰器"增强",代理"控制访问"
下一步可以看《代理模式》——它和装饰器长得几乎一样,但目的是"控制访问",Spring AOP 就是动态代理的集大成者。
