文章目录
-
- 前言
- 一、核心定义
- 二、标准体系结构图
- 三、场景推演
- 四、实战案例
-
- [4.1 需求分析](#4.1 需求分析)
- [4.2 架构图](#4.2 架构图)
-
- [4.2.1 普通方式架构图](#4.2.1 普通方式架构图)
- [4.2.2 装饰器模式架构图](#4.2.2 装饰器模式架构图)
- [4.3 时序图](#4.3 时序图)
-
- [4.3.1 普通代码时序图](#4.3.1 普通代码时序图)
- [4.3.2 装饰器模式时序图](#4.3.2 装饰器模式时序图)
- [4.5 代码分析](#4.5 代码分析)
-
- [4.5.1 普通代码(if-else/硬编码)](#4.5.1 普通代码(if-else/硬编码))
- [4.5.2 装饰器模式代码](#4.5.2 装饰器模式代码)
- 总结
前言
装饰器模式(Decorator Pattern)属于结构型设计模式。它的核心思想是:在不修改原有对象结构的前提下,通过包装对象,为原对象动态增加额外职责。
它解决的不是"能不能加功能"的问题,而是"如何在不破坏原有代码稳定性的前提下,把新功能优雅地加上去"的问题。
装饰器模式的典型认知误区:遇到"需要给某个已有类增加功能"时,第一反应总是继承 ------继承后重写方法,把新逻辑写进去。这在功能单一、不再迭代的场景下没有问题,但一旦需要横向扩展多种功能,就会催生出大量子类,最终维护成本失控。
装饰器模式提供了另一条路:不改变原有类,不继承原有类,而是用一个"包装类"在运行时动态叠加新功能。
本文代码案例:https://github.com/likerhood/CodeDesignWork/tree/main/codedesign8.0-0 和 8.0-1 、8.0-2
一、核心定义
装饰器模式,也叫 Decorator、Wrapper,是一种通过"组合"替代"继承"来扩展对象行为的模式。
它的核心定义可以拆成三句话理解:
-
装饰器和被装饰对象实现同一个接口
-
这样客户端使用时不需要关心拿到的是原始对象,还是被包装后的增强对象。
-
装饰器内部持有一个同接口对象的引用
这个引用可以指向原始对象,也可以指向另一个装饰器,所以装饰器可以一层套一层。
-
装饰器在调用原对象方法前后添加额外逻辑
原有能力不变,新增能力通过外层包装完成。
一句话总结:
装饰器模式不是直接改被装饰对象 ,也不是简单继承它,而是用一个新的对象包住它,在不破坏原对象的情况下增强它。

- 图片来源网址:装饰设计(装饰者模式 / 装饰器模式)
二、标准体系结构图
<<interface>>
Component
+operation()
ConcreteComponent
+operation()
Decorator
-component: Component
+operation()
ConcreteDecorator
+operation()
+addedBehavior()
标准结构说明:
| 角色 | 作用 |
|---|---|
Component |
定义原对象和装饰器共同遵守的接口 |
ConcreteComponent |
被装饰的原始对象,提供基础功能 |
Decorator |
抽象装饰器,内部保存一个 Component 引用 |
ConcreteDecorator |
具体装饰器,负责增加额外行为 |
Client |
负责组装对象,可以自由决定是否加装饰、加几层装饰 |
关键点在于:Decorator 实现了 Component,同时又持有一个 Component。
这就形成了一个非常灵活的结构:
java
Component component = new ConcreteComponent();
component = new ConcreteDecorator(component);
客户端最终调用的还是 component.operation(),但实际执行时已经经过了装饰器增强。
你熟悉的
new BufferedReader(new FileReader(""))就是装饰器模式的经典体现:一层嵌套一层,字节流转字符流再转缓冲流,原有对象完全不受影响。
三、场景推演
业务背景: ERP 系统使用 SSO(单点登录)组件统一管理登录验证。
初期: 系统只需验证登录 ticket 是否合法,验证通过即可访问所有资源。
演进: 随着团队扩大,加入了运营、营销、数据等不同岗位人员,需要对不同用户的访问方法范围做精细化管控------不是每个登录成功的用户都能访问所有接口。
问题: SSO 是一个通用组件 ,不能直接在里面添加特定业务的访问控制逻辑。需要在不修改 SSO 组件的前提下,为其叠加"方法访问范围校验"功能。

- 图片来源网址:[重学 Java 设计模式:实战装饰器模式「SSO单点登录功能扩展,增加拦截用户访问方法范围场景」 | 小傅哥 bugstack 虫洞栈](https://bugstack.cn/md/develop/design-pattern/2020-06-09-重学 Java 设计模式《实战装饰器模式》.html)
💡 补充说明:什么是
SSO?
SSO(Single Sign-On,单点登录)的作用是让用户只登录一次 ,就可以访问多个相互信任的系统(如公司内部的OA、ERP、CRM、财务、人事系统等)。如果没有
SSO,访问每个系统都要单独登录;有了SSO,登录交由统一认证中心处理。用户登录成功后拿到凭证(如 Ticket 或 Token),之后访问其他业务系统只需携带该凭证即可。简而言之,
SSO解决的是:"你是谁?你有没有登录?"但它不直接解决:"你登录之后能访问什么?你有没有权限调用某个方法?"****
两种解法对比:
| 解法 | 做法 | 代价 |
|---|---|---|
| 继承重写 | LoginSsoDecorator extends SsoInterceptor,在 preHandle 中重复 SSO 校验逻辑,再追加权限逻辑 |
SSO 逻辑改了要同步改所有子类;子类职责不清晰 |
| 装饰器模式 | LoginSsoDecorator extends SsoDecorator(HandlerInterceptor),通过 super.preHandle() 委托 SSO 执行,只关注权限扩展 |
零代码冗余,职责清晰,新增扩展类型只加新装饰类 |
四、实战案例
4.1 需求分析
系统原本已经有一个通用的 SSO 单点登录拦截器,它只负责判断用户是否已经登录成功。最开始这完全没有问题,因为需求很简单:
用户请求进入系统 ➔ 校验是否登录 ➔ 登录成功则放行(失败则拦截)
随着业务变复杂,不同用户访问 ERP 系统时,不仅要判断"是否登录",还要判断"是否有权限访问某个方法"。新的业务流程变成了:
用户请求进入系统 ➔ SSO 登录校验 ➔ 登录成功后继续做方法权限校验 ➔ 均通过才放行
痛点分析:为什么不直接改?
面对新增的权限校验需求,常规思路会带来明显的代码腐化:
- 如果直接修改
SsoInterceptor:会把业务权限逻辑侵入到通用登录组件中,破坏它原本的稳定性。 - 如果继承
SsoInterceptor并重写方法 :看似扩展了功能,但极易导致子类中重新复制一遍父类的登录校验逻辑,再追加权限校验。一旦后续SSO校验规则发生变化,多个子类都要跟着改,维护成本极高。
更合理的方式是:保留原有 SSO 登录校验能力,在它外面再包装一层权限校验能力。 这正是装饰器模式发挥价值的地方。
在本案例中,各个角色的分工如下表所示:
| 角色分类 | 对应类名 | 职责说明 |
|---|---|---|
| 统一接口 | HandlerInterceptor |
拦截器的顶级标准规范 |
| 原始组件 | SsoInterceptor |
仅负责基础的 SSO 登录校验 |
| 抽象装饰器 | SsoDecorator |
负责包装原始拦截器对象 |
| 具体装饰器 | LoginSsoDecorator |
负责增加具体的方法权限校验逻辑 |
最终客户端使用时,可以这样优雅地组装:
Java
new LoginSsoDecorator(new SsoInterceptor());
这行代码的语义非常清晰:用"权限校验装饰器",去包装"原始 SSO 登录拦截器"。
实际执行流程为:
- 先执行
SsoInterceptor的登录校验。 - 登录成功后,再执行
LoginSsoDecorator的权限校验。
4.2 架构图
4.2.1 普通方式架构图
使用的是继承方式。

这个版本的问题是:LoginSsoDecorator 虽然继承了 SsoInterceptor,但它并没有复用父类的 preHandle,而是把父类里的登录校验逻辑又写了一遍。
也就是说,代码从:
text
SsoInterceptor 负责登录校验
变成了:
text
LoginSsoDecorator 同时负责登录校验 + 权限校验
短期看能跑,长期看容易失控。
如果后面继续增加"部门校验""数据范围校验""接口频率校验",就可能不断出现新的子类,或者在一个类里堆越来越多逻辑。
4.2.2 装饰器模式架构图

核心变化是:
java
new LoginSsoDecorator(new SsoInterceptor())
这句代码表达的意思是:
text
用 LoginSsoDecorator 包装 SsoInterceptor
执行时先走装饰器:
java
boolean success = super.preHandle(request, response, handler);
而 super.preHandle(...) 实际会调用被包装对象:
java
handlerInterceptor.preHandle(request, response, handler);
也就是:
text
LoginSsoDecorator
-> SsoDecorator
-> SsoInterceptor
这样 SsoInterceptor 继续只做登录校验,LoginSsoDecorator 只在登录成功后增加权限校验。
4.3 时序图
4.3.1 普通代码时序图
LoginSsoDecorator ApiTest LoginSsoDecorator ApiTest alt [登录失败] [登录成功] preHandle("1successhuahua", response, handler) 截取 ticket = request.substring(1, 8) 判断 ticket 是否等于 success false 截取 userId = request.substring(8) 从 authMap 查询用户可访问方法 判断 method 是否为 queryUserInfo true / false
这个版本中,所有流程都在 LoginSsoDecorator 一个类里完成。
问题是:虽然类名叫 LoginSsoDecorator,但它并不是严格意义上的装饰器。 因为它没有包装一个 SsoInterceptor 对象,而是通过继承重写了全部流程。
4.3.2 装饰器模式时序图
SsoInterceptor SsoDecorator LoginSsoDecorator ApiTest SsoInterceptor SsoDecorator LoginSsoDecorator ApiTest alt [SSO 登录失败] [SSO 登录成功] preHandle("1successhuahua", response, handler) super.preHandle(request, response, handler) handlerInterceptor.preHandle(request, response, handler) 截取 ticket 并判断是否为 success true / false true / false false 截取 userId = huahua 查询 authMap 得到 queryUserInfo 判断是否允许访问方法 true / false
它把两件事分开了:
| 类 | 职责 |
|---|---|
SsoInterceptor |
判断是否登录 |
LoginSsoDecorator |
判断登录后是否有方法权限 |
SsoDecorator |
连接原始对象和装饰对象 |
4.5 代码分析
4.5.1 普通代码(if-else/硬编码)
tutorials-12.0-1 的核心代码:
java
public class LoginSsoDecorator extends SsoInterceptor {
private static Map<String, String> authMap = new ConcurrentHashMap<String, String>();
static {
authMap.put("huahua", "queryUserInfo");
authMap.put("doudou", "queryUserInfo");
}
@Override
public boolean preHandle(String request, String response, Object handler) {
String ticket = request.substring(1, 8);
boolean success = ticket.equals("success");
if (!success) return false;
String userId = request.substring(8);
String method = authMap.get(userId);
return "queryUserInfo".equals(method);
}
}
这个实现的优点是简单直接:
text
登录校验 -> 权限校验 -> 返回结果
但缺点也非常明显:
-
重复了 SSO 登录校验逻辑
SsoInterceptor已经有 ticket 校验,但子类没有复用,而是重新写了一遍。 -
继承关系表达不准确
LoginSsoDecorator extends SsoInterceptor看起来是扩展 SSO,但实际是覆盖 SSO 的核心流程。 -
扩展能力差
如果继续增加更多校验,比如角色、部门、数据权限、接口限流,就会让这个类越来越膨胀。
-
不利于运行时组合
继承在编译期就确定了父子关系,不能像装饰器一样灵活组装:
java
new ADecorator(new BDecorator(new SsoInterceptor()))
4.5.2 装饰器模式代码
tutorials-12.0-2 的核心代码分成两部分。
第一部分是抽象装饰器 SsoDecorator:
java
public abstract class SsoDecorator implements HandlerInterceptor {
private HandlerInterceptor handlerInterceptor;
public SsoDecorator(HandlerInterceptor handlerInterceptor) {
this.handlerInterceptor = handlerInterceptor;
}
public boolean preHandle(String request, String response, Object handler) {
return handlerInterceptor.preHandle(request, response, handler);
}
}
这一层的重点不是业务逻辑,而是结构设计。
它通过构造函数接收一个 HandlerInterceptor:
java
public SsoDecorator(HandlerInterceptor handlerInterceptor)
所以它可以包装:
java
new SsoInterceptor()
也可以包装另一个装饰器:
java
new LoginSsoDecorator(new OtherDecorator(new SsoInterceptor()))
第二部分是具体装饰器 LoginSsoDecorator:
java
public class LoginSsoDecorator extends SsoDecorator {
public LoginSsoDecorator(HandlerInterceptor handlerInterceptor) {
super(handlerInterceptor);
}
@Override
public boolean preHandle(String request, String response, Object handler) {
boolean success = super.preHandle(request, response, handler);
if (!success) return false;
String userId = request.substring(8);
String method = authMap.get(userId);
return "queryUserInfo".equals(method);
}
}
这段代码最关键的是:
java
boolean success = super.preHandle(request, response, handler);
它不是自己重新做登录校验,而是把登录校验交回被包装的 SsoInterceptor。
只有登录成功后,才执行自己的权限校验逻辑。
测试代码:
java
LoginSsoDecorator ssoDecorator = new LoginSsoDecorator(new SsoInterceptor());
String request = "1successhuahua";
boolean success = ssoDecorator.preHandle(request, "ewcdqwt40liuiu", "t");
这段组装关系非常重要:
text
LoginSsoDecorator 包装 SsoInterceptor
最终含义是:
text
先执行 SSO 登录校验,再执行登录后的权限校验
总结
装饰器模式解决的不是"能不能加功能"的问题,而是"如何优雅地加功能"的问题。
在本案例中,tutorials-12.0-1 通过继承完成了功能扩展,但登录校验和权限校验耦合在一起,还重复了父类逻辑;tutorials-12.0-2 使用装饰器模式后,SsoInterceptor 继续负责原始登录校验,LoginSsoDecorator 只负责新增权限校验,职责更清楚,扩展也更灵活。
继承是"我变成你的一种特殊类型",装饰器是"我包住你,并在你原有能力外面再加一层能力"。