结构型与行为型高频

共 19 题
📑 题目列表 19 题
#
★★★

1. 策略模式与状态模式结构几乎相同,意图差异是什么?各自的典型业务场景?

请解释策略模式与状态模式结构几乎相同但意图差异,说明各自的典型业务场景?

  • 策略与状态的结构相似(都封装算法/行为可替换)
  • 意图差异:策略是"可替换算法",状态是"随状态迁移的行为"
  • 典型场景
  • 结构相似:策略模式与状态模式都封装一组可替换的行为,通过组合 + 委托实现,客户端不直接依赖具体行为类,结构上几乎相同(都有一个 Context 持有策略/状态对象)。
  • 意图差异:
    • 策略模式:算法可替换,客户端主动选择用哪个策略,策略之间是"并列的算法",不关心状态迁移。目的是替换算法、消除 if-else 分支。
    • 状态模式:状态驱动行为,对象的行为随"内部状态"改变而改变,状态之间有迁移关系(状态机),状态切换由状态对象自己决定。目的是让状态变化自动改变行为,消除状态判断的 if-else。
  • 典型场景:策略——支付方式(支付宝/微信/银行卡)、排序算法、压缩格式;状态——订单状态机(待支付/已支付/已发货/已完成)、TCP 连接状态、电梯状态。
  • 关键区别:策略由外部/客户端选择且不迁移;状态的切换由状态对象内部驱动且有明确迁移。

一句话:策略是"客户端选算法",状态是"状态机自动换行为"。结构相似但意图不同——策略并列可替换,状态有迁移关系。

#
★★★

2. 观察者模式与发布-订阅模式的区别中有无 Broker、耦合度与失败传播行为?

请解释观察者模式与发布-订阅模式的区别,说明有无 Broker、耦合度与失败传播行为?

  • 观察者(直接通知)vs 发布-订阅(Broker 中转)
  • 耦合度差异
  • 失败传播行为
  • 观察者模式:主题(Subject)维护观察者列表,状态变化时直接调用每个观察者的更新方法。观察者与主题直接耦合(主题知道观察者)。
  • 发布-订阅模式:发布者(Publisher)与订阅者(Subscriber)通过**消息代理(Broker/事件总线)**解耦,发布者把消息发给 Broker,Broker 按订阅关系分发给订阅者。发布者不直接知道订阅者。
  • 耦合度:观察者模式主题与观察者直接耦合(但都是抽象接口);发布-订阅通过 Broker 完全解耦,发布者与订阅者互不感知,可独立扩展。
  • 失败传播:观察者模式是同步直接调用,一个观察者抛异常会传播给主题,可能中断后续通知(需 try-catch 保护);发布-订阅通过 Broker 异步分发,订阅者失败不影响发布者和其他订阅者(隔离,Broker 处理重试/失败队列)。

观察者是"直接通知"(主题知观察者,同步耦合),发布-订阅是"Broker 中转"(完全解耦,可异步)。失败传播上,观察者同步传播,发布-订阅隔离。

#
★★★

3. 装饰器模式与代理模式的结构与意图差异?动态代理(JDK/CGLIB)与代理模式的关系?

请解释装饰器模式与代理模式的结构与意图差异,说明动态代理(JDK/CGLIB)与代理模式的关系?

  • 装饰器(增强功能)vs 代理(控制访问)
  • 结构相似(都包装对象)但意图不同
  • 动态代理与代理模式
  • 结构相似:装饰器与代理都包装一个对象,通过委托转发调用,结构相似。
  • 意图差异:
    • 装饰器:增强功能,动态地为对象添加/叠加功能(如加日志、加缓存、加压缩),与客户端透明,客户端可多层装饰。
    • 代理:控制访问,控制对目标对象的访问(如权限控制、延迟加载、远程代理、智能引用),通常代理由客户端显式使用,取代目标对象。
  • 关键区别:装饰器是"功能叠加、透明",代理是"访问控制、非透明(代替目标)"。
  • 动态代理与代理模式:JDK 动态代理(基于接口,Proxy + InvocationHandler)和 CGLIB(基于继承,生成目标子类)都是代理模式的实现方式,在运行时生成代理对象,无需手写代理类。AOP、Spring 事务、MyBatis 等都基于动态代理实现代理模式。

