Appearance
设计模式高频题
面试只盯着最高频的几个模式问:单例、工厂、观察者、策略、代理、装饰器。能画出结构图 + 说出真实使用场景才算过关。
Q1: 手写一个线程安全的单例模式 「🟡 中级」
考察点:并发安全意识、对 double-check 细节的掌握。
参考答案(Java 双重检查锁):
java
public class Singleton {
// volatile 禁止指令重排,防止拿到半初始化对象
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查,避免每次加锁
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查,防止重复创建
instance = new Singleton();
}
}
}
return instance;
}
}要点:new 分三步(分配内存→初始化→引用赋值),重排后其他线程可能拿到未初始化的对象,所以必须 volatile。
追问延伸:
- 静态内部类和枚举实现单例各有什么优缺点?
- 序列化、反射能破坏单例吗?怎么防?
Q2: 工厂模式的三种形态分别解决什么问题? 「🟡 中级」
考察点:创建型模式的理解深度。
参考答案:
- 简单工厂:一个工厂类按参数创建不同产品;违反开闭原则,但产品少时够用。
- 工厂方法:每个产品对应一个工厂子类,新增产品只需新增工厂,符合开闭。
- 抽象工厂:创建"一族"相关产品(如同时造 Mac 的键盘和鼠标),保证产品之间的配套关系。
- 实际应用:
Calendar.getInstance()、Spring 的BeanFactory、MyBatis 的SqlSessionFactory。
追问延伸:
- Spring 容器算哪种工厂?(超级工厂 + 单例/原型管理)
- 工厂模式和建造者模式的取舍?
Q3: 观察者模式的应用场景? 「🟡 中级」
考察点:行为型模式 + 解耦思想。
参考答案:
- 定义一对多依赖:一个对象状态变化时,所有依赖它的观察者自动收到通知。
- 典型场景:事件系统、消息订阅、Spring 的
ApplicationEvent、前端的事件监听、WatchService文件监控。 - 优点:发布者和订阅者解耦,可动态增删订阅者。
- 缺点:通知链过长影响性能;同步通知时一个观察者异常会影响其他观察者。
追问延伸:
- 观察者模式和消息队列的异同?
- Spring 事件是同步还是异步?怎么改成异步?
Q4: 策略模式和 if-else 的关系? 「🟡 中级」
考察点:用模式重构坏味道的能力。
参考答案:
- 策略模式把一族算法封装成可互换的策略类,通过上下文持有策略接口来切换行为,消除大段 if-else / switch。
- 配合工厂或 Map 注册表,按 key 取策略,新增算法只需新增类(符合开闭)。
- 适用条件:分支逻辑复杂且稳定变化;只有两三个简单分支用 if-else 反而更清晰,不要过度设计。
追问延伸:
- 策略模式和状态模式的区别?(策略由外部选择,状态内部流转)
- Java 里用枚举 + 函数式接口能怎么简化策略模式?
Q5: 代理模式和装饰器模式的区别? 「🟡 中级」
考察点:结构型模式的辨析能力。
参考答案:
- 二者结构相同(都持有一个同接口对象并转发调用),区别在意图:
- 代理:控制对目标对象的访问(权限、懒加载、远程调用),客户端通常不感知;典型如 Spring AOP、MyBatis Mapper、RPC 客户端桩。
- 装饰器:动态给对象增加功能,客户端主动选择装饰;典型如 Java IO 流
BufferedInputStream。
追问延伸:
- Spring AOP 用的 JDK 动态代理还是 CGLIB?各什么条件?
- 动态代理是怎么在运行时生成类的?
Q6: 说说模板方法模式 「🟡 中级」
考察点:框架设计常见套路。
参考答案:
- 父类定义算法骨架(流程步骤),把可变步骤留成抽象方法由子类实现。
- 典型应用:
AbstractQueuedSynchronizer、JdbcTemplate、HttpServlet的service()、JUnit 的setUp/tearDown。 - 体现了"好莱坞原则":别找我们,我们会找你。
追问延伸:
- 模板方法和策略模式都能消除重复流程,怎么选?
- 钩子方法(hook)的作用是什么?