设计模式-装饰器模式(java)

文章目录

    • 前言
    • 一、核心定义
    • 二、标准体系结构图
    • 三、场景推演
    • 四、实战案例
      • [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,是一种通过"组合"替代"继承"来扩展对象行为的模式。

它的核心定义可以拆成三句话理解:

  1. 装饰器和被装饰对象实现同一个接口

  2. 这样客户端使用时不需要关心拿到的是原始对象,还是被包装后的增强对象。

  3. 装饰器内部持有一个同接口对象的引用

    这个引用可以指向原始对象,也可以指向另一个装饰器,所以装饰器可以一层套一层。

  4. 装饰器在调用原对象方法前后添加额外逻辑

    原有能力不变,新增能力通过外层包装完成。

一句话总结:
装饰器模式不是直接改被装饰对象 ,也不是简单继承它,而是用一个新的对象包住它,在不破坏原对象的情况下增强它。


二、标准体系结构图

<<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 组件的前提下,为其叠加"方法访问范围校验"功能。

💡 补充说明:什么是 SSO

SSO(Single Sign-On,单点登录)的作用是让用户只登录一次 ,就可以访问多个相互信任的系统(如公司内部的 OAERPCRM、财务、人事系统等)。

如果没有 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 登录拦截器"。

实际执行流程为:

  1. 先执行 SsoInterceptor 的登录校验。
  2. 登录成功后,再执行 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 复制代码
登录校验 -> 权限校验 -> 返回结果

但缺点也非常明显:

  1. 重复了 SSO 登录校验逻辑

    SsoInterceptor 已经有 ticket 校验,但子类没有复用,而是重新写了一遍。

  2. 继承关系表达不准确

    LoginSsoDecorator extends SsoInterceptor 看起来是扩展 SSO,但实际是覆盖 SSO 的核心流程。

  3. 扩展能力差

    如果继续增加更多校验,比如角色、部门、数据权限、接口限流,就会让这个类越来越膨胀。

  4. 不利于运行时组合

    继承在编译期就确定了父子关系,不能像装饰器一样灵活组装:

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 只负责新增权限校验,职责更清楚,扩展也更灵活。

继承是"我变成你的一种特殊类型",装饰器是"我包住你,并在你原有能力外面再加一层能力"。

相关推荐
爱学习的小可爱卢1 小时前
Java抽象类与接口:面试高频考点全解析
java·javase
WL_Aurora2 小时前
Java多线程详解(一)
java·开发语言
会编程的土豆2 小时前
Go 语言中的 `new` 关键字(创建指针)
java·算法·golang
逸Y 仙X2 小时前
文章三十一:ElasticSearch 管道聚合
java·大数据·elasticsearch·搜索引擎·全文检索
Full Stack Developme2 小时前
Spring 发展历史
java·后端·spring
组合缺一2 小时前
Java 流程编排新范式 Solon Flow:一个引擎,七种节点,覆盖规则/任务/工作流/AI 编排全场景
java·spring·ai·solon·workflow·flow
largecode2 小时前
企业号码认证可以线上办理吗?支持线上申请,设置来电显示品牌名
java·python·智能手机·微信公众平台·facebook·paddle·新浪微博
humcomm2 小时前
2026年 Java 面试新特点
java·开发语言·面试
lili00122 小时前
CC GUI 插件架构剖析:如何为 JetBrains IDE 打造完整的 AI 编程工作台
java·ide·人工智能·python·架构·ai编程