装饰器"增强功能"、代理"控制访问"。动态代理(JDK/CGLIB)是代理模式的运行时实现,AOP 等框架大量使用。

#
★★★

4. 模板方法模式与策略模式在"复用算法骨架"上的互补关系?钩子方法的设计要点?

请解释模板方法模式与策略模式在"复用算法骨架"上的互补关系,说明钩子方法的设计要点?

  • 模板方法复用算法骨架(继承)
  • 策略复用算法(组合)
  • 钩子方法设计
  • 模板方法模式:父类定义算法骨架(步骤顺序),把可变步骤定义为抽象方法,由子类实现。优点:复用骨架、约束结构;缺点:基于继承,耦合父类。
  • 策略模式:把算法封装成独立策略对象,通过组合注入,客户端可替换整个算法。优点:灵活组合、基于组合;缺点:不共享骨架,每个策略独立实现。
  • 互补关系:模板方法擅长"复用算法骨架,只变细节",策略擅长"替换整个算法,灵活组合"。当"骨架固定、部分步骤可变"用模板方法;当"整个算法可替换、需要组合"用策略。两者可结合:模板方法定义骨架,策略提供可变步骤。
  • 钩子方法设计要点:钩子(hook)是父类中"有默认实现、子类可覆盖"的方法,用于在特定点让子类参与。要点:钩子方法应提供合理默认值(不覆写则用默认行为);钩子位置要清晰(在算法关键步骤前后);钩子尽量少而明确,避免过度开放;钩子方法与抽象方法区分(抽象必须实现,钩子可选覆写)。

模板方法"继承复用骨架",策略"组合替换算法",互补。钩子方法提供"可选覆盖点",设计要点是默认值、清晰位置、避免过度开放。

#
★★★

5. 代理模式 vs 装饰器模式中职责不同(控制访问 vs 增强功能),在 AOP/缓存中的应用?

请对比代理模式与装饰器模式,说明职责不同(控制访问 vs 增强功能),以及它们在 AOP/缓存中的应用?

  • 代理控制访问 vs 装饰器增强功能
  • AOP 用动态代理
  • 缓存代理/装饰器
  • 职责:代理模式控制访问(权限、延迟、远程、智能引用),代理从客户端角度"代替"目标;装饰器模式增强功能(叠加日志/缓存/压缩),透明叠加。
  • 结构:都包装对象,但装饰器是"功能叠加链",代理是"访问控制替身"。
  • AOP 中的应用:AOP(如 Spring 事务、日志、安全)用动态代理(JDK/CGLIB)实现,在目标方法调用前后插入横切逻辑(如开启事务、记录日志)。这本质是代理模式(控制/拦截访问),也可以用装饰器思维(增强),但 AOP 更接近代理(拦截控制)。
  • 缓存中的应用:缓存可以用代理模式(对目标方法做缓存拦截,命中直接返回,控制对真实方法的访问)或用装饰器(给方法叠加缓存能力)。缓存代理透明地拦截重复调用,避免重复计算。

代理=控制访问(AOP 拦截、缓存拦截),装饰器=增强功能。AOP 用动态代理控制横切,缓存用代理拦截重复调用。两者结构相似但职责不同。

#
★★★

6. 迭代器与组合模式中如何统一容器遍历接口并支持递归结构?

请解释迭代器与组合模式,说明如何统一容器遍历接口并支持递归结构?

  • 迭代器统一遍历接口
  • 组合模式支持树形递归结构
  • 组合迭代器遍历树
  • 迭代器模式:定义统一的遍历接口(hasNext()/next()),把遍历逻辑从容器中分离,客户端无需知道容器内部结构即可遍历。不同容器(List/Set/Map)提供各自的迭代器,统一遍历接口。
  • 组合模式:把对象组织成树形结构(部分-整体),单个对象(叶子)与组合对象(容器)统一接口,客户端可一致地处理单个与组合。容器可包含叶子或其他容器,形成递归结构。
  • 组合迭代器:组合模式常配合迭代器实现递归遍历——组合对象提供迭代器,遍历时对叶子返回元素,对子容器递归遍历(用栈或递归),实现对整个树的统一遍历。客户端遍历树时无需区分叶子与容器。
  • 应用:文件系统(目录/文件)、菜单/菜单项、组织架构(部门/员工)。

