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

- 图片来源网址:装饰设计(装饰者模式 / 装饰器模式)
二、标准体系结构图
classDiagram
class Component {
<<interface>>
+operation()
}
class ConcreteComponent {
+operation()
}
class Decorator {
-component: Component
+operation()
}
class ConcreteDecorator {
+operation()
+addedBehavior()
}
Component <|.. ConcreteComponent
Component <|.. Decorator
Decorator o-- Component
Decorator <|-- ConcreteDecorator
标准结构说明:
| 角色 | 作用 |
|---|---|
Component |
定义原对象和装饰器共同遵守的接口 |
ConcreteComponent |
被装饰的原始对象,提供基础功能 |
Decorator |
抽象装饰器,内部保存一个 Component 引用 |
ConcreteDecorator |
具体装饰器,负责增加额外行为 |
Client |
负责组装对象,可以自由决定是否加装饰、加几层装饰 |
关键点在于:Decorator 实现了 Component,同时又持有一个 Component。
这就形成了一个非常灵活的结构:
1 | Component component = new ConcreteComponent(); |
客户端最终调用的还是 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 单点登录拦截器,它只负责判断用户是否已经登录成功。最开始这完全没有问题,因为需求很简单:
1 | 用户请求进入系统 ➔ 校验是否登录 ➔ 登录成功则放行(失败则拦截) |
随着业务变复杂,不同用户访问 ERP 系统时,不仅要判断“是否登录”,还要判断“是否有权限访问某个方法”。新的业务流程变成了:
1 | 用户请求进入系统 ➔ SSO 登录校验 ➔ 登录成功后继续做方法权限校验 ➔ 均通过才放行 |
痛点分析:为什么不直接改?
面对新增的权限校验需求,常规思路会带来明显的代码腐化:
- 如果直接修改
SsoInterceptor:会把业务权限逻辑侵入到通用登录组件中,破坏它原本的稳定性。 - 如果继承
SsoInterceptor并重写方法:看似扩展了功能,但极易导致子类中重新复制一遍父类的登录校验逻辑,再追加权限校验。一旦后续SSO校验规则发生变化,多个子类都要跟着改,维护成本极高。
更合理的方式是:保留原有 SSO 登录校验能力,在它外面再包装一层权限校验能力。 这正是装饰器模式发挥价值的地方。
在本案例中,各个角色的分工如下表所示:
| 角色分类 | 对应类名 | 职责说明 |
|---|---|---|
| 统一接口 | HandlerInterceptor |
拦截器的顶级标准规范 |
| 原始组件 | SsoInterceptor |
仅负责基础的 SSO 登录校验 |
| 抽象装饰器 | SsoDecorator |
负责包装原始拦截器对象 |
| 具体装饰器 | LoginSsoDecorator |
负责增加具体的方法权限校验逻辑 |
最终客户端使用时,可以这样优雅地组装:
1 | new LoginSsoDecorator(new SsoInterceptor()); |
这行代码的语义非常清晰:用“权限校验装饰器”,去包装“原始 SSO 登录拦截器”。
实际执行流程为:
- 先执行
SsoInterceptor的登录校验。 - 登录成功后,再执行
LoginSsoDecorator的权限校验。
4.2 架构图
4.2.1 普通方式架构图
使用的是继承方式。

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

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