Java 后端真实面试专题 · 设计模式篇
真实面经里"用过哪些设计模式""手写单例""策略+工厂怎么实现"出现极频繁。每题三段: ① 标准答(讲透 + 关键代码思路)→ ② 拓展(成体系带出关联点)→ ③ 怎么接到你自己的项目。 ⭐ 设计模式的答题精髓:别只背定义,要说"解决什么问题、不用会怎样、项目哪里用了"。
年限标签:
🟢 3年内🔴 3年+
1. 🟢 设计模式分哪几类?六大设计原则(SOLID)是什么?
标准答:23 种模式分三类——创建型(单例、工厂、建造者、原型)、结构型(代理、适配器、装饰器、外观、享元)、行为型(策略、责任链、观察者、模板方法、迭代器)。 六大原则:单一职责、开闭原则(对扩展开放对修改关闭)、里氏替换(子类能替父类)、依赖倒置(依赖抽象)、接口隔离、迪米特(最少知道)。
拓展:
- "最核心的原则?"——开闭原则,设计模式大多是为了实现它(新增功能不改老代码)。
- 设计模式是手段、SOLID 是目标,别为了用模式而用。
往项目引 ⭐:"我项目多渠道支付遵循开闭原则——新增渠道只加一个实现类、不改原有代码。我答设计模式会先讲原则、再讲用哪个模式实现,体现是真懂不是背。"
2. 🟢 手写一个单例模式?有哪几种写法?
标准答:保证一个类只有一个实例。常见写法:
- 饿汉式:类加载时就创建(
private static final Instance = new),线程安全但不懒加载。 - 懒汉式(双重检查锁 DCL):用到才创建,加 synchronized + volatile:
private static volatile Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) instance = new Singleton(); } } return instance; } - 静态内部类:懒加载 + 线程安全(靠类加载机制),优雅。
- 枚举:最简洁、天然防反射和序列化破坏(《Effective Java》推荐)。
拓展:
- "DCL 为什么要 volatile?"——
new不是原子的(分配内存→初始化→赋引用,可能重排),不加 volatile 可能拿到"半初始化"对象。 - "为什么双重检查?"——外层判空避免每次加锁、内层判空保证只创建一次。
- 反射和序列化能破坏单例,枚举能防。
往项目引 ⭐:"我项目里 Spring 的 Bean 默认就是单例(容器管理);自己写工具类单例用静态内部类或枚举。被要求手写时我能写出 DCL 并讲清为什么加 volatile——这是高频手撕题。"
3. 🟢 策略模式是什么?怎么实现?解决什么问题?
标准答:定义一系列算法、封装成独立的策略类、可互相替换。解决"大量 if-else 判断不同分支逻辑"的问题。实现:定义策略接口 → 每种策略一个实现类 → 上下文持有策略、运行时选用。
拓展:
- "怎么避免 if-else 选策略?"——用 Map<类型, 策略> 或 Spring 把所有实现注入成 Map,按 key 直接取,这就是策略 + 工厂。
- 策略模式符合开闭原则——新增策略不改原代码。
往项目引 ⭐:"我项目多渠道支付/多种促销计算用策略模式——每个渠道/活动一个策略类实现统一接口,用 Map<String, PayStrategy> 按渠道 key 取对应策略,彻底干掉了一大堆 if-else。新增渠道只加实现类。"
4. 🟢 工厂模式?简单工厂、工厂方法、抽象工厂的区别?
标准答:
- 简单工厂:一个工厂类根据参数返回不同产品(有 if-else,不符合开闭)。
- 工厂方法:每个产品对应一个工厂,新增产品加工厂、不改原代码。
- 抽象工厂:生产"一族"相关产品。 核心目的:把对象创建和使用解耦,使用方不关心怎么 new。
拓展:
- 工厂常和策略配合(工厂负责"造/选"策略对象)。
- "有没有不修改工厂就能新增的?"——用 Spring 容器把实现注入成 Map,新增实现自动进 Map,工厂不用改。
往项目引 ⭐:"我项目用策略 + 工厂——工厂用 Spring 注入的 Map<String, Strategy> 按 key 返回策略,新增策略类自动被 Spring 收集进 Map,工厂代码一行不改。这就是面试官常追的'不改工厂新增'。"
5. 🔴 责任链模式是什么?用在什么场景?
标准答:把多个处理者连成一条链,请求沿链传递、每个处理者决定自己处理还是传给下一个。解决"多个处理步骤、且步骤可灵活增减/排序"的问题。
拓展:
- 经典应用:Servlet 过滤器链、Spring Security 过滤器链、网关多级校验、风控多级规则、审批流。
- 好处:处理者解耦、链可配置。
往项目引 ⭐:"我项目下单前的多级校验(参数校验 → 风控 → 库存 → 优惠券)用责任链——每个校验一个 Handler、串成链,新增一个校验只加一个 Handler 插进链里,不动其他。和 Spring Security 过滤器链是一个思路。"
6. 🟢 观察者模式是什么?
标准答:定义对象间一对多依赖,被观察者状态变化时自动通知所有观察者。解耦"事件源"和"事件处理者"。
拓展:
- Spring 的事件机制(ApplicationEvent + Listener)就是观察者模式。
- 和 MQ 的发布订阅思想一致(MQ 是跨进程的观察者)。
往项目引 ⭐:"我项目用 Spring 的事件机制(观察者)解耦——比如'用户注册成功'发一个事件,发券、发欢迎消息各自监听处理,注册逻辑不用关心后续动作。跨服务时就升级成 MQ 发布订阅。"
7. 🟢 模板方法模式是什么?
标准答:在父类(抽象类)定义算法的固定骨架,把可变的步骤留成抽象方法由子类实现。解决"流程固定、部分步骤不同"的问题。
拓展:
- JDBC 的 JdbcTemplate、Spring 的很多 Template 类都是。
- 和策略模式区别:模板方法用继承定骨架、策略用组合换算法。
往项目引 ⭐:"我项目里不同类型的数据导入流程一样(校验 → 解析 → 入库 → 回执),只是解析逻辑不同,就用模板方法——抽象类定流程、子类只实现解析步骤,复用了公共流程。"
8. 🔴 代理模式?静态代理和动态代理的区别?
标准答:代理模式用一个代理对象控制对目标对象的访问,能在调用前后加逻辑(不改目标)。
- 静态代理:手写代理类,每个目标类要写一个代理,繁琐。
- 动态代理:运行时生成代理——JDK 动态代理(基于接口)、CGLIB(基于继承子类)。
拓展:
- Spring AOP 就是动态代理——事务、日志、权限都靠它织入。
- JDK 代理要接口、CGLIB 不要但不能代理 final。
- 这题常和 AOP 连着问。
往项目引 ⭐:"我项目用 AOP(动态代理)做统一日志、幂等、限流——本质就是代理在方法前后插逻辑、不侵入业务。理解'静态代理繁琐、动态代理运行时生成'就懂了 Spring AOP。"
9. 🟢 装饰器模式是什么?和代理有什么区别?
标准答:动态地给对象增加功能,通过"包装"一层层叠加。和代理的区别:装饰器关注"增强功能"(多层叠加),代理关注"控制访问"(如权限、延迟加载),结构相似但意图不同。
拓展:
- 经典例子:Java IO 流(
new BufferedReader(new InputStreamReader(...))一层层包装增强)。
往项目引 ⭐:"Java IO 的流就是装饰器——基础流外面包 Buffered 增强缓冲。我项目读写文件就是这样一层层包装。理解它就懂了 IO 流为什么那样嵌套写。"
10. 🟢 建造者模式是什么?
标准答:把复杂对象的构建过程分步骤,链式调用、最后 build 出对象,解决"构造参数太多、可选参数多"的问题,比一堆构造器重载清晰。
拓展:
- Lombok 的
@Builder、StringBuilder、各种 Config 的链式构建都是。
往项目引 ⭐:"我项目里参数多的对象(如查询条件、配置对象)用 @Builder 链式构建,比写一堆构造器或 set 清晰、还能保证不可变。"
11. 🟢 你项目里用了哪些设计模式?(必问,最该准备)
标准答:结合场景说 2-3 个,每个讲"用在哪、解决什么、不用会怎样"。最常用:策略 + 工厂(多渠道/多类型,干掉 if-else)、模板方法(固定流程)、责任链(多级校验)、观察者(事件解耦)、代理(AOP 横切)。
拓展:
- 面试官会追"为什么用这个、有什么缺点"。
- 别报一堆名字,深讲一两个真实用过的。
往项目引 ⭐:"我重点讲策略 + 工厂——多渠道支付每个渠道一个策略、工厂按渠道返回,符合开闭原则,新增渠道不改老代码。能讲清'解决了 if-else 膨胀、符合开闭原则',比报 23 种模式名强十倍。"
12. 🔴 Spring 里用到了哪些设计模式?
标准答:工厂(BeanFactory)、单例(Bean 默认单例)、代理(AOP)、模板方法(JdbcTemplate/RestTemplate)、观察者(事件机制)、适配器(HandlerAdapter)、装饰器(BeanWrapper)、责任链(拦截器/过滤器链)。
拓展:
- 能结合具体类讲,比报名词强。
- 学框架就是学设计模式的最佳实践。
往项目引 ⭐:"我学 Spring 时特意去对应它用的模式——这让我自己写代码也会用:跟 Spring 学了'面向接口 + 容器管理 + 策略工厂',应用到我项目的多渠道扩展上。"
13. 🟢 开闭原则是什么?为什么重要?
标准答:对扩展开放、对修改关闭——新增功能时通过扩展(加新类)实现,而不是修改已有代码。重要性:改老代码风险大(可能引入 bug、影响已上线功能),扩展更安全。
拓展:
- 策略、工厂、模板方法、责任链都是为了实现开闭。
- 实现手段:抽象 + 多态 + 配置化。
往项目引 ⭐:"我项目所有'可能新增类型'的地方都按开闭设计——支付渠道、促销类型、导入格式都是接口 + 实现,新增只加类。这样上线新功能基本不动核心代码,回归风险小。"
14. 🟢 里氏替换原则是什么?
标准答:子类必须能完全替换父类而不破坏程序——子类不改变父类原有行为的契约(不削弱前置条件、不强化后置条件)。是继承的约束。
拓展:
- 违反例子:子类重写方法却抛了父类没声明的异常、或改变了原有语义。
- 它是多态正确工作的前提。
往项目引 ⭐:"我项目里用接口/抽象类 + 多实现时遵守里氏替换——任何实现都能无差别替换,调用方面向接口编程不用关心具体实现。这是策略模式能成立的基础。"
15. 🔴 怎么手动往 Spring 容器注册一个 Bean?
标准答:几种方式——@Bean 方法、@Component 扫描、实现 BeanDefinitionRegistryPostProcessor 动态注册 BeanDefinition、用 DefaultListableBeanFactory.registerBeanDefinition 编程式注册、@Import 导入。
拓展:
- 动态注册常用于框架集成(如根据配置注册一批 Bean)。
- MyBatis 的 Mapper 就是动态注册的(MapperScannerConfigurer)。
往项目引 ⭐:"我项目做过根据配置动态注册一批处理器 Bean——用 BeanDefinitionRegistryPostProcessor 在启动时注册。理解手动注册让我能做这种'按配置动态装配'的扩展,也看懂了 MyBatis Mapper 怎么变成 Bean 的。"
16. 🟢 单例模式怎么防止被反射和序列化破坏?
标准答:
- 反射破坏:反射能
setAccessible(true)调私有构造器创建新实例。防:构造器里判断已有实例就抛异常。 - 序列化破坏:反序列化会创建新对象。防:定义
readResolve()返回单例。 - 枚举天然防这两种破坏(JVM 保证),所以是最安全的单例写法。
拓展:
- 这是《Effective Java》推荐用枚举做单例的原因。
- 一般业务用 Spring 单例 Bean 就够,不用纠结这些。
往项目引 ⭐:"我项目单例基本交给 Spring 管理;需要绝对单例(防反射序列化)的少数场景用枚举。能答出'枚举防破坏'说明你理解得透。"
17. 🟢 适配器模式是什么?
标准答:把一个类的接口转换成客户期望的另一个接口,让原本不兼容的类能一起工作。解决"接口不匹配"的复用问题。
拓展:
- 例子:SpringMVC 的 HandlerAdapter(适配不同类型的 Controller)、各种第三方 SDK 的封装适配。
往项目引 ⭐:"我项目对接多个第三方(短信、支付)服务,它们接口各不相同,我用适配器统一成自己的接口——上层调用统一、换供应商只改适配器。"
18. 🔴 为什么说"组合优于继承"?
标准答:继承是强耦合(子类依赖父类实现、父类改动影响子类、单继承限制),且容易破坏封装。组合(持有对象、委托调用)更灵活、低耦合、能运行时替换。所以优先用组合 + 接口。
拓展:
- 策略模式就是用组合替代继承(持有策略对象而非继承)。
- 继承表达"is-a"、组合表达"has-a",按语义选。
往项目引 ⭐:"我项目里能用组合就不用继承——比如功能扩展用持有 + 委托(策略/装饰)而不是继承一堆子类,避免继承层级太深、改父类牵连一片。"
你能答到第几层?
- 三段都能答、还能讲清项目用法和解决的问题:你对设计模式是真懂不是背。
- 标准答 + 拓展能成体系答:知识够,差把它绑到一个真实用过的场景。
- 标准答都磕巴:设计模式靠理解原则(尤其开闭)+ 在项目里用一两个(策略+工厂最实用),别死背 23 种。
这是面试专题的「设计模式篇」,网站上还有并发、MySQL、Redis、Spring、微服务、消息队列、JVM、安全认证、Java基础、计基与Linux、项目场景等系统整理。 🌐 更多真实面试专题与资料:smallredtech.com 💬 想系统学 / 简历与辅导咨询,加微信:Ahongbb666(备注「面试题」)