迭代器统一"遍历接口",组合模式统一"递归结构",两者结合:组合对象用迭代器递归遍历树,客户端一致处理叶子与容器。

#
★★★

7. 你在实际项目中如何用策略模式消除 if-else 分支?结合支付/营销/计费场景说明重构步骤、策略注册表与测试收益?

请结合实际项目说明如何用策略模式消除 if-else 分支,结合支付/营销/计费场景说明重构步骤、策略注册表与测试收益?

  • 用策略模式消除 if-else
  • 重构步骤
  • 策略注册表与测试收益
  • 问题:支付/计费场景常有一堆 if-else 判断"用什么方式/算法"(如按支付方式、按计费类型),分支多、难维护、违背开闭。
  • 重构步骤:① 提取公共接口(策略接口,如 PayStrategy 定义 pay());② 把每个分支的算法封装成独立策略类(实现接口);③ 用策略注册表(Map<类型, 策略>)替代 if-else,按类型从注册表取策略并调用;④ 客户端依赖策略接口,运行时根据类型选择策略。
  • 策略注册表:Map<PayType, PayStrategy> 在启动时注册,业务代码 strategy = registry.get(type); strategy.pay(order),消除 if-else,新增支付方式只需新增策略类 + 注册,符合开闭。
  • 测试收益:每个策略是独立类,可单独单元测试(传入 mock 依赖),无需跑整个分支流程;职责清晰、可读性好;新增策略不影响已有测试。

策略模式 + 注册表把"分支判断"变成"查找 + 多态调用",消除 if-else、符合开闭、测试隔离。支付/计费/营销是最典型场景。

interface PayStrategy { void pay(Order o); }
@Component class AlipayStrategy implements PayStrategy { public void pay(Order o){} }
@Component class WechatStrategy implements PayStrategy { public void pay(Order o){} }
// 注册表
@Component class PayRegistry {
    Map<PayType, PayStrategy> map = new HashMap<>();
    PayRegistry(List<PayStrategy> list) { for (PayStrategy s : list) map.put(s.type(), s); }
    PayStrategy get(PayType t) { return map.get(t); }
}
// 使用
payRegistry.get(order.getPayType()).pay(order);
#
★★★

8. Spring/主流框架中大量使用了哪些设计模式?举出 AOP(动态代理)、IoC(工厂)、事件监听(观察者)、拦截器(责任链)、JdbcTemplate(模板方法)等实例

请说明 Spring/主流框架中大量使用的设计模式,举出 AOP(动态代理)、IoC(工厂)、事件监听(观察者)、拦截器(责任链)、JdbcTemplate(模板方法)等实例?

  • Spring 中的设计模式实例
  • AOP、IoC、事件、拦截器、模板方法
  • 框架与设计模式的结合
  • 工厂模式:Spring IoC 容器(BeanFactory/ApplicationContext)是工厂,创建并管理 Bean。
  • 单例模式:默认单例 Bean。
  • 代理模式:AOP 用动态代理(JDK/CGLIB)在方法调用前后插入横切逻辑(事务、日志、安全)。
  • 观察者模式:Spring 事件监听(ApplicationEventPublisher + @EventListener),发布事件、监听者响应。
  • 责任链模式:拦截器链(HandlerInterceptor)、过滤器链(Filter)按顺序执行,可终止。
  • 模板方法模式:JdbcTemplate(回调模板)、RestTemplate、各种 *Template 封装固定流程,把可变步骤交给回调/子类。
  • 策略模式:Resource 解析、Bean 实例化策略、HandlerMapping 等。
  • 装饰器/适配器:IO 流(InputStreamReader 装饰),HandlerAdapter 适配不同的 Controller 类型。

Spring 是设计模式的集大成者:IoC 工厂、单例 Bean、AOP 动态代理、事件观察者、拦截器责任链、Template 模板方法、HandlerAdapter 适配器等,理解框架背后的设计模式能加深对框架的理解。

#
★★

9. 责任链模式在过滤器链/拦截器/审批流中的应用与纯/不纯责任链差异?

