Appearance
装饰器模式 (Decorator Pattern)
一、定义
一句话概括:在不改变原有对象结构的情况下,动态地给对象添加额外功能。
官方定义(GoF):Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
装饰器模式的核心思想是:用组合+委托代替继承来扩展功能。它创建一个装饰类来包装原始类,在保持接口一致的前提下,在方法调用前后添加新行为。
二、解决的问题
2.1 什么场景下需要装饰器模式?
- 动态扩展功能:需要在运行时给对象添加功能,而不是编译时通过继承固定。
- 功能组合:需要灵活组合多个功能,如"带缓冲的、带压缩的、带加密的"输出流。
- 避免类爆炸:用继承实现功能组合会导致子类数量指数级增长。
- 不修改原代码:原始类已经稳定,不能或不想修改其源码。
2.2 不用装饰器模式会有什么问题?
假设我们要设计一个咖啡订单系统,需要支持加牛奶、加糖、加摩卡等配料。
继承方案:
Coffee
├── MilkCoffee
├── SugarCoffee
├── MochaCoffee
├── MilkSugarCoffee
├── MilkMochaCoffee
├── SugarMochaCoffee
├── MilkSugarMochaCoffee
...(指数级增长)问题1:类爆炸 —— 3 种配料产生 2^3 = 8 个子类,n 种配料产生 2^n 个子类
问题2:编译时绑定 —— 功能组合在编译时确定,无法运行时动态调整
问题3:违反开闭原则 —— 新增配料需要修改多个类
问题4:强耦合 —— 子类与父类紧耦合,难以独立演化三、结构
3.1 文字描述
装饰器模式包含以下角色:
- Component(抽象构件):定义原始对象和装饰器的公共接口。
- ConcreteComponent(具体构件):被装饰的原始对象。
- Decorator(抽象装饰器):持有一个 Component 引用,实现 Component 接口。
- ConcreteDecorator(具体装饰器):在调用被装饰对象的方法前后添加增强逻辑。
3.2 ASCII 类图
┌─────────────────────────┐
│ <<interface/abstract>> │
│ Component │
├─────────────────────────┤
│ + operation() │
└────────────┬────────────┘
│
┌────────┴──────────────────────┐
│ │
┌───┴──────────────────┐ ┌────────┴──────────────────┐
│ ConcreteComponent │ │ Decorator │
├──────────────────────┤ ├───────────────────────────┤
│ + operation() │ │ # component: Component │
│ 原始实现 │ │ + Decorator(Component) │
└──────────────────────┘ │ + operation() │
│ component.operation() │
└────────────┬──────────────┘
│
┌────────────┴──────────────┐
│ │
┌────────┴──────────┐ ┌───────────┴──────────┐
│ ConcreteDecoratorA│ │ ConcreteDecoratorB │
├───────────────────┤ ├──────────────────────┤
│ + operation() │ │ + operation() │
│ pre-process() │ │ component.op() │
│ component.op() │ │ post-process() │
└───────────────────┘ └──────────────────────┘四、代码实现
4.1 基础实现
场景:咖啡订单系统
java
// ============ Component:抽象构件 ============
interface Beverage {
String getDescription();
double cost();
}
// ============ ConcreteComponent:具体构件 ============
class Espresso implements Beverage {
@Override
public String getDescription() {
return "浓缩咖啡";
}
@Override
public double cost() {
return 15.0;
}
}
class HouseBlend implements Beverage {
@Override
public String getDescription() {
return "综合咖啡";
}
@Override
public double cost() {
return 12.0;
}
}
// ============ Decorator:抽象装饰器 ============
abstract class CondimentDecorator implements Beverage {
protected Beverage beverage; // 被装饰的对象
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
@Override
public abstract String getDescription();
}
// ============ ConcreteDecorator:具体装饰器 ============
class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + " + 牛奶";
}
@Override
public double cost() {
return beverage.cost() + 3.0;
}
}
class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + " + 摩卡";
}
@Override
public double cost() {
return beverage.cost() + 4.0;
}
}
class Whip extends CondimentDecorator {
public Whip(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + " + 奶泡";
}
@Override
public double cost() {
return beverage.cost() + 2.0;
}
}
// ============ 客户端测试 ============
public class DecoratorDemo {
public static void main(String[] args) {
// 浓缩咖啡 + 牛奶 + 摩卡 + 奶泡
Beverage order = new Espresso();
order = new Milk(order);
order = new Mocha(order);
order = new Whip(order);
System.out.println("订单: " + order.getDescription());
System.out.println("总价: ¥" + order.cost());
// 输出:订单: 浓缩咖啡 + 牛奶 + 摩卡 + 奶泡
// 总价: ¥24.0
}
}4.2 进阶实现
4.2.1 Java IO 的装饰器体系(重点)
Java IO 是装饰器模式最经典的工业级应用。整个体系分为"节点流"(被装饰者)和"处理流"(装饰器)。
java
// ============ Java IO 装饰器体系结构 ============
//
// InputStream(抽象 Component)
// ├── FileInputStream(ConcreteComponent —— 节点流)
// ├── ByteArrayInputStream(ConcreteComponent —— 节点流)
// ├── FilterInputStream(抽象 Decorator)
// │ ├── BufferedInputStream(ConcreteDecorator —— 加缓冲)
// │ ├── DataInputStream(ConcreteDecorator —— 读基本类型)
// │ ├── PushbackInputStream(ConcreteDecorator —— 可回退)
// │ └── CheckedInputStream(ConcreteDecorator —— 加校验和)
// └── ObjectInputStream(ConcreteDecorator —— 反序列化)
// ============ 手动模拟 Java IO 装饰器 ============
// 抽象 Component
abstract class MyInputStream {
public abstract int read();
public abstract void close();
}
// 具体 Component(节点流)
class MyFileInputStream extends MyInputStream {
private String filePath;
public MyFileInputStream(String filePath) {
this.filePath = filePath;
System.out.println("打开文件: " + filePath);
}
@Override
public int read() {
System.out.println("从文件读取一个字节");
return 'A'; // 模拟
}
@Override
public void close() {
System.out.println("关闭文件: " + filePath);
}
}
// 抽象 Decorator
abstract class MyFilterInputStream extends MyInputStream {
protected MyInputStream in; // 被装饰的流
public MyFilterInputStream(MyInputStream in) {
this.in = in;
}
@Override
public int read() {
return in.read();
}
@Override
public void close() {
in.close();
}
}
// 具体 Decorator:缓冲流
class MyBufferedInputStream extends MyFilterInputStream {
private byte[] buffer = new byte[8192];
private int pos = 0;
private int count = 0;
public MyBufferedInputStream(MyInputStream in) {
super(in);
}
@Override
public int read() {
if (pos >= count) {
fillBuffer();
}
if (count == -1) return -1;
return buffer[pos++] & 0xFF;
}
private void fillBuffer() {
System.out.println("[缓冲] 填充缓冲区...");
pos = 0;
count = 0;
for (int i = 0; i < buffer.length; i++) {
int b = in.read();
if (b == -1) { count = i == 0 ? -1 : i; return; }
buffer[i] = (byte) b;
count++;
}
}
}
// 具体 Decorator:数据流(读基本类型)
class MyDataInputStream extends MyFilterInputStream {
public MyDataInputStream(MyInputStream in) {
super(in);
}
public byte readByte() {
return (byte) read();
}
public int readInt() {
int ch1 = read();
int ch2 = read();
int ch3 = read();
int ch4 = read();
return (ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4;
}
}
// ============ 测试:多层装饰 ============
public class IODecoratorDemo {
public static void main(String[] args) {
// 文件输入 → 缓冲 → 数据读取
MyInputStream raw = new MyFileInputStream("data.bin");
MyBufferedInputStream buffered = new MyBufferedInputStream(raw);
MyDataInputStream data = new MyDataInputStream(buffered);
int value = data.readInt();
System.out.println("读取的整数: " + value);
data.close();
}
}4.2.2 实际 Java IO 使用示例
java
// 装饰器链:从一个文件读取,经过缓冲,再按行读取
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("test.txt"), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
// 装饰器链分析:
// FileInputStream → 节点流(读取原始字节)
// InputStreamReader → 装饰器(字节→字符转换)
// BufferedReader → 装饰器(加缓冲,提供 readLine)
// 每一层都增强了功能,但接口兼容(都是 Reader/InputStream 体系)4.2.3 半透明装饰器
当装饰器需要提供额外方法时,称为"半透明装饰器"。
java
// 增强装饰器:提供额外方法
class CachedInputStream extends MyFilterInputStream {
private List<Byte> cache = new ArrayList<>();
private int readPos = 0;
public CachedInputStream(MyInputStream in) {
super(in);
}
@Override
public int read() {
int b = super.read();
if (b != -1) cache.add((byte) b);
return b;
}
// 额外方法:重置读取位置
public void reset() {
readPos = 0;
}
// 额外方法:获取已读取的所有字节
public byte[] getCachedData() {
byte[] result = new byte[cache.size()];
for (int i = 0; i < cache.size(); i++) {
result[i] = cache.get(i);
}
return result;
}
}4.3 生产级实现
Spring Boot 请求日志装饰器
java
// ============ HttpServletRequest 装饰器 ============
// 问题:HttpServletRequest 的 InputStream 只能读取一次
// 解决:装饰器模式包装,缓存请求体
class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private byte[] cachedBody;
public CachedBodyHttpServletRequest(HttpServletRequest request) {
super(request);
try {
InputStream inputStream = request.getInputStream();
this.cachedBody = inputStream.readAllBytes();
} catch (IOException e) {
this.cachedBody = new byte[0];
}
}
@Override
public ServletInputStream getInputStream() {
return new CachedBodyServletInputStream(this.cachedBody);
}
@Override
public BufferedReader getReader() {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
return new BufferedReader(new InputStreamReader(byteArrayInputStream));
}
public String getRequestBody() {
return new String(cachedBody, StandardCharsets.UTF_8);
}
}
class CachedBodyServletInputStream extends ServletInputStream {
private final ByteArrayInputStream inputStream;
public CachedBodyServletInputStream(byte[] cachedBody) {
this.inputStream = new ByteArrayInputStream(cachedBody);
}
@Override public boolean isFinished() { return inputStream.available() == 0; }
@Override public boolean isReady() { return true; }
@Override public void setReadListener(ReadListener listener) {}
@Override public int read() { return inputStream.read(); }
}
// ============ 请求日志过滤器 ============
@Component
class RequestLoggingFilter implements Filter {
private static final Logger log = LoggerFactory.getLogger(RequestLoggingFilter.class);
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
CachedBodyHttpServletRequest cachedRequest =
new CachedBodyHttpServletRequest((HttpServletRequest) request);
log.info("请求 URI: {}", cachedRequest.getRequestURI());
log.info("请求方法: {}", cachedRequest.getMethod());
log.info("请求体: {}", cachedRequest.getRequestBody());
chain.doFilter(cachedRequest, response);
}
}
// ============ Spring Cache 装饰器 ============
// 为 Service 添加缓存装饰
@Service
class UserService {
public User getUserById(Long id) {
// 模拟耗时查询
sleep(100);
return new User(id, "张三");
}
private void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) {}
}
}
// 缓存装饰器
@Component
class CachedUserService extends UserService {
private final UserService delegate;
private final Map<Long, User> cache = new ConcurrentHashMap<>();
public CachedUserService(UserService delegate) {
this.delegate = delegate;
}
@Override
public User getUserById(Long id) {
return cache.computeIfAbsent(id, delegate::getUserById);
}
}五、优缺点
优点
| 优点 | 说明 |
|---|---|
| 灵活扩展 | 比继承更灵活,可以运行时动态组合功能 |
| 避免类爆炸 | 用组合代替继承,n 种功能只需 n 个装饰器类 |
| 符合开闭原则 | 对扩展开放,对修改关闭 |
| 单一职责 | 每个装饰器只关注一个功能,职责清晰 |
| 可组合 | 装饰器可以任意组合,排列顺序影响最终行为 |
缺点
| 缺点 | 说明 |
|---|---|
| 小对象多 | 装饰链会产生大量小对象,增加内存开销 |
| 调试困难 | 多层装饰链使得调用栈很深,排查问题复杂 |
| 初始化复杂 | 客户端需要手动组装装饰链,代码冗长 |
| 类型丢失 | 装饰后原始类型信息可能丢失(半透明装饰器例外) |
六、适用场景
- 动态添加功能:需要在运行时给对象添加功能,而不影响其他对象。
- 功能组合:需要灵活组合多个功能,如数据流处理(缓冲+压缩+加密)。
- 不可继承的场景:类被
final修饰,无法通过继承扩展。 - 撤销功能:装饰器可以动态添加和移除,适合实现撤销/重做。
- 权限控制:给不同对象动态添加权限检查。
- 日志/监控:给服务方法添加日志、性能监控等功能。
七、JDK / Spring 框架中的实际应用
7.1 Java IO 流(最经典)
java
// 字节流装饰器体系
InputStream is = new FileInputStream("file.txt");
is = new BufferedInputStream(is); // 加缓冲
is = new DataInputStream(is); // 读基本类型
is = new CheckedInputStream(is, new CRC32()); // 加校验
// 字符流装饰器体系
Reader reader = new FileReader("file.txt");
reader = new BufferedReader(reader); // 加缓冲
reader = new LineNumberReader(reader); // 加行号
// 对象流
ObjectInputStream ois = new ObjectInputStream(
new BufferedInputStream(
new FileInputStream("data.obj")));7.2 Collections 工具类
java
// 同步装饰器
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
// 不可变装饰器
List<String> unmodifiableList = Collections.unmodifiableList(new ArrayList<>());
// 类型安全装饰器
List<String> checkedList = Collections.checkedList(new ArrayList<>(), String.class);7.3 Spring 中的装饰器
java
// 1. HttpServletRequestWrapper / HttpServletResponseWrapper
// Servlet API 的请求/响应装饰器
// 2. BeanDefinitionDecorator
// Spring 的 Bean 定义装饰器
// 3. TransactionAwareCacheDecorator
// Spring Cache 的事务感知装饰器
// 4. ServerHttpRequestDecorator / ServerHttpResponseDecorator
// Spring WebFlux 的请求/响应装饰器八、与其他模式的关系
| 模式 | 关系 |
|---|---|
| 适配器模式 | 装饰器不改变接口,增强功能;适配器改变接口,不改变功能。装饰器支持递归组合,适配器通常不递归。 |
| 代理模式 | 代理控制对对象的访问,装饰器增强对象的功能。结构上几乎相同,但意图不同。 |
| 组合模式 | 装饰器只有一个子节点,组合有多个子节点。装饰器补充功能,组合统一处理整体和部分。 |
| 策略模式 | 装饰器改变对象的"外表"(包装),策略模式改变对象的"内心"(算法)。 |
| 责任链模式 | 两者结构相似(链式调用),但责任链中每个处理器可以终止请求,装饰器不会终止。 |
九、面试常见问题
Q1:装饰器模式和代理模式有什么区别?
答:
- 意图不同:装饰器强调增强功能,代理强调控制访问。
- 关注点不同:装饰器动态添加职责,代理控制对对象的访问(如延迟加载、权限检查)。
- 实例化不同:装饰器通常由客户端显式组装,代理通常由框架自动生成(动态代理)。
- 典型例子:
BufferedInputStream是装饰器(增强功能),Spring AOP 是代理(控制访问)。
Q2:Java IO 中为什么使用装饰器模式而不是继承?
答:
- 如果用继承,每种功能组合都要一个子类(如
BufferedFileInputStream、DataFileInputStream、BufferedDataFileInputStream...),导致类爆炸。 - 装饰器模式用组合方式,n 种功能只需 n 个装饰器类,可以任意组合。
- 装饰器可以在运行时动态组合,而继承在编译时固定。
Q3:装饰器模式中的"半透明"是什么意思?
答:
- 透明装饰器:完全实现 Component 接口,不提供额外方法,客户端完全感知不到装饰器的存在。
- 半透明装饰器:在实现 Component 接口之外,还提供额外方法(如
BufferedReader.readLine())。客户端需要知道具体装饰器类型才能使用额外方法,失去了一部分透明性。
Q4:装饰器模式中,装饰顺序重要吗?
答:非常重要。装饰顺序不同,行为可能完全不同。例如:
java
// 先缓冲再加密:数据先进入缓冲区,再加密写入
OutputStream out = new EncryptOutputStream(new BufferedOutputStream(new FileOutputStream("a.dat")));
// 先加密再缓冲:数据先加密,再进入缓冲区
OutputStream out = new BufferedOutputStream(new EncryptOutputStream(new FileOutputStream("a.dat")));Q5:如何在 Spring Boot 中使用装饰器模式?
答:常见场景:
- 请求体缓存:使用
HttpServletRequestWrapper装饰请求,缓存请求体以便多次读取。 - 响应体包装:使用
HttpServletResponseWrapper装饰响应,捕获响应内容进行日志记录。 - 缓存装饰:为 Service 创建缓存装饰器,在调用原方法前后添加缓存逻辑。
- Spring Cache 的
TransactionAwareCacheDecorator就是装饰器模式的应用。
