欢迎你来读这篇博客,这篇博客主要是关于装饰器模式。
其中包括装饰器模式的核心思想、适用场景、优缺点、与代理模式和适配器模式的区别,以及 Java 后端开发中消息发送器增强的完整案例。
序言
在软件开发中,我们经常会遇到这样的需求:
已经有一个类可以完成核心功能,但现在想在不修改它的前提下,额外增加一些能力。
比如有一个短信发送器:
1
| smsSender.send("13800000000", "验证码 9527");
|
现在希望在发送短信前后增加一些功能:
- 打印日志;
- 统计耗时;
- 失败重试;
- 限流保护;
- 监控埋点;
- 异常告警;
- TraceId 追踪。
最直接的方式是修改原来的 SmsSender。
但是这样会带来几个问题:
- 原类越来越臃肿;
- 不同增强逻辑耦合在一起;
- 违反开闭原则;
- 不同组合方式难以复用;
- 想给邮件发送器、企业微信发送器也加同样能力时,会重复写代码。
这时就可以使用装饰器模式。
装饰器模式的核心思想是:
在不改变原对象结构的情况下,动态地给对象增加额外功能。
它就像给一个对象一层一层“穿装备”。
原对象负责核心能力。
装饰器负责增强能力。
对象本身不用改,但能力可以一层层叠上去。
这就像 Java 后端里的“加日志、加重试、加限流、加监控”,别一股脑塞进业务类,业务类不是圣诞树,别什么都往上挂。
正文
chapter 1:什么是装饰器模式
装饰器模式,英文是 Decorator Pattern,属于结构型设计模式。
它的定义是:
动态地给一个对象添加一些额外职责。就扩展功能而言,装饰器模式比生成子类更加灵活。
简单说:
装饰器模式通过包装对象来增强对象功能,而不是通过修改原类或大量继承子类来扩展功能。
装饰器模式通常包含四个角色:
- Component 抽象组件:定义被装饰对象和装饰器的共同接口。
- ConcreteComponent 具体组件:真正完成核心功能的对象。
- Decorator 抽象装饰器:持有一个 Component,并实现 Component 接口。
- ConcreteDecorator 具体装饰器:在调用原对象前后增加额外功能。
结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Client │ ▼ Component ▲ │ ├── ConcreteComponent │ └── Decorator │ └── Component ▲ │ ConcreteDecorator
|
最关键的一点是:
装饰器和被装饰对象实现同一个接口。
所以装饰后的对象仍然可以当作原接口使用。
chapter 2:为什么需要装饰器模式
假设我们有一个消息发送接口。
1 2 3 4
| public interface MessageSender {
void send(String receiver, String content); }
|
短信发送器实现:
1 2 3 4 5 6 7
| public class SmsMessageSender implements MessageSender {
@Override public void send(String receiver, String content) { System.out.println("发送短信给 " + receiver + ",内容:" + content); } }
|
现在要增加日志功能。
一种方式是直接改 SmsMessageSender:
1 2 3 4 5 6 7 8 9 10 11
| public class SmsMessageSender implements MessageSender {
@Override public void send(String receiver, String content) { System.out.println("开始发送短信");
System.out.println("发送短信给 " + receiver + ",内容:" + content);
System.out.println("短信发送完成"); } }
|
然后又要增加重试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class SmsMessageSender implements MessageSender {
@Override public void send(String receiver, String content) { System.out.println("开始发送短信");
try { System.out.println("发送短信给 " + receiver + ",内容:" + content); } catch (Exception e) { System.out.println("发送失败,开始重试"); }
System.out.println("短信发送完成"); } }
|
再加限流、监控、TraceId、异常告警,类就会越来越乱。
另一个方案是继承。
1 2 3 4 5 6 7 8
| SmsMessageSender ├── LogSmsMessageSender ├── RetrySmsMessageSender ├── RateLimitSmsMessageSender ├── LogRetrySmsMessageSender ├── LogRateLimitSmsMessageSender ├── RetryRateLimitSmsMessageSender └── LogRetryRateLimitSmsMessageSender
|
这会导致类爆炸。
如果增强能力有 n 个,组合数量可能接近:
装饰器模式的做法是:
每一种增强能力单独做成一个装饰器,需要什么能力,就一层一层包起来。
例如:
1 2 3 4 5
| MessageSender sender = new LoggingMessageSenderDecorator( new RetryMessageSenderDecorator( new SmsMessageSender() ) );
|
这样核心类不用改,增强能力也可以自由组合。
chapter 3:装饰器模式的核心思想
装饰器模式的核心可以概括为三句话:
- 被装饰对象和装饰器实现同一个接口;
- 装饰器内部持有一个同接口对象;
- 装饰器在调用原对象前后添加增强逻辑。
抽象装饰器通常长这样:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public abstract class MessageSenderDecorator implements MessageSender {
protected final MessageSender delegate;
protected MessageSenderDecorator(MessageSender delegate) { this.delegate = delegate; }
@Override public void send(String receiver, String content) { delegate.send(receiver, content); } }
|
具体装饰器长这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class LoggingMessageSenderDecorator extends MessageSenderDecorator {
public LoggingMessageSenderDecorator(MessageSender delegate) { super(delegate); }
@Override public void send(String receiver, String content) { System.out.println("发送前记录日志");
delegate.send(receiver, content);
System.out.println("发送后记录日志"); } }
|
装饰器不是替换原对象,而是增强原对象。
chapter 4:不用装饰器模式会怎样
如果不用装饰器模式,常见写法有两种。
1. 直接修改原类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class SmsMessageSender implements MessageSender {
@Override public void send(String receiver, String content) { checkRateLimit(receiver);
long start = System.currentTimeMillis();
try { doSend(receiver, content); recordMetrics(System.currentTimeMillis() - start); } catch (Exception e) { retry(receiver, content); sendAlarm(e); }
writeLog(receiver, content); } }
|
这种写法的问题是:
- 发送逻辑和增强逻辑混在一起;
- 类职责变重;
- 修改风险增大;
- 不同增强逻辑难以复用;
- 不同渠道也要重复写类似增强代码。
2. 使用大量继承
1 2 3 4 5 6 7 8 9 10
| SmsSender EmailSender
LogSmsSender RetrySmsSender LogRetrySmsSender
LogEmailSender RetryEmailSender LogRetryEmailSender
|
这种写法的问题是:
- 类数量爆炸;
- 组合不灵活;
- 新增一个增强能力,要扩展很多类;
- 新增一个发送渠道,也要扩展很多类。
装饰器模式解决的是:
不通过修改原类,也不通过继承爆炸,而是通过对象包装来动态叠加能力。
chapter 5:案例背景:消息发送器增强
下面用一个 Java 后端开发中非常常见的案例:消息发送器。
系统中有一个统一消息发送接口:
核心发送器有:
现在需要给发送器动态叠加以下能力:
- 日志记录;
- 失败重试;
- 限流;
- 耗时统计。
要求:
- 不修改原始发送器;
- 不为每种组合创建子类;
- 增强能力可以自由组合;
- 客户端仍然只依赖
MessageSender 接口。
这个场景非常适合装饰器模式。
chapter 6:定义抽象组件 MessageSender
先定义统一接口。
1 2 3 4
| public interface MessageSender {
void send(String receiver, String content); }
|
这是装饰器模式中的 Component。
不管是原始发送器,还是装饰器,都实现这个接口。
chapter 7:定义具体组件
1. 短信发送器
1 2 3 4 5 6 7
| public class SmsMessageSender implements MessageSender {
@Override public void send(String receiver, String content) { System.out.println("发送短信给 " + receiver + ",内容:" + content); } }
|
2. 邮件发送器
1 2 3 4 5 6 7
| public class EmailMessageSender implements MessageSender {
@Override public void send(String receiver, String content) { System.out.println("发送邮件给 " + receiver + ",内容:" + content); } }
|
这两个类只负责核心发送逻辑。
它们不关心日志、重试、限流、监控。
这很重要。
核心类越干净,系统越好维护。
chapter 8:定义抽象装饰器
抽象装饰器也实现 MessageSender。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public abstract class MessageSenderDecorator implements MessageSender {
protected final MessageSender delegate;
protected MessageSenderDecorator(MessageSender delegate) { if (delegate == null) { throw new IllegalArgumentException("delegate can not be null"); }
this.delegate = delegate; }
@Override public void send(String receiver, String content) { delegate.send(receiver, content); } }
|
这里的关键字段是:
1
| protected final MessageSender delegate;
|
装饰器内部持有另一个 MessageSender。
这个 delegate 可以是:
所以装饰器可以一层一层包起来。
chapter 9:日志装饰器
日志装饰器负责在发送前后打印日志。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class LoggingMessageSenderDecorator extends MessageSenderDecorator {
public LoggingMessageSenderDecorator(MessageSender delegate) { super(delegate); }
@Override public void send(String receiver, String content) { System.out.println("[LOG] 准备发送消息,receiver = " + receiver);
try { delegate.send(receiver, content);
System.out.println("[LOG] 消息发送成功,receiver = " + receiver); } catch (RuntimeException e) { System.out.println("[LOG] 消息发送失败,receiver = " + receiver + ",error = " + e.getMessage()); throw e; } } }
|
日志装饰器不关心底层到底是短信还是邮件。
只要被包装对象实现了 MessageSender,它就能增强。
chapter 10:耗时统计装饰器
耗时统计装饰器负责记录发送耗时。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class MetricsMessageSenderDecorator extends MessageSenderDecorator {
public MetricsMessageSenderDecorator(MessageSender delegate) { super(delegate); }
@Override public void send(String receiver, String content) { long start = System.currentTimeMillis();
try { delegate.send(receiver, content); } finally { long cost = System.currentTimeMillis() - start;
System.out.println("[METRICS] 消息发送耗时:" + cost + " ms"); } } }
|
这个装饰器使用 finally,保证无论成功还是失败都会统计耗时。
真实项目中可以把这里替换成 Micrometer 指标:
chapter 11:重试装饰器
重试装饰器负责在发送失败时自动重试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public class RetryMessageSenderDecorator extends MessageSenderDecorator {
private final int maxRetryTimes;
public RetryMessageSenderDecorator(MessageSender delegate, int maxRetryTimes) { super(delegate);
if (maxRetryTimes < 0) { throw new IllegalArgumentException("maxRetryTimes can not be negative"); }
this.maxRetryTimes = maxRetryTimes; }
@Override public void send(String receiver, String content) { int attempt = 0;
while (true) { try { attempt++;
delegate.send(receiver, content);
return; } catch (RuntimeException e) { if (attempt > maxRetryTimes) { System.out.println("[RETRY] 已达到最大重试次数,attempt = " + attempt); throw e; }
System.out.println("[RETRY] 消息发送失败,准备重试,attempt = " + attempt); } } } }
|
这里要注意一个边界:
如果 maxRetryTimes = 3,通常表示失败后最多重试 3 次。
总调用次数是 1 次初始调用 + 3 次重试。
真实项目中还需要考虑:
- 重试间隔;
- 指数退避;
- 只对特定异常重试;
- 幂等性;
- 是否会重复发送消息。
重试不是万能药。
在支付、下单、扣库存场景中,盲目重试可能比失败更危险。
chapter 12:限流装饰器
为了简单演示,这里写一个基于计数的简化限流器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import java.util.concurrent.Semaphore;
public class RateLimitMessageSenderDecorator extends MessageSenderDecorator {
private final Semaphore semaphore;
public RateLimitMessageSenderDecorator(MessageSender delegate, int maxConcurrent) { super(delegate);
if (maxConcurrent <= 0) { throw new IllegalArgumentException("maxConcurrent must be greater than 0"); }
this.semaphore = new Semaphore(maxConcurrent); }
@Override public void send(String receiver, String content) { boolean acquired = semaphore.tryAcquire();
if (!acquired) { throw new RuntimeException("Too many concurrent message send requests"); }
try { delegate.send(receiver, content); } finally { semaphore.release(); } } }
|
这个装饰器限制并发发送数量。
真实项目中可以替换成:
- Guava RateLimiter;
- Sentinel;
- Resilience4j RateLimiter;
- Redis 分布式限流;
- 自研滑动窗口限流。
装饰器模式并不关心限流算法是什么。
它只负责把限流能力包在原对象外面。
chapter 13:客户端组合装饰器
现在可以自由组合这些增强能力。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class DecoratorDemo {
public static void main(String[] args) { MessageSender sender = new SmsMessageSender();
sender = new RateLimitMessageSenderDecorator(sender, 10); sender = new RetryMessageSenderDecorator(sender, 3); sender = new MetricsMessageSenderDecorator(sender); sender = new LoggingMessageSenderDecorator(sender);
sender.send("13800000000", "您的验证码是 9527"); } }
|
调用链如下:
1 2 3 4 5
| Logging └── Metrics └── Retry └── RateLimit └── SmsMessageSender
|
执行顺序是:
1 2 3 4 5 6 7 8 9
| Logging 前置逻辑 Metrics 开始计时 Retry 开始尝试 RateLimit 获取许可 SmsMessageSender 发送短信 RateLimit 释放许可 Retry 成功返回 Metrics 统计耗时 Logging 后置逻辑
|
可以看到,装饰器的顺序会影响执行效果。
例如:
1 2
| sender = new RetryMessageSenderDecorator(sender, 3); sender = new MetricsMessageSenderDecorator(sender);
|
表示统计的是“整体重试后的总耗时”。
如果顺序反过来:
1 2
| sender = new MetricsMessageSenderDecorator(sender); sender = new RetryMessageSenderDecorator(sender, 3);
|
表示每次重试都会单独统计耗时。
这就是装饰器模式非常重要的点:
装饰器不仅可以自由组合,组合顺序也有语义。
chapter 14:装饰器顺序很重要
装饰器模式最容易被忽略的问题就是顺序。
例如日志和重试。
1. 日志包在重试外面
1 2 3 4 5 6
| MessageSender sender = new LoggingMessageSenderDecorator( new RetryMessageSenderDecorator( new SmsMessageSender(), 3 ) );
|
效果是:
- 日志记录一次整体发送;
- 重试过程在内部发生;
- 如果最终成功,日志显示成功;
- 如果最终失败,日志显示失败。
2. 重试包在日志外面
1 2 3 4 5 6
| MessageSender sender = new RetryMessageSenderDecorator( new LoggingMessageSenderDecorator( new SmsMessageSender() ), 3 );
|
效果是:
- 每一次尝试都会打印日志;
- 如果失败重试 3 次,日志可能打印多次失败;
- 更适合排查每次调用细节。
没有绝对好坏,只有语义不同。
所以在真实项目中,装饰器链一定要设计清楚。
否则线上看日志时会怀疑人生:为什么明明调用一次,日志出现了四遍?不是系统闹鬼,是装饰器在重试。
chapter 15:JDK 中的装饰器模式:IO 流
Java JDK 中最经典的装饰器模式就是 IO 流。
例如:
1 2 3
| BufferedInputStream inputStream = new BufferedInputStream( new FileInputStream("demo.txt") );
|
这里:
InputStream 是抽象组件;
FileInputStream 是具体组件;
FilterInputStream 是抽象装饰器;
BufferedInputStream 是具体装饰器。
再比如:
1 2 3 4 5
| DataInputStream inputStream = new DataInputStream( new BufferedInputStream( new FileInputStream("demo.txt") ) );
|
这就是一层一层增强:
1 2 3
| DataInputStream └── BufferedInputStream └── FileInputStream
|
FileInputStream 负责从文件读字节。
BufferedInputStream 增加缓冲能力。
DataInputStream 增加读取 Java 基本类型的能力。
这就是装饰器模式最经典的落地。
chapter 16:Spring 中的装饰器思想
Spring 中也有很多装饰器思想。
例如:
1. BeanPostProcessor
可以在 Bean 初始化前后增强 Bean。
2. HttpServletRequestWrapper
Servlet API 中的 HttpServletRequestWrapper 是典型装饰器。
它包装一个 HttpServletRequest,并允许子类重写部分方法。
1 2 3 4 5 6
| public class CustomRequestWrapper extends HttpServletRequestWrapper {
public CustomRequestWrapper(HttpServletRequest request) { super(request); } }
|
3. ResponseEntity 装配
虽然不是严格装饰器,但很多响应增强、包装、转换都体现了“在原对象外面加一层”的思想。
4. Spring Security Filter Chain
过滤器链和装饰器模式不完全相同,但都体现了对请求处理能力的层层增强。
设计模式不是只有“教科书式长相”才算,真实框架里经常是模式思想的变体。
chapter 17:在 Spring Boot 中落地装饰器
在 Spring Boot 项目中,装饰器可以通过 Bean 组合实现。
假设我们有一个核心发送器:
1 2 3 4 5 6 7 8 9 10
| import org.springframework.stereotype.Component;
@Component public class SmsMessageSender implements MessageSender {
@Override public void send(String receiver, String content) { System.out.println("发送短信给 " + receiver + ",内容:" + content); } }
|
如果直接加多个 @Component 装饰器,会遇到一个问题:
Spring 不知道注入哪个 MessageSender。
所以真实项目里通常有几种做法。
chapter 18:Spring 方式一:手动配置装饰器链
可以使用 @Configuration 显式装配。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Configuration public class MessageSenderConfig {
@Bean public MessageSender messageSender() { MessageSender sender = new SmsMessageSender();
sender = new RateLimitMessageSenderDecorator(sender, 10); sender = new RetryMessageSenderDecorator(sender, 3); sender = new MetricsMessageSenderDecorator(sender); sender = new LoggingMessageSenderDecorator(sender);
return sender; } }
|
业务服务直接注入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import org.springframework.stereotype.Service;
@Service public class NotificationService {
private final MessageSender messageSender;
public NotificationService(MessageSender messageSender) { this.messageSender = messageSender; }
public void sendVerifyCode(String phone, String code) { messageSender.send(phone, "您的验证码是:" + code); } }
|
这种方式简单直观。
缺点是装饰器链写死在配置类里。
chapter 19:Spring 方式二:核心 Bean 和装饰器 Bean 分开命名
也可以保留核心 Bean,然后定义一个增强后的 Bean。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Configuration public class MessageSenderConfig {
@Bean public MessageSender rawSmsMessageSender() { return new SmsMessageSender(); }
@Bean public MessageSender decoratedMessageSender() { MessageSender sender = rawSmsMessageSender();
sender = new RateLimitMessageSenderDecorator(sender, 10); sender = new RetryMessageSenderDecorator(sender, 3); sender = new MetricsMessageSenderDecorator(sender); sender = new LoggingMessageSenderDecorator(sender);
return sender; } }
|
业务侧使用 @Qualifier 指定注入增强后的 Bean。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service;
@Service public class NotificationService {
private final MessageSender messageSender;
public NotificationService(@Qualifier("decoratedMessageSender") MessageSender messageSender) { this.messageSender = messageSender; }
public void send(String receiver, String content) { messageSender.send(receiver, content); } }
|
这种方式比较清晰:
rawSmsMessageSender 是原始对象;
decoratedMessageSender 是装饰后的对象。
chapter 20:Spring 方式三:@Primary 指定装饰后对象
如果希望业务默认使用装饰后的对象,可以使用 @Primary。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary;
@Configuration public class MessageSenderConfig {
@Bean public MessageSender rawSmsMessageSender() { return new SmsMessageSender(); }
@Bean @Primary public MessageSender decoratedMessageSender(MessageSender rawSmsMessageSender) { MessageSender sender = rawSmsMessageSender;
sender = new RateLimitMessageSenderDecorator(sender, 10); sender = new RetryMessageSenderDecorator(sender, 3); sender = new MetricsMessageSenderDecorator(sender); sender = new LoggingMessageSenderDecorator(sender);
return sender; } }
|
业务服务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import org.springframework.stereotype.Service;
@Service public class NotificationService {
private final MessageSender messageSender;
public NotificationService(MessageSender messageSender) { this.messageSender = messageSender; }
public void send(String receiver, String content) { messageSender.send(receiver, content); } }
|
这样默认注入的是 @Primary 的装饰后对象。
不过要注意避免循环依赖或注入歧义。
chapter 21:Spring 方式四:使用 AOP 是否也可以
很多增强能力也可以用 AOP 实现,例如:
- 日志;
- 耗时统计;
- 权限校验;
- 事务;
- 监控埋点。
那装饰器和 AOP 怎么选?
可以简单理解:
装饰器适合对象级、显式、可组合的增强。
AOP 适合横切关注点、规则统一、透明增强。
例如:
- 给某一个
MessageSender 自由组合重试、限流、日志,用装饰器很合适;
- 给所有
@Service 方法统一打印耗时日志,用 AOP 更合适;
- 给所有数据库操作加事务,用 Spring AOP 更合适;
- 给不同渠道发送器配置不同增强链,用装饰器更合适。
两者不是对立关系。
真实项目里经常一起使用。
chapter 22:装饰器模式和代理模式的区别
装饰器模式和代理模式很容易混。
它们都包装一个对象,也都实现相同接口。
但目的不同。
| 对比项 |
装饰器模式 |
代理模式 |
| 目的 |
动态增强对象功能 |
控制对对象的访问 |
| 关注点 |
添加职责 |
访问控制 |
| 是否强调组合多个增强 |
是 |
不一定 |
| 客户端是否知道被增强 |
可以知道 |
通常不关心 |
| 典型场景 |
日志、重试、限流、缓冲 |
远程代理、权限代理、懒加载代理 |
| 示例 |
BufferedInputStream 包装 FileInputStream |
MyBatis Mapper 代理、Spring AOP 代理 |
一句话区分:
装饰器是为了增强功能。
代理是为了控制访问。
例如:
1
| new LoggingMessageSenderDecorator(new SmsMessageSender())
|
更像装饰器。
Spring AOP 生成代理对象,对方法调用做拦截,更像代理模式。
当然,真实框架中二者思想有时会重叠,不用过度纠结名字。
chapter 23:装饰器模式和适配器模式的区别
适配器模式也是包装对象,但目的不同。
| 对比项 |
装饰器模式 |
适配器模式 |
| 目的 |
增强原有功能 |
转换不兼容接口 |
| 接口是否一致 |
一致 |
通常不一致 |
| 关注点 |
加功能 |
做接口转换 |
| 示例 |
给 SmsSender 加日志 |
把 AliPaySdk 适配成 PaymentClient |
装饰器说:
你这个接口我能用,我只是给你加点功能。
适配器说:
你这个接口我不能直接用,我得给你转一下。
chapter 24:装饰器模式和组合模式的区别
装饰器模式和组合模式都用了组合关系,但结构目的不同。
| 对比项 |
装饰器模式 |
组合模式 |
| 目的 |
动态增强对象功能 |
表达树形整体-部分关系 |
| 内部对象数量 |
通常包装一个对象 |
通常包含多个子节点 |
| 结构 |
链式包裹 |
树形结构 |
| 示例 |
日志装饰器包短信发送器 |
菜单包含子菜单和按钮 |
装饰器是一层包一层。
组合模式是一棵树。
二者都用了“组合”,但不是一回事。
chapter 25:装饰器模式和桥接模式的区别
桥接模式是拆分两个独立变化维度。
装饰器模式是给对象动态叠加功能。
| 对比项 |
装饰器模式 |
桥接模式 |
| 目的 |
动态增强功能 |
分离抽象和实现 |
| 结构 |
同接口对象互相包装 |
抽象层持有实现层 |
| 关注点 |
功能叠加 |
维度拆分 |
| 示例 |
给 MessageSender 加重试 |
Message 类型组合 Sender 渠道 |
桥接模式解决:
装饰器模式解决:
一个是横向拆维度,一个是纵向叠功能。
chapter 26:装饰器模式的优点
1. 符合开闭原则
新增增强功能时,不需要修改原类。
新增一个装饰器即可。
2. 比继承更灵活
装饰器可以在运行时组合。
继承关系在编译期就固定了。
3. 可以自由组合功能
例如:
1 2 3
| 日志 + 重试 限流 + 指标 日志 + 限流 + 重试 + 指标
|
组合方式灵活。
4. 单一职责更清晰
核心对象只负责核心功能。
装饰器分别负责日志、重试、限流、监控。
5. 可以多层嵌套
装饰器可以包装另一个装饰器,从而形成增强链。
chapter 27:装饰器模式的缺点
1. 类数量增加
每一种增强能力通常对应一个装饰器类。
2. 调用链变长
对象被多层包装后,调试时调用链会更长。
3. 装饰顺序影响行为
不同顺序可能产生不同结果。
这需要开发者明确设计。
4. 过度使用会让结构复杂
如果装饰器太多,代码可能变成:
1
| new A(new B(new C(new D(new E(target)))))
|
看起来像俄罗斯套娃开会。
可以通过配置类、Builder 或工厂来管理装饰器链。
5. 状态管理要谨慎
装饰器如果内部有可变状态,要考虑线程安全。
尤其是在 Spring 单例 Bean 场景中。
chapter 28:适用场景
装饰器模式适合以下场景。
1. 希望在不修改原类的情况下增强功能
例如:
- 日志;
- 监控;
- 限流;
- 重试;
- 缓存;
- 压缩;
- 加密;
- 权限校验。
2. 增强功能可以自由组合
例如某些接口需要日志和重试,某些接口只需要日志,某些接口需要限流和监控。
3. 不希望通过继承扩展功能
如果继承会导致类爆炸,装饰器更合适。
4. 原类不能修改
例如 JDK 类、第三方类、历史系统类。
5. 功能是附加职责,而不是核心职责
例如发送消息的核心职责是发送。
日志、重试、限流是附加职责。
chapter 29:不适合使用的场景
以下场景不建议使用装饰器模式。
1. 只是简单增强一次
如果只有一个非常简单的增强,直接修改原类可能更简单。
2. 增强逻辑强依赖对象内部状态
装饰器通常通过接口和公开方法增强对象。
如果增强逻辑需要大量访问对象内部私有状态,装饰器不一定合适。
3. 增强顺序非常复杂且难以理解
如果装饰器顺序多到难以维护,可能需要重新设计流程。
4. 核心对象接口过大
装饰器需要实现同一个接口。
如果接口方法很多,每个装饰器都要处理很多方法,代码会很重。
5. 横切逻辑覆盖范围很广
例如全系统所有方法统一日志,这种用 AOP 更合适。
chapter 30:真实项目中的实践建议
1. 装饰器名称要体现增强能力
推荐命名:
1 2 3 4
| LoggingMessageSenderDecorator RetryMessageSenderDecorator RateLimitMessageSenderDecorator MetricsMessageSenderDecorator
|
看到类名就知道它加了什么能力。
2. 抽象装饰器不要塞太多逻辑
抽象装饰器只做委托即可。
具体增强逻辑放到具体装饰器中。
3. 注意装饰器顺序
日志、重试、指标、限流的顺序要设计清楚。
推荐用配置类统一组装,避免业务代码到处手写装饰链。
4. 装饰器里不要写核心业务
装饰器适合附加职责。
不要把订单创建、库存扣减、支付状态流转写进装饰器。
否则装饰器会变成隐形业务流程,排查问题非常痛苦。
5. Spring 单例装饰器注意线程安全
装饰器如果作为 Spring Bean 单例存在,内部不要保存请求级状态。
例如不要这样:
1
| private String currentReceiver;
|
请求级数据应该放在方法参数、局部变量或上下文中。
6. 可组合增强适合装饰器,统一横切增强适合 AOP
选择建议:
| 场景 |
推荐方式 |
| 给某个对象自由组合增强 |
装饰器 |
| 全局统一日志 |
AOP |
| 全局事务 |
AOP |
| JDK IO 缓冲/压缩 |
装饰器 |
| 第三方接口转换 |
适配器 |
| 远程调用代理 |
代理模式 |
7. 可以用工厂封装装饰器链
如果装饰器很多,不建议在业务代码中直接套娃。
可以写一个工厂:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class MessageSenderFactory {
public static MessageSender createSmsSender() { MessageSender sender = new SmsMessageSender();
sender = new RateLimitMessageSenderDecorator(sender, 10); sender = new RetryMessageSenderDecorator(sender, 3); sender = new MetricsMessageSenderDecorator(sender); sender = new LoggingMessageSenderDecorator(sender);
return sender; } }
|
业务代码只调用:
1
| MessageSender sender = MessageSenderFactory.createSmsSender();
|
chapter 31:完整案例代码汇总
抽象组件
1 2 3 4
| public interface MessageSender {
void send(String receiver, String content); }
|
具体组件
1 2 3 4 5 6 7
| public class SmsMessageSender implements MessageSender {
@Override public void send(String receiver, String content) { System.out.println("发送短信给 " + receiver + ",内容:" + content); } }
|
1 2 3 4 5 6 7
| public class EmailMessageSender implements MessageSender {
@Override public void send(String receiver, String content) { System.out.println("发送邮件给 " + receiver + ",内容:" + content); } }
|
抽象装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public abstract class MessageSenderDecorator implements MessageSender {
protected final MessageSender delegate;
protected MessageSenderDecorator(MessageSender delegate) { if (delegate == null) { throw new IllegalArgumentException("delegate can not be null"); }
this.delegate = delegate; }
@Override public void send(String receiver, String content) { delegate.send(receiver, content); } }
|
日志装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class LoggingMessageSenderDecorator extends MessageSenderDecorator {
public LoggingMessageSenderDecorator(MessageSender delegate) { super(delegate); }
@Override public void send(String receiver, String content) { System.out.println("[LOG] 准备发送消息,receiver = " + receiver);
try { delegate.send(receiver, content);
System.out.println("[LOG] 消息发送成功,receiver = " + receiver); } catch (RuntimeException e) { System.out.println("[LOG] 消息发送失败,receiver = " + receiver + ",error = " + e.getMessage()); throw e; } } }
|
指标装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class MetricsMessageSenderDecorator extends MessageSenderDecorator {
public MetricsMessageSenderDecorator(MessageSender delegate) { super(delegate); }
@Override public void send(String receiver, String content) { long start = System.currentTimeMillis();
try { delegate.send(receiver, content); } finally { long cost = System.currentTimeMillis() - start;
System.out.println("[METRICS] 消息发送耗时:" + cost + " ms"); } } }
|
重试装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public class RetryMessageSenderDecorator extends MessageSenderDecorator {
private final int maxRetryTimes;
public RetryMessageSenderDecorator(MessageSender delegate, int maxRetryTimes) { super(delegate);
if (maxRetryTimes < 0) { throw new IllegalArgumentException("maxRetryTimes can not be negative"); }
this.maxRetryTimes = maxRetryTimes; }
@Override public void send(String receiver, String content) { int attempt = 0;
while (true) { try { attempt++;
delegate.send(receiver, content);
return; } catch (RuntimeException e) { if (attempt > maxRetryTimes) { System.out.println("[RETRY] 已达到最大重试次数,attempt = " + attempt); throw e; }
System.out.println("[RETRY] 消息发送失败,准备重试,attempt = " + attempt); } } } }
|
限流装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import java.util.concurrent.Semaphore;
public class RateLimitMessageSenderDecorator extends MessageSenderDecorator {
private final Semaphore semaphore;
public RateLimitMessageSenderDecorator(MessageSender delegate, int maxConcurrent) { super(delegate);
if (maxConcurrent <= 0) { throw new IllegalArgumentException("maxConcurrent must be greater than 0"); }
this.semaphore = new Semaphore(maxConcurrent); }
@Override public void send(String receiver, String content) { boolean acquired = semaphore.tryAcquire();
if (!acquired) { throw new RuntimeException("Too many concurrent message send requests"); }
try { delegate.send(receiver, content); } finally { semaphore.release(); } } }
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class DecoratorDemo {
public static void main(String[] args) { MessageSender sender = new SmsMessageSender();
sender = new RateLimitMessageSenderDecorator(sender, 10); sender = new RetryMessageSenderDecorator(sender, 3); sender = new MetricsMessageSenderDecorator(sender); sender = new LoggingMessageSenderDecorator(sender);
sender.send("13800000000", "您的验证码是 9527"); } }
|
chapter 32:一句话总结
装饰器模式的本质是:
在不修改原对象的前提下,通过包装对象的方式动态叠加额外功能。
它特别适合:
- 日志增强;
- 重试增强;
- 限流增强;
- 缓存增强;
- 监控增强;
- 加密压缩;
- IO 流增强;
- 对已有对象增加附加职责。
装饰器模式解决的不是“接口不兼容”,那是适配器模式。
它解决的是:
原对象能用,但我还想给它加点能力。
在 Java 后端开发中,装饰器模式最适合那些可组合、可插拔、非核心业务的增强逻辑。
好的装饰器像一层轻甲:增强能力,但不改变本体。
坏的装饰器像一层层胶带:越缠越厚,最后谁也不知道里面包的是什么。
参考资料
- Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software.
- Robert C. Martin. Agile Software Development, Principles, Patterns, and Practices.
- Joshua Bloch. Effective Java.
- Oracle Java Documentation: java.io InputStream and FilterInputStream.
- Spring Framework Documentation: Core Technologies - The IoC Container.
- Refactoring Guru: Decorator Pattern.
- SourceMaking: Decorator Design Pattern.
启示录
富贵岂由人,时会高志须酬。
能成功于千载者,必以近察远。