请解释责任链模式在过滤器链/拦截器/审批流中的应用,说明纯责任链与不纯责任链的差异?

  • 责任链的链式处理
  • 过滤器链/拦截器/审批流
  • 纯 vs 不纯责任链
  • 责任链模式:把多个处理器串成链,请求沿链传递,每个处理器决定是否处理并传给下一个。优点:解耦发送者与接收者,可动态组合处理器。
  • 应用:
    • 过滤器链(Servlet Filter):HTTP 请求按顺序经过多个过滤器(编码、鉴权、日志),每个过滤器可处理并调用 chain.doFilter 传给下一个。
    • 拦截器(Spring HandlerInterceptor):拦截请求,按配置顺序执行 preHandle/postHandle,可终止(返回 false 中断)。
    • 审批流:审批节点(经理→总监→CEO)串成链,每个节点处理或传给下一个。
  • 纯 vs 不纯责任链:
    • 纯责任链:每个处理器要么处理要么传递,且只有一个处理器处理(处理完即终止,不传给下一个);请求必须被某个处理器处理。
    • 不纯责任链:处理器可以既处理又传递(处理部分后继续传给下一个,多个处理器都处理),或允许无处理器处理(请求可被丢弃)。过滤器链/拦截器通常是不纯(每个都执行,可继续也可终止)。

责任链解耦处理,过滤器/拦截器/审批流是典型应用。纯责任链"一个处理器处理完即止",不纯责任链"可处理又传递、多个参与"。

#
★★

10. 适配器、桥接、外观三者的意图辨析中分别解决什么维度的适配问题?

请辨析适配器、桥接、外观三者的意图,说明它们分别解决什么维度的适配问题?

  • 适配器:接口转换
  • 桥接:抽象与实现分离
  • 外观:统一入口
  • 适配器(Adapter):把一个接口转换成客户端期望的另一个接口,解决"接口不匹配"的问题。让原本不兼容的类能协作。维度:接口转换。
  • 桥接(Bridge):把抽象实现分离,让两者可以独立变化(如不同形状与不同颜色组合)。解决"多维度的变化"问题。维度:抽象与实现解耦,避免类爆炸。
  • 外观(Facade):为复杂子系统提供一个统一、简化的入口,隐藏内部复杂性。解决"客户端与子系统耦合"的问题。维度:简化访问入口。
  • 区别:适配器是"适配已有接口给另一个";桥接是"分离抽象与实现使其独立扩展";外观是"为子系统提供门面简化调用"。三者都解耦,但目标维度不同:接口转换、抽象/实现分离、统一入口。

适配器=接口转换,桥接=抽象与实现解耦,外观=统一门户。三者维度不同,适配器解决"能力不匹配",桥接解决"多维变化",外观解决"复杂子系统访问"。

#
★★

11. 享元模式与对象池的区别?Integer 缓存是享元吗?

请解释享元模式与对象池的区别,说明 Integer 缓存是否是享元?

  • 享元(共享内部状态)vs 对象池(复用对象)
  • 共享 vs 复用
  • Integer 缓存与享元
  • 享元模式(Flyweight):通过共享大量对象的内部状态(不可变、可共享的部分)来减少对象数量,外部状态(可变的)由客户端持有。目的是减少内存占用。
  • 对象池:复用对象(借用/归还),对象本身可复用,目的减少创建/销毁开销。两者都"减少对象",但享元是"共享内部状态",对象池是"复用完整对象"。
  • Integer 缓存是享元吗:是。Integer.valueOf(i) 对 -128~127 范围缓存 Integer 对象,多个引用共享同一对象(内部状态是不可变的值),是享元模式(共享不可变对象)。Integer 实现类似享元,缓存不可变对象供共享。
  • 区别:享元共享"不可变的内部状态",对象池复用"可变的完整对象(借用归还)"。

享元=共享不可变内部状态(Integer 缓存),对象池=复用可借还对象。两者都减少内存/对象数,但机制不同:共享 vs 复用。

#
★★

12. 策略模式 vs 状态模式中算法替换 vs 状态迁移,如何避免 if-else 爆炸?

