12-设计模式
常用的设计模式有哪些?分别解决什么问题?
原始问法:
- 常用的设计模式有哪些?分别解决什么问题?
来源题目:
SRC-12-115-404
面试先答
GoF(四人组)总结了 23 种经典设计模式,分为三大类:创建型(5 种)、结构型(7 种)、行为型(11 种)。面试高频考察的有:单例(保证唯一实例)、工厂(封装对象创建)、代理(控制访问)、观察者(一对多通知)、策略(算法可切换)、模板方法(固定执行框架)、适配器(接口兼容)、装饰器(动态增强)。Spring 框架深度应用了这些模式:IOC 用了工厂和单例,AOP 用了代理,事务用了模板方法。
核心结论
- 23 种设计模式分三类:创建型、结构型、行为型。
- 面试高频:单例、工厂、代理、观察者、策略、模板方法。
- Spring 框架是设计模式的集大成者。
1. 是什么
设计模式是对软件设计中常见问题的可复用解决方案。GoF 的 23 种经典模式:
创建型模式(5 种):关注对象的创建过程
| 模式 | 意图 | 核心问题 |
|---|---|---|
| 单例(Singleton) | 保证一个类只有一个实例 | 如何全局共享唯一实例 |
| 工厂方法(Factory Method) | 定义创建对象的接口 | 如何延迟到子类决定创建哪个对象 |
| 抽象工厂(Abstract Factory) | 创建产品族 | 如何创建一系列相关对象 |
| 建造者(Builder) | 分步创建复杂对象 | 如何简化复杂对象的创建 |
| 原型(Prototype) | 克隆已有对象 | 如何高效创建相似对象 |
结构型模式(7 种):关注类和对象的组合
| 模式 | 意图 | 核心问题 |
|---|---|---|
| 适配器(Adapter) | 转换接口使其兼容 | 如何让不兼容的接口协作 |
| 装饰器(Decorator) | 动态添加职责 | 如何在不修改原类的情况下增强功能 |
| 外观(Facade) | 提供统一入口 | 如何简化子系统的使用 |
| 组合(Composite) | 树形结构统一处理 | 如何统一处理单个和组合对象 |
| 代理(Proxy) | 控制对象访问 | 如何间接访问对象 |
| 桥接(Bridge) | 分离抽象和实现 | 如何独立变化抽象和实现 |
| 享元(Flyweight) | 共享细粒度对象 | 如何高效创建大量相似对象 |
行为型模式(11 种):关注对象间的通信和职责分配
| 模式 | 意图 | 核心问题 |
|---|---|---|
| 策略(Strategy) | 算法族可互换 | 如何在运行时切换算法 |
| 模板方法(Template Method) | 固定算法框架 | 如何让子类实现可变步骤 |
| 观察者(Observer) | 一对多依赖 | 如何在状态变化时通知所有依赖 |
| 迭代器(Iterator) | 顺序访问集合元素 | 如何不暴露集合内部结构遍历 |
| 责任链(Chain of Responsibility) | 请求沿链传递 | 如何让多个处理器都有机会处理 |
| 命令(Command) | 请求封装为对象 | 如何支持撤销和排队 |
| 中介者(Mediator) | 对象间简化通信 | 如何降低对象间的耦合 |
| 备忘录(Memento) | 保存/恢复对象状态 | 如何实现可撤销操作 |
| 访问者(Visitor) | 分离数据和操作 | 如何在不修改数据类的情况下添加操作 |
| 状态(State) | 状态驱动行为变化 | 如何让状态切换行为 |
| 解释器(Interpreter) | 定义文法解释器 | 如何解释特定语言的语句 |
2. 为什么需要它
- 复用经过验证的设计方案,避免重复发明轮子。
- 提高代码的可维护性、可扩展性、可复用性。
- 降低耦合,提高内聚。
- 帮助开发者形成统一的设计语言和思维方式。
3. 怎么使用
Spring 中的设计模式应用:
| Spring 组件 | 使用的设计模式 |
|---|---|
| BeanFactory | 工厂模式 + 单例模式 |
| BeanDefinition | 原型模式(克隆创建 Bean) |
| AOP 代理 | 代理模式(JDK/CGLIB) |
| JdbcTemplate | 模板方法模式 |
| ApplicationEvent | 观察者模式 |
| @Conditional | 策略模式 |
| HandlerMapping | 策略模式 |
| DispatcherServlet | 前端控制器模式 |
| RestTemplate | 外观模式 |
| BeanPostProcessor | 装饰器模式 |
4. 适用场景
- 面试中回答设计模式问题。
- 日常开发中根据问题选择合适模式。
- 阅读和理解 Spring 等框架源码。
5. 易错点
❌ 错误:设计模式越多越好。
✅ 正确:设计模式是工具,过度使用会增加复杂度。
❌ 错误:设计模式可以直接套用。
✅ 正确:应根据实际问题选择和调整模式。
一句话总结
23 种经典设计模式分创建型、结构型、行为型三类,面试高频包括单例、工厂、代理、观察者、策略、模板方法,Spring 框架是设计模式的集大成者。
单例模式的实现方式有哪些?应用场景是什么?
原始问法:
- 单例模式的实现方式有哪些?应用场景是什么?
来源题目:
SRC-12-115-405
面试先答
单例模式保证一个类只有一个实例,提供全局访问点。实现方式有六种:1. 饿汉式(静态常量,线程安全但浪费内存);2. 懒汉式(同步方法,线程安全但性能差);3. 双重检查锁(DCL,线程安全且延迟加载,推荐);4. 静态内部类(延迟加载且线程安全,推荐);5. 枚举(最安全,防止反射和序列化破坏);6. 容器单例(Spring Bean 默认)。应用场景:配置中心、日志对象、数据库连接池、缓存、单例工具类。
核心结论
- 六种实现方式各有优劣,推荐 DCL、静态内部类、枚举。
- 线程安全是单例模式的关键考量。
- 需防止反射和序列化破坏单例。
1. 是什么
单例模式(Singleton)是创建型设计模式,保证一个类在系统中只有一个实例,并提供全局访问点。
2. 实现方式
方式一:饿汉式(静态常量)
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
优点:线程安全、实现简单。缺点:类加载时就创建,可能浪费内存。
方式二:懒汉式(同步方法)
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点:线程安全、延迟加载。缺点:每次 getInstance 都同步,性能差。
方式三:双重检查锁(DCL,推荐)
public class Singleton {
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;
}
}
优点:线程安全、延迟加载、性能好。缺点:实现稍复杂。
方式四:静态内部类(推荐)
public class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
优点:线程安全、延迟加载、实现简洁。缺点:无法传参构造。
方式五:枚举(最安全)
public enum Singleton {
INSTANCE;
public void doSomething() {
// 业务方法
}
}
优点:防止反射和序列化破坏。缺点:不够灵活。
方式六:Spring 容器单例
@Service
public class MyService {
// Spring 默认就是单例
}
3. 应用场景
| 场景 | 说明 |
|---|---|
| 配置中心 | ConfigCenter.getInstance() 全局配置 |
| 日志对象 | Logger 全局唯一 |
| 数据库连接池 | DataSource 全局共享 |
| 缓存实例 | CacheManager 全局缓存 |
| 工具类 | StringUtils、DateUtils |
| 线程池 | 全局共享的线程池 |
4. 防止破坏单例
// 1. 防止反射破坏
private Singleton() {
if (instance != null) {
throw new IllegalStateException("Singleton already exists");
}
}
// 2. 防止序列化破坏
protected Object readResolve() {
return getInstance();
}
5. 易错点
❌ 错误:饿汉式线程不安全。
✅ 正确:饿汉式通过 JVM 类加载机制保证线程安全。
❌ 错误:单例模式一定是最佳选择。
✅ 正确:单例违反单一职责原则,应谨慎使用。
一句话总结
单例模式有六种实现方式,推荐 DCL、静态内部类和枚举,适用于需要全局唯一实例的场景,需防止反射和序列化破坏。
手写双重检验锁(DCL)实现单例模式?
原始问法:
- 手写双重检验锁(DCL)实现单例模式?
来源题目:
SRC-12-115-406
面试先答
DCL(Double-Checked Locking)实现单例模式的关键:1. 使用 volatile 修饰实例变量(防止指令重排序);2. 第一次 null 检查(避免不必要的同步);3. synchronized 同步块(保证线程安全);4. 第二次 null 检查(防止多线程同时通过第一次检查后重复创建)。DCL 结合了懒加载和线程安全的优点,是面试中最常考的单例实现方式。
核心结论
- DCL 关键:
volatile+ 双重 null 检查 +synchronized。 volatile防止指令重排序,保证多线程可见性。- 两次 null 检查分别解决性能和线程安全问题。
1. 完整实现代码
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
if (instance != null) {
throw new IllegalStateException("Singleton already exists");
}
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
protected Object readResolve() {
return getInstance();
}
}
2. 关键细节解析
为什么使用 volatile?
// instance = new Singleton() 实际分为三步:
// 1. 分配内存空间
// 2. 初始化对象
// 3. 指向内存地址
// 没有 volatile 可能发生指令重排序:
// 1. 分配内存空间
// 3. 指向内存地址 ← 此时 instance 不为 null
// 2. 初始化对象 ← 但对象还未初始化
// 线程 A 执行到 3,线程 B 检查 instance != null
// 返回未初始化的对象 → NPE
第一次 null 检查的作用:
if (instance == null) { // 第一次检查
// 如果实例已存在,直接返回,避免进入同步块
// 解决性能问题:只在第一次创建时同步
}
第二次 null 检查的作用:
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
// 多个线程可能同时通过第一次检查
// 进入同步块后必须再次检查
instance = new Singleton();
}
}
3. 执行流程图解
线程 A 线程 B
│ │
├─ instance == null? YES │
│ ├─ instance == null? YES
├─ enter synchronized │ (等待锁释放)
│ │
├─ second null check? YES │
│ │
├─ new Singleton() │
│ │
├─ instance = new instance │
│ │
├─ exit synchronized │
│ ├─ enter synchronized
│ │
│ ├─ second null check? NO
│ │ (instance already created)
│ │
│ ├─ exit synchronized
│ │
│ ├─ return instance
│ │
├─ return instance
4. 优缺点
| 优点 | 缺点 |
|---|---|
| 线程安全 | 实现稍复杂 |
| 延迟加载 | 需理解 volatile 和指令重排 |
| 性能优秀(大部分时间无锁) | JDK 1.4 及以下版本有问题 |
5. 易错点
❌ 错误:可以用
static synchronized方法替代。✅ 正确:
static synchronized每次调用都同步,性能差。❌ 错误:去掉
volatile不影响正确性。✅ 正确:没有
volatile可能因指令重排导致获取未初始化的对象。
一句话总结
DCL 单例通过 volatile 防止指令重排、双重 null 检查兼顾性能和线程安全、synchronized 保证原子性,是面试中最经典的单例实现。
工厂模式的应用场景是什么?
原始问法:
- 工厂模式的应用场景是什么?
来源题目:
SRC-12-115-407
面试先答
工厂模式(Factory Pattern)是创建型设计模式,核心是将对象的创建与使用分离。分为三种:简单工厂(一个工厂创建多种产品)、工厂方法(每个产品对应一个工厂)、抽象工厂(创建产品族)。应用场景:1. 对象创建逻辑复杂(需要条件判断、资源初始化);2. 需要创建多种类型的相关对象;3. 需要解耦创建和使用;4. 不确定未来需要扩展哪些产品类型。Spring 的 BeanFactory 就是工厂模式的经典应用。
核心结论
- 三种工厂模式:简单工厂、工厂方法、抽象工厂,复杂度递增。
- 核心价值:解耦创建和使用、降低耦合、提高可扩展性。
- Spring BeanFactory 是工厂模式的典范。
1. 三种工厂模式
简单工厂:
public class ShapeFactory {
public Shape getShape(String type) {
switch (type) {
case "circle": return new Circle();
case "rectangle": return new Rectangle();
default: throw new IllegalArgumentException();
}
}
}
工厂方法:
public interface ShapeFactory {
Shape createShape();
}
public class CircleFactory implements ShapeFactory {
@Override
public Shape createShape() { return new Circle(); }
}
public class RectangleFactory implements ShapeFactory {
@Override
public Shape createShape() { return new Rectangle(); }
}
抽象工厂:
public interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
public class WindowsFactory implements GUIFactory {
@Override public Button createButton() { return new WindowsButton(); }
@Override public Checkbox createCheckbox() { return new WindowsCheckbox(); }
}
public class MacFactory implements GUIFactory {
@Override public Button createButton() { return new MacButton(); }
@Override public Checkbox createCheckbox() { return new MacCheckbox(); }
}
2. 应用场景
| 场景 | 工厂类型 | 示例 |
|---|---|---|
| 日志框架 | 简单工厂 | LoggerFactory.getLogger() |
| 数据库连接 | 简单工厂 | DriverManager.getConnection() |
| Spring Bean 创建 | 工厂方法 | BeanFactory.getBean() |
| 跨平台 UI | 抽象工厂 | 不同操作系统的 UI 组件 |
| 支付渠道 | 抽象工厂 | 微信/支付宝/银联支付 |
| 序列化方式 | 工厂方法 | JSON/XML/Protobuf 序列化 |
3. 优缺点
| 类型 | 优点 | 缺点 |
|---|---|---|
| 简单工厂 | 简单易用 | 违反开闭原则,新增产品需修改工厂 |
| 工厂方法 | 符合开闭原则 | 类爆炸,每种产品需一个工厂 |
| 抽象工厂 | 创建产品族 | 增加新产品需修改所有工厂接口 |
4. 易错点
❌ 错误:工厂模式就是工厂方法模式。
✅ 正确:工厂模式是统称,包含简单工厂、工厂方法、抽象工厂三种。
❌ 错误:工厂模式增加了代码复杂度,不如直接 new。
✅ 正确:工厂模式在创建逻辑复杂或需要解耦时非常有价值。
一句话总结
工厂模式将对象创建与使用分离,三种模式复杂度递增,适用于创建逻辑复杂、需要解耦、多产品类型的场景。
代理模式的应用场景是什么?
原始问法:
- 代理模式的应用场景是什么?
来源题目:
SRC-12-115-408
面试先答
代理模式(Proxy Pattern)是结构型设计模式,为目标对象提供代理控制访问。分为:静态代理(手动创建代理类)、JDK 动态代理(基于接口)、CGLIB 动态代理(基于类)。应用场景:1. 延迟加载(虚拟代理);2. 权限控制(保护代理);3. 远程调用(远程代理);4. AOP 面向切面编程;5. 事务管理(Spring @Transactional);6. 日志记录、性能监控。Spring AOP 和 MyBatis 接口代理都是代理模式的经典应用。
核心结论
- 代理模式三种实现:静态代理、JDK 动态代理、CGLIB。
- 核心价值:在不修改目标对象的情况下增强功能。
- 应用广泛:AOP、事务、缓存、日志、权限等。
1. 三种代理实现
静态代理:
public interface UserService {
void save();
}
public class UserServiceImpl implements UserService {
@Override
public void save() { System.out.println("保存用户"); }
}
public class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void save() {
System.out.println("前置:权限检查");
target.save();
System.out.println("后置:日志记录");
}
}
JDK 动态代理:
public class LogProxyHandler implements InvocationHandler {
private Object target;
public LogProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用方法: " + method.getName());
long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
long elapsed = System.currentTimeMillis() - start;
System.out.println("耗时: " + elapsed + "ms");
return result;
}
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogProxyHandler(target)
);
}
}
CGLIB 动态代理:
public class LogInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("调用方法: " + method.getName());
long start = System.currentTimeMillis();
Object result = proxy.invokeSuper(obj, args);
long elapsed = System.currentTimeMillis() - start;
System.out.println("耗时: " + elapsed + "ms");
return result;
}
public static <T> T createProxy(Class<T> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(new LogInterceptor());
return (T) enhancer.create();
}
}
2. 应用场景
| 场景 | 代理类型 | 实际案例 |
|---|---|---|
| AOP 切面 | JDK/CGLIB | Spring AOP、事务管理 |
| 日志监控 | JDK/CGLIB | 方法耗时统计、调用链追踪 |
| 权限控制 | 静态代理/RMI | 接口权限校验 |
| 延迟加载 | 虚拟代理 | Hibernate 懒加载 |
| 远程调用 | 远程代理 | RMI、RPC |
| MyBatis Mapper | JDK 动态代理 | MyBatis 接口代理 |
| 缓存代理 | 静态代理 | 方法级缓存 |
3. JDK vs CGLIB 对比
| 维度 | JDK 动态代理 | CGLIB |
|---|---|---|
| 原理 | 基于接口 | 基于类(生成子类) |
| 要求 | 目标类实现接口 | 无接口要求 |
| 限制 | final 类/方法不可代理 | final 方法不可代理 |
| 性能 | JDK 8+ 优秀 | 略优于 JDK |
| 框架 | Spring 默认 | Spring 可配置 |
4. 优缺点
| 优点 | 缺点 |
|---|---|
| 无侵入增强功能 | 增加调用链路,调试困难 |
| 灵活可组合 | 动态代理有性能开销 |
| 解耦增强逻辑 | 可能导致循环代理 |
5. 易错点
❌ 错误:JDK 动态代理可以代理任意类。
✅ 正确:JDK 动态代理只能代理实现了接口的类。
❌ 错误:CGLIB 可以代理所有类。
✅ 正确:CGLIB 不能代理 final 类和 final 方法。
一句话总结
代理模式通过为目标对象创建代理控制访问,JDK 基于接口、CGLIB 基于类,广泛应用于 AOP、事务、日志、权限等横切关注点。
观察者模式的应用场景是什么?
原始问法:
- 观察者模式的应用场景是什么?
来源题目:
SRC-12-115-409
面试先答
观察者模式(Observer Pattern)是行为型设计模式,定义了对象间的一对多依赖关系:当主题(Subject)状态变化时,所有观察者(Observer)自动收到通知。核心角色:Subject(主题/被观察者)、Observer(观察者)、ConcreteSubject(具体主题)、ConcreteObserver(具体观察者)。应用场景:1. 事件驱动系统(事件订阅/发布);2. 消息通知系统(邮件、短信推送);3. 数据绑定(UI 与数据同步);4. Spring 事件机制(ApplicationEvent);5. 消息队列(Kafka、RocketMQ 的发布订阅模式)。
核心结论
- 观察者模式实现一对多的依赖通知。
- 核心角色:Subject、Observer、ConcreteSubject、ConcreteObserver。
- Spring 事件机制、消息队列都基于此模式。
1. 核心结构
Subject(主题/被观察者)
├─ 维护观察者列表
├─ attach(Observer) 注册观察者
├─ detach(Observer) 移除观察者
└─ notify() 通知所有观察者
Observer(观察者接口)
└─ update() 接收通知
ConcreteSubject(具体主题)
└─ 触发状态变化 → notify()
ConcreteObserver(具体观察者)
└─ update() 实现具体逻辑
2. 代码实现
// 主题接口
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers();
}
// 观察者接口
public interface Observer {
void update(String event);
}
// 具体主题:事件发布器
public class EventBus implements Subject {
private List<Observer> observers = new ArrayList<>();
private String event;
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(event);
}
}
public void publish(String event) {
this.event = event;
notifyObservers();
}
}
// 具体观察者 A
public class EmailNotifier implements Observer {
@Override
public void update(String event) {
System.out.println("发送邮件通知: " + event);
}
}
// 具体观察者 B
public class SmsNotifier implements Observer {
@Override
public void update(String event) {
System.out.println("发送短信通知: " + event);
}
}
3. 应用场景
| 场景 | Subject | Observer | 实际案例 |
|---|---|---|---|
| 事件系统 | EventBus | 各事件处理器 | Guava EventBus |
| Spring 事件 | ApplicationEvent | ApplicationListener | Spring 容器事件 |
| 消息队列 | Topic | Consumer | Kafka、RocketMQ |
| 数据绑定 | DataModel | UI 组件 | Vue 双向绑定 |
| 订单通知 | OrderService | 邮件/短信/积分 | 电商订单状态变更 |
| 配置变更 | ConfigService | 缓存/服务实例 | 配置中心推送 |
4. Spring 中的观察者模式
// 自定义事件
public class OrderCreatedEvent extends ApplicationEvent {
private final Order order;
public OrderCreatedEvent(Object source, Order order) {
super(source);
this.order = order;
}
public Order getOrder() { return order; }
}
// 事件监听器
@Component
public class OrderEventListener {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
// 处理订单创建事件
sendEmail(event.getOrder());
updatePoints(event.getOrder());
}
}
// 发布事件
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher publisher;
public void createOrder(Order order) {
// 创建订单逻辑...
publisher.publishEvent(new OrderCreatedEvent(this, order));
}
}
5. 优缺点
| 优点 | 缺点 |
|---|---|
| 解耦主题和观察者 | 通知顺序不确定 |
| 支持一对多广播 | 可能导致循环通知 |
| 动态添加/移除观察者 | 观察者过多影响性能 |
6. 易错点
❌ 错误:观察者模式等同于发布订阅模式。
✅ 正确:观察者模式是同步直接通知,发布订阅模式通常引入中间件(消息队列)。
❌ 错误:观察者只能同步通知。
✅ 正确:可以使用异步通知(如
@EventListener(async = true))。
一句话总结
观察者模式实现一对多的依赖通知,当主题状态变化时所有观察者自动收到通知,Spring 事件机制和消息队列都基于此模式。
你的项目中使用了哪些设计模式?为什么选择这些模式?
原始问法:
- 你的项目中使用了哪些设计模式?为什么选择这些模式?
来源题目:
SRC-12-115-410
面试先答
在项目中,我使用了以下设计模式:1. 单例模式:Spring Bean 默认单例,用于 Service、Repository 等无状态服务,保证全局唯一实例、节省资源;2. 工厂模式:使用 Spring BeanFactory 创建 Bean,解耦对象创建和使用;3. 代理模式:Spring AOP 实现日志、事务、权限等横切关注点,MyBatis 接口代理实现数据访问;4. 观察者模式:Spring 事件机制实现订单状态变更通知、配置变更推送;5. 策略模式:支付渠道选择、多种导出格式(Excel/PDF)等需要动态切换算法的场景;6. 模板方法模式:Spring JdbcTemplate 封装数据库操作模板,统一异常处理和资源管理;7. 装饰器模式:MyBatis 插件链、Spring BeanPostProcessor 动态增强功能。选择这些模式的核心考量是:解耦、扩展性、可维护性。
核心结论
- 项目中最常用的模式:单例、工厂、代理、观察者、策略、模板方法。
- 选择依据:解耦需求、扩展需求、团队熟悉度。
- 面试回答要结合具体业务场景,体现模式的价值。
1. 项目实战案例
案例一:支付系统(策略模式 + 工厂模式)
// 支付策略接口
public interface PaymentStrategy {
PaymentResult pay(PaymentRequest request);
String getChannel();
}
// 微信支付策略
@Component("WECHAT")
public class WechatPaymentStrategy implements PaymentStrategy { ... }
// 支付宝支付策略
@Component("ALIPAY")
public class AlipayPaymentStrategy implements PaymentStrategy { ... }
// 工厂 + 策略选择
@Service
public class PaymentFactory {
private Map<String, PaymentStrategy> strategyMap;
public PaymentFactory(List<PaymentStrategy> strategies) {
this.strategyMap = strategies.stream()
.collect(Collectors.toMap(PaymentStrategy::getChannel, s -> s));
}
public PaymentStrategy getStrategy(String channel) {
return strategyMap.get(channel);
}
}
案例二:订单通知(观察者模式)
// 订单状态变更事件
public class OrderStatusEvent extends ApplicationEvent {
private final Order order;
private final OrderStatus status;
}
// 短信通知监听器
@Component
public class SmsNotificationListener {
@Async
@EventListener
public void onOrderStatusChanged(OrderStatusEvent event) {
// 发送短信
}
}
// 积分更新监听器
@Component
public class PointsUpdateListener {
@EventListener
public void onOrderStatusChanged(OrderStatusEvent event) {
// 更新积分
}
}
案例三:数据导出(模板方法模式)
// 导出模板
public abstract class DataExporter {
public void export(ExportRequest request) {
// 固定流程:查询→转换→写入→通知
List<?> data = queryData(request);
List<?> rows = convertData(data);
writeToFile(rows, request);
notifyCompletion(request);
}
protected abstract List<?> queryData(ExportRequest request);
protected abstract List<?> convertData(List<?> data);
protected abstract void writeToFile(List<?> rows, ExportRequest request);
protected void notifyCompletion(ExportRequest request) {
// 默认实现,子类可覆盖
}
}
// Excel 导出器
@Component
public class ExcelExporter extends DataExporter { ... }
// PDF 导出器
@Component
public class PdfExporter extends DataExporter { ... }
2. 模式选择决策矩阵
| 需求 | 选择的模式 | 原因 |
|---|---|---|
| 全局唯一实例 | 单例 | 节省资源、状态一致 |
| 对象创建复杂 | 工厂 | 解耦创建和使用 |
| 横切关注点 | 代理/AOP | 无侵入增强 |
| 一对多通知 | 观察者 | 解耦事件和处理 |
| 算法可切换 | 策略 | 运行时灵活选择 |
| 固定流程可变步骤 | 模板方法 | 统一流程、差异化实现 |
| 动态增强 | 装饰器 | 灵活组合 |
| 接口兼容 | 适配器 | 复用现有代码 |
3. 回答框架
面试回答"项目中使用了哪些设计模式"的建议结构:
- 开场白:概览使用的模式数量和种类。
- 逐个展开:每个模式说明"是什么→为什么用→怎么用→效果"。
- 重点突出:选择 2-3 个最核心的模式详细说明。
- 量化成果:说明使用模式带来的收益(如代码量减少、扩展性提升)。
- 反思总结:说明模式的权衡和改进空间。
4. 易错点
❌ 错误:模式用得越多越好。
✅ 正确:模式是解决问题的手段,过度使用反而增加复杂度。
❌ 错误:每个模式都要自己实现。
✅ 正确:很多模式(单例、工厂、代理)Spring 已经提供,直接使用即可。
一句话总结
项目中最常用的设计模式是单例、工厂、代理、观察者、策略、模板方法,选择依据是解耦需求和扩展需求,回答时要结合具体业务场景和量化收益。