文章目录
-
- 前言
- 一、核心定义
- 二、标准体系结构图
- 三、场景推演
- 四、实战案例
-
- [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 只负责新增权限校验,职责更清楚,扩展也更灵活。
继承是"我变成你的一种特殊类型",装饰器是"我包住你,并在你原有能力外面再加一层能力"。