请对比策略模式与状态模式,说明算法替换 vs 状态迁移,以及如何避免 if-else 爆炸?

  • 策略替换算法 vs 状态迁移
  • 都消除 if-else
  • 用多态代替分支
  • 策略模式:封装可替换算法,客户端选择策略,重点在"算法替换"。
  • 状态模式:封装随状态变化的行为,状态有迁移关系,重点在"状态迁移"。
  • 都避免 if-else 爆炸:两者都用多态代替 if-else 分支——策略/状态对象封装行为,通过组合调用,把"if/switch 判断"变成"多态调用"。
  • 如何避免 if-else 爆炸:
    • 策略:把每个分支的算法提升为策略类,用注册表按类型查找,消除 if-else。
    • 状态:把每个状态的逻辑封装为状态类(状态对象),状态迁移由状态对象驱动,消除状态判断的 if-else。
    • 选择依据:是"算法可替换"用策略,是"状态随迁移变化"用状态。
  • 注意:策略是"外部/客户端选择",状态是"内部状态迁移自动切换"。

策略=算法替换(外部选择),状态=状态迁移(内部驱动)。都用多态消除 if-else,避免分支爆炸。选型看"可替换算法"还是"状态驱动行为"。

#
★★

13. 适配器 vs 外观中接口转换 vs 统一入口,第三方库封装时的选择?

请对比适配器与外观,说明接口转换 vs 统一入口,以及封装第三方库时的选择?

  • 适配器:接口转换
  • 外观:统一入口
  • 封装第三方库的选择
  • 适配器(Adapter):把第三方/已有类的接口转换成客户端期望的接口,解决"接口不匹配"。客户端依赖适配器提供的接口。
  • 外观(Facade):为第三方/复杂子系统提供统一、简化的入口,隐藏内部 API 的复杂性,客户端只调用外观的简单方法,不直接与子系统复杂 API 交互。
  • 封装第三方库时的选择:
    • 若需把第三方库的接口适配成我们系统的接口(接口不匹配),用适配器。
    • 若第三方库接口复杂、需要统一简化入口、隐藏内部细节(如封装整个 SDK 暴露简单方法),用外观。
    • 实际上两者常结合:外观提供统一简化入口,内部用适配器适配具体库。选择关键是"接口转换"还是"简化入口"。

适配器=把一个接口转成另一个(接口不匹配),外观=为复杂库提供统一简单入口。封装第三方库时,接口不匹配用适配器,简化复杂 API 用外观。

#
★★

14. 中介者模式(Mediator)如何把多对多的对象交互集中到单一中介者,以聊天室/航班调度为例,同事类为何只依赖中介者接口,中介者膨胀为'上帝对象'时如何权衡,与观察者/发布-订阅的差异?

请解释中介者模式如何把多对多的对象交互集中到单一中介者,以聊天室/航班调度为例,说明同事类为何只依赖中介者接口,中介者膨胀为上帝对象时如何权衡,以及与观察者/发布-订阅的差异?

  • 中介者集中多对多交互
  • 同事类只依赖中介者接口
  • 中介者膨胀与观察者/发布-订阅差异
  • 中介者模式:把多个对象(同事类)之间的多对多交互集中到一个中介者(Mediator)对象,同事类之间不直接互相引用,而是通过中介者交互。聊天室:多个用户通过聊天室(中介者)广播消息,用户之间不直接通信;航班调度:多个航班/塔台通过调度中心(中介者)协调。
  • 同事类只依赖中介者接口:因为要解耦,同事类只依赖抽象的中介者接口(不依赖具体中介者),通过中介者转发消息,同事类之间没有直接引用,降低耦合、便于扩展(新增同事只需实现接口并注册)。
  • 中介者膨胀:当交互逻辑复杂时,中介者可能聚集大量交互逻辑,膨胀为"上帝对象"(上帝类)。权衡:① 把中介者的逻辑按职责拆分(多个中介者/子中介者);② 适度拆分,避免过集中;③ 交互逻辑简单时用中介者,复杂时考虑事件驱动/发布-订阅分散逻辑。
  • 与观察者/发布-订阅差异:观察者/发布-订阅通过事件/消息解耦,交互是"事件通知"(主题通知订阅者,或 Broker 分发);中介者是"集中转发"(同事统一通过中介者协调)。中介者更强调"集中协调逻辑",发布-订阅更强调"解耦事件分发"。发布-订阅可视为中介者的分布式/异步变体。

