外观
设计模式 速查卡
🎯 覆盖 18 题 | ⭐ 高频 9 题 | 预计扫描 9 分钟 📌 先看⭐一句话答案 → 展开要点 → 自测清单检验
一、基础与原则
知识地图:SOLID 六原则 → 23 种 GoF 模式(创建型 / 结构型 / 行为型)→ Spring 中的设计模式
⭐ SOLID 六大原则
一句话: SRP 单一职责 / OCP 开闭 / LSP 里氏替换 / ISP 接口隔离 / DIP 依赖倒置 + 迪米特法则——核心三句话:封装变化、多用组合少用继承、面向接口编程。
| 原则 | 一句话 | 示例 |
|---|---|---|
| SRP 单一职责 | 一个类只负责一件事 | Controller / Service / DAO 分层 |
| OCP 开闭 | 对扩展开放,对修改关闭 | 策略模式新增策略不改调用方 |
| LSP 里氏替换 | 子类能替换父类且行为正确 | List list = new ArrayList() |
| ISP 接口隔离 | 接口小而专,不强迫依赖 | UserQueryService / UserCommandService 拆分 |
| DIP 依赖倒置 | 依赖抽象不依赖实现 | @Autowired 注入接口类型 |
| LoD 迪米特 | 只与直接朋友通信 | Facade 隐藏子系统复杂性 |
⭐ Spring 中的设计模式
一句话: Spring 是模式集大成者——工厂(BeanFactory) / 代理(AOP) / 模板方法(JdbcTemplate) / 观察者(Event) 是面试必讲四个,再加单例 / 策略 / 适配器。
| 模式 | Spring 体现 | 关键词 |
|---|---|---|
| 工厂 | BeanFactory / ApplicationContext | IoC 容器本身就是工厂 |
| 单例 | Bean 默认 singleton | 容器保证唯一 |
| 代理 | AOP(JDK / CGLIB) | @Transactional / @Async |
| 模板方法 | JdbcTemplate / RedisTemplate | 骨架固定,用户传回调 |
| 观察者 | ApplicationEvent + @EventListener | 发布-订阅 |
| 策略 | Resource 接口(ClassPath/Url/FileSystem) | 按前缀选策略 |
| 适配器 | HandlerAdapter(MVC) | 统一调用不同 Controller |
| 责任链 | Spring Security Filter 链 | 逐个 Filter 放行 |
二、创建型模式
⭐ 单例四种写法 + DCL 为什么要 volatile
一句话: 四种写法各有取舍;DCL 必须 volatile 是因为 new 分三步可能指令重排,导致其他线程拿到未初始化对象。
| 写法 | 线程安全 | 懒加载 | 防反射 | 推荐度 |
|---|---|---|---|---|
| 饿汉式 | 是(JVM 类加载) | 否 | 否 | ★★★ |
| DCL | 是(需 volatile) | 是 | 否 | ★★★★ |
| 静态内部类 | 是(内部类加载) | 是 | 否 | ★★★★★ |
| 枚举 | 是(JVM 保证) | 否 | 是 | ★★★★★ |
DCL volatile 原理:
new Singleton() 三步:①分配内存 → ②初始化对象 → ③引用指向内存
JVM 可能重排为 ①→③→② → 线程B看到非null但未初始化的对象 → NPE
volatile 内存屏障禁止重排,保证 ①→②→③⚠️ 易错:枚举是 Effective Java 推荐的终极写法,天然防反射 + 反序列化破坏
⭐ 三种工厂对比
一句话: 简单工厂一个类集中创建(违反 OCP);工厂方法一产品一工厂(OCP);抽象工厂一族一工厂(产品族一致性)。
| 维度 | 简单工厂 | 工厂方法 | 抽象工厂 |
|---|---|---|---|
| 工厂数量 | 1 个 | 多个(一产品一工厂) | 多个(一族一工厂) |
| 产品维度 | 多种产品 | 单一产品 | 产品族 |
| 新增产品 | 改工厂代码 | 新增工厂子类 | 改所有工厂(难) |
| 新增产品族 | — | — | 新增工厂子类(易) |
| 开闭原则 | 违反 | 遵守 | 部分遵守 |
| Spring 例 | BeanFactory.getBean(name) | FactoryBean 接口 | — |
💡 工厂方法是抽象工厂的特例——产品族只有一种产品时退化为工厂方法
三、结构型模式
⭐ 代理模式三种实现
一句话: 静态代理手写、JDK 动态代理基于接口(反射)、CGLIB 基于继承(字节码子类);Spring Boot 2.x+ 默认 CGLIB。
| 维度 | 静态代理 | JDK 动态代理 | CGLIB |
|---|---|---|---|
| 实现 | 手写代理类 | Proxy + InvocationHandler | 字节码生成子类 |
| 要求 | 相同接口 | 必须有接口 | 不能是 final |
| 性能 | 编译期确定,最快 | 反射调用,较慢 | 字节码直调,略快于 JDK |
| Spring | — | 有接口时默认 | Boot 2.x+ 默认 |
Spring AOP 选择逻辑:
有接口 → 默认 JDK 动态代理
无接口 → CGLIB
Boot 2.x+ → 全局默认 CGLIB(proxy-target-class=true)⭐ 装饰器模式(Java IO)
一句话: 装饰器在不改变接口的前提下动态增强功能;Java IO 是最经典案例——FileInputStream 被 Buffered / Data 层层包装。
InputStream(抽象组件)
├── FileInputStream(具体组件)
└── FilterInputStream(装饰器基类)
├── BufferedInputStream → 增加缓冲
├── DataInputStream → 增加读基本类型
└── PushbackInputStream → 增加回退java
// 装饰器层层包装
new DataInputStream(new BufferedInputStream(new FileInputStream("data.bin")))
// File: 读字节 → Buffered: +缓冲 → Data: +readInt/readDouble装饰器 vs 代理: 装饰器增强功能不改语义;代理控制访问(权限 / 远程调用)
装饰器 vs 继承: 装饰器运行时动态组合,继承编译期静态确定 → 装饰器避免类爆炸
四、行为型模式
⭐ 策略模式替代 if/else
一句话: 定义策略接口 → 每种算法一个实现类 → Map/枚举做路由分发,彻底消除 if/else,符合开闭原则。
三板斧:
① 定义接口 NotifyStrategy
② 每种渠道一个实现类 @Component("sms") / @Component("email")
③ Map 分发:@Autowired Map<String, NotifyStrategy> → get(type).send()Java 中的策略实例:
Comparator→ 不同比较策略传给 Collections.sort()ThreadPoolExecutor拒绝策略 → Abort / CallerRuns / Discard- Spring
Resource→ ClassPath / FileSystem / Url
⭐ 观察者模式 + Spring Event
一句话: 一对多依赖,Subject 状态变化自动通知所有 Observer;Spring Event = ApplicationEventPublisher 发布 + @EventListener 订阅。
Spring Event 三步:
① 定义事件:class OrderCreatedEvent extends ApplicationEvent
② 发布事件:publisher.publishEvent(new OrderCreatedEvent(...))
③ 监听事件:@EventListener public void onOrderCreated(OrderCreatedEvent e)| 维度 | 说明 |
|---|---|
| 默认同步 | 监听器在发布者线程执行 |
| 异步 | @Async + @EnableAsync |
| 底层 | ApplicationEventMulticaster 维护 Listener 列表,遍历调用 |
⚠️ 观察者过多时同步通知会阻塞;可能引发循环依赖
⭐ 通知系统场景题(四种模式协作)
一句话: 观察者解耦事件触发 → 工厂根据类型获取策略 → 策略封装各渠道发送逻辑 → 模板方法统一流程(校验→组装→发送→日志)。
事件触发 ──观察者──▶ Listener ──工厂──▶ StrategyFactory
│
┌────────────┼────────────┐
▼ ▼ ▼
Sms策略 Email策略 Push策略 ← 策略模式
│ │ │
└────────────┼────────────┘
▼
AbstractNotifyTemplate ← 模板方法
(校验→组装→发送→日志)💡 扩展性:新增渠道只需实现接口 + 注册 Bean,符合开闭原则
补充速览
| 题号 | 关键词 | 核心答案 |
|---|---|---|
| Q2 | 三大分类 | 创建型(对象创建) / 结构型(类组合) / 行为型(通信职责);高频 8 个:单工建 + 代装适 + 策观模 |
| Q6 | Builder | 复杂对象多可选参数用 Builder 链式构建;vs 工厂:Builder 关注构建过程,Factory 关注创建结果 |
| Q7 | 原型/深拷贝 | clone() 默认浅拷贝(引用共享);深拷贝:手动递归 clone 或序列化/反序列化 |
| Q10 | 适配器 | 接口不兼容时做转换;HandlerAdapter 统一调用不同类型 Controller;InputStreamReader 字节→字符 |
| Q11 | 责任链 | 请求沿链传递,每个节点决定处理或转发;Servlet Filter / Security Filter / Netty Pipeline |
| Q13 | 模板方法/AQS | 父类定骨架(final)子类实现钩子;AQS: acquire(模板) 调 tryAcquire(子类);ReentrantLock/Semaphore |
| Q15 | 状态 vs 策略 | 状态:内部自动切换(客户透明);策略:外部指定(客户端决定) |
| Q17 | 重构 if/else | 识别变化点 → 策略接口 → 每分支一个类 → Map 路由 → 公共流程加模板方法 |
| Q18 | 项目实战 | 准备 3-4 个:单例(Bean) / 策略(多解析器) / 模板方法(导入流程) / 观察者(事件解耦) |
🧠 助记汇总
| 口诀 | 含义 |
|---|---|
| 单工建 代装适 策观模 | 高频 8 模式:单例/工厂/建造者 + 代理/装饰器/适配器 + 策略/观察者/模板方法 |
| 封组接 | 设计模式三大思想:封装变化 / 多用组合少用继承 / 面向接口编程 |
| 饿懒静枚 | 单例四写法:饿汉式 / 懒汉DCL / 静态内部类 / 枚举 |
| 分初指 | DCL 重排问题:分配内存 / 初始化 / 指向引用——可能 1→3→2 |
| 接策图 | 策略模式三板斧:接口 / 策略实现类 / Map 路由分发 |
| 定发监 | Spring Event 三步:定义事件 / 发布事件 / 监听事件 |
| 观工策模 | 通知系统四模式协作:观察者/工厂/策略/模板方法 |
✅ 自测清单
| # | 问题 | 你能说出... |
|---|---|---|
| 1 | SOLID 原则 | 6 个原则名称 + 各举一个 Spring 示例 |
| 2 | Spring 设计模式 | 至少 4 个模式 + 对应 Spring 组件 |
| 3 | 单例写法 | 4 种写法对比 + DCL 为什么要 volatile |
| 4 | 三种工厂 | 简单/方法/抽象的区别 + 新增产品谁违反 OCP |
| 5 | 代理模式 | 静态/JDK/CGLIB 对比 + Spring 如何选择 |
| 6 | 装饰器 | Java IO 的装饰器结构 + 与代理/继承的区别 |
| 7 | 策略模式 | 替代 if/else 的三板斧 + Java 中的实例 |
| 8 | 观察者 | Spring Event 三步 + 同步/异步区别 |
| 9 | 通知系统场景 | 四种模式如何协作 + 扩展性如何保证 |
💡 首次全部过一遍 → 第2天只过答不上来的 → 第4天再复习 → 面试前一天最后扫一遍