设计模式面试手册
面向后端 / Java 面试(原则与结构同样适用于 Go、Kotlin、TypeScript)。目标不是背定义,而是:一句话说清问题、讲清取舍、能落到代码和项目。
0. 面试官真正在听什么
问「你了解设计模式吗」通常不是考百科,而是三件事:
- 你能不能把复杂设计讲简单(抽象能力)
- 你知不知道什么时候不该用(工程判断)
- 你有没有在真实系统里做过权衡(项目可信度)
推荐答题结构(STAR 的设计版):
当时遇到 什么变化会频繁发生 → 如果硬写会 怎样爆炸 → 用 哪个模式 把变化点隔离 → 代价 是什么 → 线上/代码里 具体在哪
一句话原则:
模式解决的是「变化点」:把变的和不变的分开。
1. 先过设计原则(比 23 种更常被追问)
SOLID
| 原则 | 一句话 | 面试怎么说 | 反例 |
|---|---|---|---|
| S 单一职责 | 一个类只有一个引起它变化的原因 | Service 不要既算价格又发短信 | UserService 里写 Excel 导出、发邮件、扣库存 |
| O 开闭 | 对扩展开放,对修改关闭 | 加新策略不改老代码 | 每加一种支付就改 if-else 巨石 |
| L 里氏替换 | 子类必须能替换父类 | 不要削弱父类契约 | Square extends Rectangle 改了 setWidth 语义 |
| I 接口隔离 | 不要强迫依赖用不到的方法 | 拆小接口 | Animal 上既有 fly() 又有 swim() |
| D 依赖倒置 | 依赖抽象,不依赖具体 | 高层不 new 底层 | OrderService 直接 new MysqlOrderRepo() |
常一起考的
- DRY:重复的是知识/规则,不是两行长得像的代码。乱抽公共方法比重复更糟。
- KISS / YAGNI:没变化就不要上模式。面试里主动说「这里用策略是过度设计」会加分。
- 迪米特(最少知识):只和直接朋友说话,避免
a.getB().getC().foo()。 - 组合优于继承:继承是编译期耦合;装饰、策略、桥接都是组合。
- 高内聚低耦合:模式的目的几乎都是降耦合。
高频追问:「开闭原则和策略模式什么关系?」
答:策略把变化封装成可替换对象,新增策略不必改上下文,是开闭的典型落地。
2. 23 种总览(先建立地图)
按 变化点 记,比按名字记牢。
创建型(怎么 new,别把 new 散落得到处都是)
| 模式 | 隔离的变化 | 一句话 |
|---|---|---|
| 单例 Singleton | 实例个数 | 全局唯一访问点 |
| 工厂方法 Factory Method | 产品类 | 子类决定创建哪个 |
| 抽象工厂 Abstract Factory | 产品族 | 一次创建一套互相匹配的产品 |
| 建造者 Builder | 构造步骤 / 可选参数 | 分步组装复杂对象 |
| 原型 Prototype | 创建成本 | 克隆已有对象 |
结构型(怎么把类/对象拼起来)
| 模式 | 隔离的变化 | 一句话 |
|---|---|---|
| 适配器 Adapter | 接口不匹配 | 把已有接口转成客户要的 |
| 桥接 Bridge | 抽象与实现两维变化 | 两个维度独立扩展 |
| 组合 Composite | 树形部分-整体 | 叶子和容器统一对待 |
| 装饰器 Decorator | 职责叠加 | 运行时套功能 |
| 外观 Facade | 子系统复杂度 | 提供一把 simplification 门面 |
| 享元 Flyweight | 大量细粒度对象内存 | 共享不变状态 |
| 代理 Proxy | 访问方式 | 控制/增强真实对象的访问 |
行为型(对象之间怎么协作、怎么分配职责)
| 模式 | 隔离的变化 | 一句话 |
|---|---|---|
| 职责链 Chain of Responsibility | 谁处理 | 请求沿着链找处理者 |
| 命令 Command | 请求本身 | 把请求封装成对象 |
| 解释器 Interpreter | 文法 | 用类表示语法规则(面试少写,能辨认即可) |
| 迭代器 Iterator | 遍历方式 | 统一访问聚合,不暴露内部 |
| 中介者 Mediator | 网状依赖 | 同事只和中介通信 |
| 备忘录 Memento | 状态快照 | 不破坏封装地存档/回滚 |
| 观察者 Observer | 通知名单 | 一改多知 |
| 状态 State | 对象状态 | 状态对象化,行为随状态变 |
| 策略 Strategy | 算法/规则 | 可互换的算法族 |
| 模板方法 Template Method | 算法骨架中的某几步 | 父类定骨架,子类填步骤 |
| 访问者 Visitor | 作用于稳定结构上的新操作 | 双分派,给对象结构加操作 |
面试加分句:
「创建型管对象从哪来,结构型管对象怎么包,行为型管对象怎么说话。」
3. 创建型详解
3.1 单例 Singleton
问题: 配置中心、线程池、ID 生成器这类东西,进程内只该有一份。
要点(Java 面试几乎必追线程安全):
// 1) 饿汉:类加载时创建,简单线程安全,可能浪费
public enum ConfigHolder { INSTANCE; } // 推荐:防反序列化、反射
// 2) 双重检查 + volatile(面试常写)
public class LazyHolder {
private static volatile LazyHolder inst;
public static LazyHolder get() {
if (inst == null) {
synchronized (LazyHolder.class) {
if (inst == null) inst = new LazyHolder();
}
}
return inst;
}
}
// 3) 静态内部类(懒加载 + 类加载线程安全)
public class Holder {
private static class H { static final Holder I = new Holder(); }
public static Holder get() { return H.I; }
}
为什么 volatile: 防止指令重排,别的线程看到「半初始化」对象。
代价 / 别滥用:
- 全局状态,测试难 mock
- 隐藏依赖,变成「超级缓存」
- 集群下 JVM 单例 ≠ 全局唯一(分布式要用 Redis / DB 约束)
真实例子: Spring 默认 Bean 是单例(容器管生命周期,比自己写单例更合适);Runtime.getRuntime();日志框架的 LoggerFactory。
追问: Spring 单例是线程安全的吗?
答:Bean 是单例,但 Bean 里的字段 不是。无状态 Service 可以单例;有可变成员要自己同步或改成 prototype / ThreadLocal。
3.2 简单工厂(非 GoF,但必考)
Payment p = PaymentFactory.create(type); // switch(type)
静态方法集中创建。违反开闭(每加类型改工厂)。小型、类型稳定时可用。引出工厂方法和策略。
3.3 工厂方法 Factory Method
问题: 创建哪个产品会变,但使用产品的流程不变。
结构: Creator.createProduct() 由子类实现;客户只依赖产品接口。
interface Product { void use(); }
abstract class Creator {
abstract Product create();
void doWork() { create().use(); } // 稳定流程
}
和简单工厂差在哪: 简单工厂一个类里 switch;工厂方法把「选哪个」下放到子类,符合开闭。
例子: Calendar.getInstance();日志 LoggerFactory.getLogger;Spring FactoryBean;集合 iterator() 也是工厂方法味道。
3.4 抽象工厂 Abstract Factory
问题: 要创建的是 一套配套产品(产品族),不能混搭。
例:WindowsFactory 产出 WindowsButton + WindowsScroll;MacFactory 产出 Mac 一套。客户拿 AbstractFactory,从不会 Windows 按钮配 Mac 滚动条。
和工厂方法: 工厂方法是「一个产品」的升级;抽象工厂是「多个相关产品」的工厂方法集合。
例子: JDBC 的 Connection 能产出配套的 Statement / ResultSet(同一厂商驱动);UI 主题;跨云 SDK 的一组客户端。
追问: 新加一种产品(产品等级)怎么办?
答:抽象工厂所有工厂都要加方法,产品等级扩展难、产品族扩展易。反过来工厂方法更好扩展单一产品类型。
3.5 建造者 Builder
问题: 对象参数多、有必填/选填、构造过程有顺序约束。
User u = User.builder()
.id(1)
.name("Ada")
.email("a@x")
.build();
和工厂: 工厂关注 造哪个类;建造者关注 怎么把同一个复杂对象填满。
例子: StringBuilder(更像流畅接口);Lombok @Builder;MyBatis SqlSessionFactoryBuilder;HTTP 请求 Request.Builder。
面试别把 Builder 和「链式 setter」混为一谈: 真 Builder 通常有 build() 做校验,可生成不可变对象。
3.6 原型 Prototype
问题: 创建成本高(深拷贝配置、游戏单位、报表模板),或要避开复杂构造。
Java:Cloneable + clone()。面试重点是 浅拷贝 vs 深拷贝。
例子: Object.clone;Spring BeanDefinition 的拷贝;原型 Bean(scope=prototype 每次都是新实例,名字像但不是 GoF 原型)。
讲清楚:GoF 原型是「克隆已有实例」;Spring prototype scope 是「每次向容器要都 new」,机制不同。
4. 结构型详解
4.1 适配器 Adapter
问题: 现成类接口对不上,又不能改对方(第三方、遗留)。
- 对象适配器(组合,推荐): Adapter 持有 Adaptee,实现 Target
- 类适配器(继承,Java 少见,单继承限制)
interface Target { void request(); }
class Adapter implements Target {
private final Legacy adaptee;
public void request() { adaptee.oldCall(); }
}
例子: InputStreamReader(字节流 → 字符流);Arrays.asList;Spring MVC HandlerAdapter;SLF4J 适配各种日志实现。
4.2 桥接 Bridge
问题: 有 两个独立变化维度,用继承会类爆炸(形状 × 颜色 × 渲染引擎)。
把「抽象」和「实现」拆开,用组合连起来。
Shape ──持有──> DrawingAPI
Circle / Rect VectorAPI / RasterAPI
和适配器: 适配器是事后补丁(已有接口不匹配);桥接是事先设计(预防两维耦合)。
例子: JDBC:应用代码面向 Driver 抽象,具体 MySQL/PG 驱动是实现;图形 UI。
4.3 组合 Composite
问题: 树形结构,客户想对叶子和容器调用同一套操作。
Component ← Leaf / Composite(children)
例子: 文件系统目录;GUI 控件树;组织架构;菜单/权限树;DOM。
追问: 透明 vs 安全?
透明:Component 上就有 add/remove,叶子空实现或抛异常。安全:只有 Composite 有子节点方法,客户要转型。
4.4 装饰器 Decorator
问题: 给对象动态加职责,且要能叠加,避免子类爆炸。
装饰器和被装饰者实现同一接口,构造器包一层,调用前后加行为。
InputStream in = new BufferedInputStream(new GZIPInputStream(new FileInputStream(f)));
例子: Java IO 全家桶;Servlet HttpServletRequestWrapper;Spring TransactionInterceptor 这类增强(和代理常一起出现);Java I/O、Collections 的 unmodifiableXxx 有装饰味道。
和继承: 继承是编译期静态加功能;装饰是运行时任意组合。
4.5 外观 Facade
问题: 子系统 API 又多又乱,给上层一个粗粒度入口。
例子: JdbcTemplate(盖住获取连接、异常翻译、关闭);公司网关对内封装一堆微服务;Slf4j 外观也可以这么讲。
和适配器: 适配器转换接口;外观降低复杂度、不一定换接口。
和中介者: 外观是单向简化(客户 → 子系统);中介者是同事之间互相协作的中枢。
4.6 享元 Flyweight
问题: 大量相似对象撑爆内存。把 内部状态(可共享) 和 外部状态(场景相关) 拆开。
例子: Java Integer 缓存 [-128, 127];字符串常量池;编辑器里字符的字形对象;游戏里树的网格共享。
追问: 享元一定不可变吗?
共享部分必须不可变(或线程安全),否则会互相踩。
4.7 代理 Proxy
问题: 不能/不想直接访问真实对象:远程、懒加载、权限、日志、事务、缓存。
| 种类 | 作用 |
|---|---|
| 虚拟代理 | 懒创建大对象 |
| 远程代理 | 本地代表远程(RPC stub) |
| 保护代理 | 权限 |
| 智能引用 | 引用计数、锁 |
| 静态代理 | 手写包装类 |
| 动态代理 | JDK 接口代理 / CGLIB 子类 / ByteBuddy |
// JDK 动态代理要求目标实现接口
Proxy.newProxyInstance(cl, new Class[]{Foo.class}, (p, m, args) -> {
// before
Object r = m.invoke(real, args);
// after
return r;
});
Spring AOP: 有接口默认 JDK 代理,否则 CGLIB。事务、缓存、异步都是代理。
装饰 vs 代理(必考):
| 装饰器 | 代理 | |
|---|---|---|
| 目的 | 增强功能,对客户透明叠加 | 控制访问,常隐藏真实对象 |
| 持有 | 通常对外暴露被装饰者,可多层 | 经常自己创建/独占 real subject |
| 接口 | 同一接口 | 同一接口 |
| 典型 | IO 套娃 | RPC、权限、懒加载、AOP |
可以说:结构很像,意图不同。AOP 两者味道都有,面试说「以代理为主,用来做横切」最稳。
5. 行为型详解
5.1 策略 Strategy
问题: 同一件事多种算法,运行时选,避免满屏 if-else。
Context 持有 Strategy,可注入/可替换。
interface Discount { Money apply(Order o); }
class VipDiscount implements Discount { ... }
class Context {
private Discount d;
Money pay(Order o) { return d.apply(o); }
}
例子: 比较器 Comparator;Spring 的 Resource 加载策略;支付渠道、计价、风控规则。
落地: Spring 里 Map<String, Discount> 注入所有实现,按 type 取。这是面试项目里最容易讲圆的模式。
开闭: 加新策略加类,不改老策略。上下文若用反射/配置注册,连上下文都不用改。
5.2 模板方法 Template Method
问题: 流程骨架稳定,某几步各不相同。
abstract class AbstractImporter {
public final void importFile(File f) { // 防篡改骨架
parse(f);
validate();
save();
hook(); // 可选钩子
}
abstract void parse(File f);
void hook() {}
}
例子: HttpServlet.service → doGet/doPost;JUnit 生命周期;Spring JdbcTemplate 回调;抽象类里的 ApplicationListener 流程。
策略 vs 模板(必考):
| 策略 | 模板方法 | |
|---|---|---|
| 复用手段 | 组合 | 继承 |
| 变化粒度 | 整段算法 | 算法中的步骤 |
| 耦合 | 松 | 继承,较紧 |
| 选择 | 优先策略(组合优于继承) | 骨架强约束时用模板 |
5.3 观察者 Observer / 发布订阅
问题: 一对多依赖,主体变了通知观察者,主体不需要知道观察者是谁。
interface Observer { void on(Event e); }
class Subject {
List<Observer> obs;
void setState(...) { obs.forEach(o -> o.on(e)); }
}
Observer vs Pub/Sub:
观察者:Subject 直接持有 Observer 列表(耦合在「谁订阅了我」)。
发布订阅:中间有 EventBus / MQ,发布者和订阅者完全解耦。面试可以说 Pub/Sub 是观察者的解耦升级。
例子: java.util.Observer(已过时);Swing 监听器;Spring ApplicationEvent + @EventListener;Guava EventBus;Kafka 是分布式 pub/sub。
追问: 同步还是异步?异常怎么办?通知顺序?
好回答:默认同步,阻塞发布者;可用线程池异步;要事务内一致性用同步并小心回滚;Spring @TransactionalEventListener(AFTER_COMMIT) 常被追问。
5.4 责任链 Chain of Responsibility
问题: 多个对象都可能处理请求,发送者不指定具体处理者。
Handler 里 if (can) handle else next.handle。
例子: 过滤器链 Servlet Filter / Spring HandlerInterceptor / Tomcat Pipeline;网关鉴权-限流-路由;审批流;异常处理链;日志 Level。
和装饰: 装饰强调叠加增强且通常都会执行;链强调「谁处理、能否短路」。Filter 两者都像,按「能 pass 也能 reject」讲成责任链更准。
5.5 命令 Command
问题: 把「请求」做成对象,从而能排队、撤销、日志、宏命令。
Command.execute() / undo();Invoker 不关心 Receiver。
例子: 线程池 Runnable/Callable 是最简命令;GUI 按钮;编辑器撤销栈;任务队列;Netflix Hystrix 命令;CQRS 里的 Command。
和策略: 策略是可替换算法,通常不记录历史;命令是一次操作的对象化,常带撤销/队列。
5.6 状态 State
问题: 对象行为随内部状态剧烈变化,if 状态码会膨胀。
每个状态一个类,handle(context) 里可能切换 context.setState(...)。
例子: 订单(待支付/已支付/已发货/退款中);TCP 状态机;工作流引擎。
状态 vs 策略: 策略由外部注入、彼此不知道;状态知道下一个状态是谁,转换规则在状态机内部。策略不改上下文身份;状态改变的是对象「现在是什么」。
5.7 迭代器 Iterator
问题: 遍历与容器实现解耦。
hasNext/next。增强 for、Stream 都建立在这套抽象上。
例子: Collection.iterator();MyBatis 游标;数据库 cursor。
知道 fail-fast(modCount)即可。
5.8 中介者 Mediator
问题: 对象网状引用(每个人都认识所有人),改成星型:只认识中介。
例子: UI 对话框各控件;机场塔台;聊天室服务器;MQ 某种程度上是分布式中介;Spring ApplicationContext 作为对象协作中心也可以浅提。
代价: 中介容易变成上帝类,要拆。
5.9 备忘录 Memento
问题: 快照 + 回滚,且不破坏封装(别把所有字段 public)。
Originator 创建 Memento;Caretaker 只保管不拆开。
例子: 编辑器撤销;事务回滚(数据库 undo);游戏存档;浏览器历史。
注意: 快照内存成本;深浅拷贝。
5.10 访问者 Visitor
问题: 对象结构稳定(多种元素),但操作经常新增。用双分派:element.accept(visitor) → visitor.visit(concrete)。
例子: 编译器 AST 上做类型检查/代码生成;IDE 重构;表达式树求值。
代价: 每加一种元素,所有 Visitor 都要改。结构常变就别用。面试能讲清双分派就够,现场写代码很少要求。
5.11 解释器 Interpreter
用类表示文法规则。SQL 引擎、正则、规则引擎有这味。业务里少手写。知道「适合简单文法,复杂请用解析器生成器」即可。
6. 面试最高频对比(背这一页)
6.1 工厂方法 vs 抽象工厂 vs 建造者 vs 简单工厂
- 简单工厂:一个类 switch,类型少
- 工厂方法:一个产品,子类决定
- 抽象工厂:一套产品族
- 建造者:同一个复杂对象的组装过程
6.2 适配器 vs 桥接 vs 外观 vs 代理 vs 装饰
| 模式 | 核心意图 |
|---|---|
| 适配器 | 接口转换(已有的变可用的) |
| 桥接 | 两维独立变化(预防类爆炸) |
| 外观 | 简化一堆子系统 |
| 代理 | 控制访问 |
| 装饰 | 叠加职责 |
口诀:适配补接口,桥接拆两维,外观做门面,代理看门,装饰添功能。
6.3 策略 vs 状态 vs 命令 vs 模板
- 策略:算法可互换,调用方选
- 状态:行为由内部状态驱动,自己转移
- 命令:请求对象化(队列/撤销)
- 模板:继承上的流程骨架
6.4 观察者 vs 中介 vs 责任链
- 观察者:1 对 N 通知
- 中介:N 对 N 协作改走中心
- 责任链:1 对「未知的那个处理者」,可短路
7. JDK / Spring 对照表(非常加分)
把模式落到「你每天用的 API」,比默写 UML 有用。
| 模式 | JDK / 语言 | Spring / 框架 |
|---|---|---|
| 单例 | Runtime、enum |
默认 Bean scope |
| 工厂方法 | Collection.iterator、Integer.valueOf |
BeanFactory、FactoryBean |
| 抽象工厂 | DocumentBuilderFactory、JDBC |
xxxTemplate 配套对象 |
| 建造者 | StringBuilder、Stream.Builder |
BeanDefinitionBuilder |
| 适配器 | InputStreamReader、Arrays.asList |
HandlerAdapter、AdviceAdapter |
| 装饰 | BufferedInputStream 等 IO |
HttpServletRequestWrapper、部分 Advice |
| 代理 | 动态 Proxy |
AOP、事务、@Async、OpenFeign |
| 外观 | java.util.logging 对 JUL |
JdbcTemplate、RestTemplate |
| 享元 | Integer 缓存、String intern |
作用不大,可提连接池是资源复用不是标准享元 |
| 组合 | Swing 组件树、NIO 少 | 菜单树、权限树业务代码 |
| 观察者 | PropertyChangeListener |
ApplicationEvent |
| 策略 | Comparator、ThreadPoolExecutor 拒绝策略 |
Resource、InjectionStrategy |
| 模板方法 | AbstractList、Servlet |
JdbcTemplate、AbstractApplicationContext.refresh |
| 责任链 | Filter |
DispatcherServlet 拦截器、Security FilterChain |
| 迭代器 | Iterator、Stream |
Repository 遍历 |
| 命令 | Runnable |
RedisTemplate 的 SessionCallback 可类比 |
| 状态 | Thread.State 枚举(弱) |
订单状态机(业务) |
| 中介 | — | ApplicationContext 可浅比 |
8. 项目里怎么讲(准备 2~3 个故事就够)
面试官最烦「我们用了 23 种」。准备 2 个深的 比 10 个浅的强。
故事 A:策略消除支付 / 促销 if-else
促销规则经常加。最早
if (type==VIP)堆在一个类,每次需求都改这段还容易回归。抽PromotionStrategy,Spring 按 beanName 注入 Map,配置中心发类型。新活动加一个类 + 配置,核心下单代码不动。代价是类变多,所以只对「变化频繁的规则」抽,运费这种一年不变的没抽。
可被追问:策略放哪、怎么路由、怎么灰度、错误的 type 怎么办(默认策略 / 明确失败)。
故事 B:责任链做网关 / 下单校验
下单要登录校验、库存、优惠券、风控、限额。用链,每段一个 Handler,失败短路。新增「未成年人校验」只加一环。和 Filter 一样。要注意顺序和幂等。
故事 C:模板方法做数据导入
Excel / CSV / API 三种导入,校验和落库一样,解析不同。
AbstractImportTemplate定骨架,子类实现 parse。后来发现解析差异变大、骨架也在变,部分改成策略 + 组合,避免继承树僵化。——这句话展示你知道模板的代价。
故事 D:观察者做解耦
下单成功后要发积分、发短信、写审计。如果全写在
OrderService里违反单一职责。发OrderPaidEvent,各监听者自己干。坑:同步监听拖垮下单、监听里再开事务、失败重试。后来改 MQ,保证最终一致性。
故事 E:代理 / AOP
接口耗时、鉴权用注解 + AOP,业务方法保持干净。坑:自调用不走代理(
this.foo()),要暴露 self 或AopContext。
讲项目时主动提 坑,比只提模式名更像做过。
9. 高频问答速查
Q:有必要用设计模式吗?
A:模式是沟通词汇和针对特定变化的成熟解。没变化就不要套。我先看变化点再决定。
Q:单例有哪些坑?
A:反射、反序列化、克隆会破唯一性(enum 最稳);DCL 要 volatile;多 ClassLoader 各一份;集群不是一份;测试困难;误当共享可变数据导致并发 bug。
Q:Spring 用了哪些模式?
A:按上面对照表说 6~8 个并各举一真实类,不要背 20 个。优先:单例 Bean、工厂、代理 AOP、模板 JdbcTemplate、观察者事件、适配器 HandlerAdapter、责任链拦截器。
Q:JDK 动态代理和 CGLIB 区别?
A:JDK 只能代理接口,基于 InvocationHandler;CGLIB 继承类,final 方法和类不行;Spring Boot 2+ 有时默认 CGLIB。调用 final/private 不会被增强。
Q:装饰和代理代码都是包装,怎么选?
A:看意图:给对象加职责且希望组合 —— 装饰;控制访问、懒加载、远程、权限、事务 —— 代理。装饰常由客户组装链条;代理常对客户隐藏真实对象。
Q:如何避免过度设计?
A:Rule of three:第一次硬写,第二次皱眉,第三次再抽象。抽象要对准稳定的变化轴。加层数会伤害可读性和调试。
Q:组合优于继承,那模板方法还用吗?
A:用,但当骨架真的稳定。骨架一变,所有子类跟着炸,这时改为策略或钩子委托。
Q:空对象模式 Null Object?
A:非 GoF 但常提。用什么都不做的实现代替 null,少 NPE 和 if。和策略很配(默认策略)。
Q:MVC / MVVM 是设计模式吗?
A:是架构模式,粒度比 GoF 大。可以说「表现层架构,内部仍会用观察者(视图监听模型)」。
Q:依赖注入是什么模式?
A:不是 GoF 23。是控制反转的实现,常配合工厂(容器)和单例。它让策略/适配器更好注入。
10. 现场白板怎么画(够用就行)
不要画满 23 个 UML。任选一个讲透:
- 画接口 + 两个实现 + 上下文(策略最容易画)
- 指出客户依赖的是接口
- 说出加第三种实现时改哪些文件(理想:只加类,不改旧类)
- 补一句测试怎么 mock 接口
策略模式白板 2 分钟就能画完,是最稳的现场题。
11. 复习优先级(时间不够按这个砍)
必须能讲 10 分钟: 单例(含 DCL/volatile/enum)、工厂 vs 抽象工厂、策略、模板、代理 vs 装饰、观察者、责任链、适配器。再加上 SOLID、Spring 对照、一个项目故事。
能辨认、能对比即可: 建造者、桥接、外观、组合、状态、命令、中介、享元。
点到为止: 访问者、解释器、原型、迭代器、备忘录。
12. 30 秒自我介绍版(可直接说)
设计模式我主要当「变化点的词汇」用,不是为了用而用。创建型里单例和工厂在框架里天天见;结构型里适配器、代理、装饰分得清;行为型里策略、模板、观察者、责任链在业务里用得最多。项目里我用策略拆过分期计价,用事件把下单后的副作用拆出去,也踩过 AOP 自调用不生效。原则是:对准频繁变化的那一轴再抽象,并且能说清代价。
把其中「分期计价 / 下单事件」换成你自己仓库里的真实模块即可。
附录:一页作弊条
变化的是「具体类」 → 工厂 / 抽象工厂
变化的是「构造过程」 → 建造者
变化的是「实例数量」 → 单例
变化的是「接口」 → 适配器
变化的是「两维」 → 桥接
变化的是「树结构」 → 组合
变化的是「附加能力」 → 装饰
变化的是「访问控制」 → 代理
变化的是「子系统复杂度」→ 外观
变化的是「算法」 → 策略
变化的是「算法步骤」 → 模板
变化的是「对象状态」 → 状态
变化的是「谁来处理」 → 责任链
变化的是「通知谁」 → 观察者
变化的是「互相调用网」 → 中介
变化的是「一次操作」 → 命令
文档版本:面试向精讲(GoF 23 + SOLID + JDK/Spring 映射 + 项目话术)。
建议用法:先背第 6、7、11 章,再按第 3–5 章把「必须能讲」的 8 个模式各讲一遍给自己听,最后用第 8 章改成你的项目。