中介者把多对多集中为"中介者转发",同事只依赖中介者接口解耦;中介者易膨胀为上帝对象,需按职责拆分;与观察者/发布-订阅比,中介者集中协调,发布-订阅事件分发更解耦。

#
★★

15. 备忘录模式(Memento)如何在不破坏封装的前提下实现撤销,Originator 生成快照、Caretaker 保存与恢复的三角协作,为何窄/宽接口设计(仅 Originator 可访问内部状态)是封装关键,与命令模式组合实现多级撤销的代价?

请解释备忘录模式如何在不破坏封装的前提下实现撤销,说明 Originator 生成快照、Caretaker 保存与恢复的三角协作,窄/宽接口设计的封装关键,以及与命令模式组合实现多级撤销的代价?

  • Originator/Caretaker/Memento 三角协作
  • 窄/宽接口封装
  • 与命令模式组合做多级撤销的代价
  • 备忘录模式:在不暴露内部状态的前提下,保存对象状态快照以便撤销。三个角色:Originator(生成/恢复快照)、Memento(快照)、Caretaker(保存/管理快照)。
  • 三角协作:Originator 生成 Memento(保存自己的内部状态),Caretaker 保存 Memento 并管理历史,需要撤销时从 Caretaker 取出 Memento 交给 Originator 恢复。Originator 是唯一能创建/读取 Memento 内部状态的。
  • 窄/宽接口:Memento 有窄接口(给 Caretaker,只能保存/获取,看不到内部状态)与宽接口(给 Originator,能访问内部状态)。这样 Caretaker 无法访问内部状态,只有 Originator 能读/写,确保封装(外部不破坏对象内部状态)。
  • 与命令模式组合做多级撤销:命令模式记录每次操作,与备忘录组合(每次操作前保存快照)可实现多级撤销。代价:每个快照保存完整状态,内存占用大;反复撤销/重做性能开销;需管理快照生命周期与清理。适合状态可快照、撤销多级、内存可承受的场景。

备忘录三角协作 + 窄/宽接口保证"外部不破坏封装"。与命令组合做多级撤销,代价是快照内存与性能开销。

#
★★

16. 观察者模式与 Spring 事件机制中@EventListener 如何实现发布订阅,异步监听器的事务边界、异常与回滚语义?

请解释观察者模式与 Spring 事件机制,说明 @EventListener 如何实现发布订阅,以及异步监听器的事务边界、异常与回滚语义?

  • @EventListener 发布订阅
  • 同步/异步监听
  • 事务边界与异常回滚
  • Spring 事件机制:ApplicationEventPublisher.publishEvent(event) 发布事件,@EventListener 注解的方法监听事件,实现观察者/发布-订阅模式。发布者与监听者解耦。
  • 同步监听:默认监听器在发布线程同步执行,发布者等待监听器完成。异步监听:@Async 注解监听器方法,在独立线程执行,发布者不等待。
  • 事务边界:Spring 事件监听默认在发布者的事务内(若发布发生在事务内),同步监听器参与同一事务。异步监听器在新线程执行,不在发布者事务内,有独立事务边界。
  • 异常与回滚:同步监听器抛异常会传播给发布者,可能破坏发布者事务(若监听器在事务内抛异常,事务回滚)。异步监听器抛异常不影响发布者事务(隔离),但需自行处理失败(如重试、记录)。@TransactionalEventListener 可在事务提交后/前触发,控制监听时机。
  • 建议:异步监听器内做幂等与错误处理,事务性操作用 @TransactionalEventListener 控制时机。

Spring 事件机制是观察者/发布-订阅。同步监听参与发布者事务(异常可能回滚),异步监听独立线程有独立事务边界(异常隔离)。用 @TransactionalEventListener 控制事务时机。

#
★★

17. 责任链模式在 SpringMVC 拦截器/过滤器链中的执行顺序与终止机制,与 AOP 切面的差异?

请解释责任链模式在 SpringMVC 拦截器/过滤器链中的执行顺序与终止机制,以及与 AOP 切面的差异?

  • 过滤器链与拦截器链的执行顺序
  • 终止机制
  • 与 AOP 切面的差异
  • 过滤器(Filter):Servlet 容器层,在请求进入 DispatcherServlet 前执行,按配置顺序(@Order/注册顺序)执行。每个过滤器调用 chain.doFilter 传给下一个,不调用则终止。
  • 拦截器(Spring HandlerInterceptor):Spring MVC 层,执行顺序:preHandle(按注册顺序)→ 业务方法 → postHandle(逆序)→ afterCompletion(逆序)。preHandle 返回 false 则终止(不再执行后续拦截器与业务方法)。
  • 执行顺序:过滤器在拦截器之前(过滤器最外层,拦截器在 MVC 内部)。终止机制:过滤器不调用 doFilter 即终止;拦截器 preHandle 返回 false 终止。
  • 与 AOP 切面差异:AOP 切面(@Aspect)基于代理,在目标方法调用前后织入(@Before/@After/@Around),作用于 Bean 方法;拦截器/过滤器作用于 HTTP 请求链路(MVC 层)。AOP 更细粒度(方法级)、透明(代理),拦截器/过滤器是请求级、显式。AOP 切面在拦截器之后(方法调用时)。

过滤器(最外层)→ 拦截器(MVC 层)→ 业务方法(AOP 切面在方法级)。终止靠 doFilter/false。AOP 是方法级代理切面,拦截器/过滤器是请求级。

#

18. 观察者模式与发布-订阅中同步/异步通知、事件总线与内存泄漏(未解绑)问题?

请解释观察者模式与发布-订阅,说明同步/异步通知、事件总线与内存泄漏(未解绑)问题?

  • 同步 vs 异步通知
  • 事件总线
  • 内存泄漏(未解绑)
  • 同步通知:观察者直接回调,简单、实时,但阻塞发布者、慢观察者拖累全部。
  • 异步通知:通过事件总线/消息队列异步分发,发布者不阻塞,解耦,但实时性降低、需处理失败。
  • 事件总线:发布-订阅的本地实现,集中管理订阅与分发,支持同步/异步、过滤、优先级。发布者发事件到总线,总线按订阅分发。
  • 内存泄漏(未解绑):观察者/订阅者若被主题/总线持有强引用,且未正确解绑(unsubscribe/remove),则对象无法被 GC 回收,造成内存泄漏。尤其在长生命周期对象(如 Spring 单例、监听器)持有短生命周期对象(如界面组件)时。解决:订阅后及时解绑、用 WeakReference 弱引用、事件总线自动清理失效订阅者。

同步/异步是通知模式,事件总线是发布-订阅载体。未解绑订阅导致内存泄漏,需主动解绑或用弱引用/自动清理。

#

19. 模板方法模式在框架基类(如 JdbcTemplate 的回调模板)中的应用,为什么回调比子类覆写更灵活?

请解释模板方法模式在框架基类(如 JdbcTemplate 的回调模板)中的应用,说明为什么回调比子类覆写更灵活?

  • 模板方法 + 回调
  • JdbcTemplate 的回调模板
  • 回调 vs 子类覆写的灵活性
  • 模板方法模式:父类定义算法骨架,子类覆写可变步骤。JdbcTemplate 用模板方法封装"获取连接→执行 SQL→处理结果→释放连接"的固定流程,可变部分(SQL 执行、结果转换)交给回调。
  • 回调模板:JdbcTemplate 提供 execute(StatementCallback)query(PreparedStatementCallback, RowMapper) 等方法,把"可变步骤"作为回调参数传入,框架跑固定流程,调用回调完成可变部分。这避免了继承,用组合(回调)注入可变逻辑。
  • 为什么回调比子类覆写更灵活:回调用组合(传入函数/对象),无需继承框架类,一个类可复用多个回调、可结合 lambda(更简洁);子类覆写要继承框架类,强耦合父类、每个场景要一个新子类、无法复用。回调解耦、灵活、可组合,更符合"组合优于继承"。
  • 应用:JdbcTemplate、RestTemplate、MyBatis 的 SqlSessionTemplate 等都用回调模板。

模板方法 + 回调:框架固定流程,回调注入可变部分。回调基于组合(lambda 简洁、可复用)比继承子类覆写更灵活,避免强耦合框架类。