设计模式
💡 设计模式是面试中区分初级与中高级开发的关键考点。本文覆盖面试最高频的 5 种设计模式:单例、工厂、模板方法、观察者、代理。每种模式包含核心原理 + 完整 Java 代码 + 面试要点。
一、单例模式(Singleton)
⭐ 高频指数最高。 确保一个类只有一个实例,提供全局访问点。
1.1 饿汉式 — 最简单
public class EagerSingleton {
// 类加载时就创建(线程安全由 JVM 保证)
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {} // 私有构造,禁止外部 new
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
1.2 DCL 双重检查锁 — 标准写法(面试重点)
public class DclSingleton {
// volatile 必须加!防止指令重排导致拿到未初始化对象
private static volatile DclSingleton instance;
private DclSingleton() {}
public static DclSingleton getInstance() {
if (instance == null) { // 第一重检查(无锁,性能)
synchronized (DclSingleton.class) { // 类锁
if (instance == null) { // 第二重检查(保证只创建一次)
instance = new DclSingleton();
}
}
}
return instance;
}
}
volatile 为什么必不可少?
// new DclSingleton() 不是原子操作!JVM 分三步:
// ① 分配内存空间
// ② 调用构造器初始化对象
// ③ 将引用指向分配的内存地址
// JVM 可能把 ②③ 重排序 → 先指向地址,再初始化
// 线程 A 执行 ①③(还没初始化),线程 B 在第一重检查时发现 instance != null
// → 直接返回了一个未初始化的对象 → 程序崩溃!
// volatile 禁止指令重排,保证 ①②③ 顺序执行。
1.3 静态内部类 — 推荐写法
public class InnerClassSingleton {
private InnerClassSingleton() {}
// 静态内部类在被调用时才加载(懒加载)
private static class Holder {
private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
}
public static InnerClassSingleton getInstance() {
return Holder.INSTANCE; // 触发 Holder 类加载,JVM 保证线程安全
}
}
1.4 枚举 — 最安全写法(推荐)
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
System.out.println("执行单例方法");
}
}
// 使用
EnumSingleton.INSTANCE.doSomething();
为什么枚举最安全? Java 的枚举底层由 JVM 保证:① 防反射攻击(newInstance() 会抛异常);② 防序列化破坏(反序列化自动返回同一个实例);③ 写法最简单。
1.5 四种写法对比
|
写法 |
懒加载 |
线程安全 |
防反射 |
防序列化 |
推荐度 |
|---|---|---|---|---|---|
|
饿汉式 |
❌ |
✅ |
❌ |
❌ |
⭐⭐ |
|
DCL + volatile |
✅ |
✅ |
❌ |
❌ |
⭐⭐⭐ |
|
静态内部类 |
✅ |
✅ |
❌ |
❌ |
⭐⭐⭐⭐ |
|
枚举 |
❌ |
✅ |
✅ |
✅ |
⭐⭐⭐⭐⭐ |
1.6 单例的破坏与防御
// ===== 防御反射破坏 =====
public class SafeSingleton {
private static final SafeSingleton INSTANCE = new SafeSingleton();
private SafeSingleton() {
if (INSTANCE != null) { // 构造器中判断
throw new RuntimeException("单例已创建,禁止反射调用");
}
}
}
// ===== 防御序列化破坏 =====
public class SerializableSingleton implements Serializable {
private static final SerializableSingleton INSTANCE = new SerializableSingleton();
private SerializableSingleton() {}
public static SerializableSingleton getInstance() { return INSTANCE; }
// 反序列化时自动调用此方法返回已有实例
private Object readResolve() {
return INSTANCE;
}
}
1.7 Spring 中的单例
Spring Bean 默认 scope 就是单例(singleton)。Spring 容器通过 ConcurrentHashMap<String, Object>(singletonObjects 缓存)保证每个 Bean 只创建一次。
二、工厂模式(Factory)
⭐ 将对象的创建和使用解耦。Spring IoC 容器就是庞大的工厂体系。
2.1 简单工厂
// ===== 产品接口 =====
interface Payment {
void pay(BigDecimal amount);
}
// ===== 具体产品 =====
class WechatPay implements Payment {
public void pay(BigDecimal amount) {
System.out.println("微信支付: " + amount + " 元");
}
}
class AliPay implements Payment {
public void pay(BigDecimal amount) {
System.out.println("支付宝支付: " + amount + " 元");
}
}
// ===== 简单工厂(根据参数创建不同对象) =====
class PaymentFactory {
public static Payment create(String type) {
switch (type) {
case "wechat": return new WechatPay();
case "alipay": return new AliPay();
default: throw new IllegalArgumentException("未知支付方式: " + type);
}
}
}
// ===== 使用 =====
Payment payment = PaymentFactory.create("wechat");
payment.pay(new BigDecimal("100.00"));
2.2 工厂方法
// ===== 抽象工厂(符合开闭原则:新增产品无需修改已有代码) =====
interface PaymentFactory {
Payment createPayment();
}
class WechatPayFactory implements PaymentFactory {
public Payment createPayment() { return new WechatPay(); }
}
class AliPayFactory implements PaymentFactory {
public Payment createPayment() { return new AliPay(); }
}
// ===== 使用 =====
PaymentFactory factory = new WechatPayFactory(); // 可替换为 AliPayFactory
Payment payment = factory.createPayment();
payment.pay(new BigDecimal("100.00"));
2.3 抽象工厂
// ===== 产品族:手机 + 耳机 =====
interface Phone { void call(); }
interface Earphone { void listen(); }
// ===== 苹果产品族 =====
class IPhone implements Phone { public void call() { System.out.println("iPhone 打电话"); } }
class AirPods implements Earphone { public void listen() { System.out.println("AirPods 听音乐"); } }
// ===== 华为产品族 =====
class MatePhone implements Phone { public void call() { System.out.println("Mate 打电话"); } }
class FreeBuds implements Earphone { public void listen() { System.out.println("FreeBuds 听音乐"); } }
// ===== 抽象工厂:创建一族相关产品 =====
interface DeviceFactory {
Phone createPhone();
Earphone createEarphone();
}
class AppleFactory implements DeviceFactory {
public Phone createPhone() { return new IPhone(); }
public Earphone createEarphone() { return new AirPods(); }
}
class HuaweiFactory implements DeviceFactory {
public Phone createPhone() { return new MatePhone(); }
public Earphone createEarphone() { return new FreeBuds(); }
}
2.4 Spring 中的工厂模式
// BeanFactory / ApplicationContext — 最大的工厂
ApplicationContext ctx = SpringApplication.run(MyApp.class);
UserService service = ctx.getBean(UserService.class); // 工厂方法
// FactoryBean — 自定义 Bean 创建逻辑
@Component
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory> {
@Override
public SqlSessionFactory getObject() {
return new SqlSessionFactoryBuilder().build(config);
}
@Override
public Class<?> getObjectType() { return SqlSessionFactory.class; }
}
三、模板方法模式(Template Method)
📌 父类定义算法骨架,子类实现具体步骤。好莱坞原则: "Don't call us, we'll call you."
3.1 代码示例
// ===== 抽象父类:定义算法骨架 =====
abstract class DataExporter {
// 模板方法 — final 防止子类覆盖
public final void export() {
// 第1步:连接数据源
connect();
// 第2步:查询数据(子类实现)
List<Map<String, Object>> data = queryData();
// 第3步:转换格式(子类实现)
String content = formatData(data);
// 第4步:输出
writeToFile(content);
// 第5步:关闭连接
close();
}
private void connect() { System.out.println("● 连接数据源"); }
private void close() { System.out.println("● 关闭连接"); }
private void writeToFile(String content) {
System.out.println("● 写入文件: " + content.substring(0, 50) + "...");
}
// 交给子类实现的方法
protected abstract List<Map<String, Object>> queryData();
protected abstract String formatData(List<Map<String, Object>> data);
}
// ===== 子类1:导出 Excel =====
class ExcelExporter extends DataExporter {
@Override
protected List<Map<String, Object>> queryData() {
System.out.println(" └ 查询全量数据...");
return List.of(Map.of("name", "张三", "age", 25));
}
@Override
protected String formatData(List<Map<String, Object>> data) {
System.out.println(" └ 转为 Excel 格式...");
return "Excel: " + data.toString();
}
}
// ===== 子类2:导出 PDF =====
class PdfExporter extends DataExporter {
@Override
protected List<Map<String, Object>> queryData() {
System.out.println(" └ 查询当月数据...");
return List.of(Map.of("name", "李四", "age", 30));
}
@Override
protected String formatData(List<Map<String, Object>> data) {
System.out.println(" └ 转为 PDF 格式...");
return "PDF: " + data.toString();
}
}
// ===== 使用 =====
new ExcelExporter().export();
new PdfExporter().export();
3.2 实际应用场景
|
场景 |
骨架(父类) |
变化步骤(子类实现) |
|---|---|---|
|
Spring JdbcTemplate |
|
SQL 语句、结果集映射方式 |
|
Spring AbstractApplicationContext.refresh() |
13 步 IoC 容器启动流程 |
后置处理器的具体逻辑 |
|
HttpServlet |
|
|
|
MyBatis BaseExecutor |
一级缓存逻辑 |
|
|
AQS(AbstractQueuedSynchronizer) |
|
|
四、观察者模式(Observer)
📌 定义一对多依赖关系,当一个对象状态改变时,所有依赖者自动收到通知。也叫发布-订阅模式。
4.1 手写实现
// ===== 观察者接口 =====
interface Observer {
void update(String message);
}
// ===== 被观察者(主题) =====
class Subject {
private List<Observer> observers = new ArrayList<>();
public void attach(Observer observer) { observers.add(observer); }
public void detach(Observer observer) { observers.remove(observer); }
// 通知所有观察者
public void notifyAll(String message) {
for (Observer o : observers) {
o.update(message);
}
}
}
// ===== 具体观察者 =====
class UserObserver implements Observer {
private String name;
public UserObserver(String name) { this.name = name; }
@Override
public void update(String message) {
System.out.println(name + " 收到通知: " + message);
}
}
// ===== 使用 =====
Subject orderSubject = new Subject();
orderSubject.attach(new UserObserver("用户A"));
orderSubject.attach(new UserObserver("用户B"));
orderSubject.notifyAll("订单已发货");
// 输出:
// 用户A 收到通知: 订单已发货
// 用户B 收到通知: 订单已发货
4.2 Spring Event(生产推荐写法)
// ===== 1. 定义事件 =====
public class OrderEvent extends ApplicationEvent {
private Long orderId;
public OrderEvent(Object source, Long orderId) {
super(source);
this.orderId = orderId;
}
public Long getOrderId() { return orderId; }
}
// ===== 2. 发布事件 =====
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher publisher;
public void createOrder() {
// ... 创建订单逻辑 ...
publisher.publishEvent(new OrderEvent(this, 12345L));
}
}
// ===== 3. 监听事件(完全解耦) =====
@Component
public class SmsListener {
@EventListener
public void sendSms(OrderEvent event) {
System.out.println("发送短信: 订单 " + event.getOrderId() + " 已创建");
}
}
@Component
public class CouponListener {
@EventListener
@Async // 异步执行,不阻塞主流程
public void sendCoupon(OrderEvent event) {
System.out.println("发送优惠券: 订单 " + event.getOrderId());
}
}
4.3 实际应用场景
|
场景 |
说明 |
|---|---|
|
Spring Event |
|
|
消息队列 RabbitMQ/Kafka |
分布式观察者模式,生产者发布消息,消费者订阅 |
|
Zookeeper Watch |
节点数据变化 → 通知所有 Watcher |
|
Vue 的 watch / React 的 useEffect |
前端响应式本质也是观察者模式 |
五、代理模式(Proxy)
⭐ 为对象提供替身/代理,控制对原对象的访问。Spring AOP 是代理模式最经典的应用。
5.1 静态代理
// ===== 目标接口 =====
interface UserService {
void addUser(String name);
void deleteUser(Long id);
}
// ===== 目标对象 =====
class UserServiceImpl implements UserService {
public void addUser(String name) {
System.out.println("添加用户: " + name);
}
public void deleteUser(Long id) {
System.out.println("删除用户: " + id);
}
}
// ===== 静态代理类(手动实现相同接口) =====
class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) { this.target = target; }
@Override
public void addUser(String name) {
System.out.println("[日志] 调用 addUser,参数: " + name);
long start = System.currentTimeMillis();
target.addUser(name); // 调用目标方法
long end = System.currentTimeMillis();
System.out.println("[日志] addUser 耗时: " + (end - start) + "ms");
}
@Override
public void deleteUser(Long id) {
System.out.println("[日志] 调用 deleteUser,参数: " + id);
target.deleteUser(id);
}
}
// ===== 使用 =====
UserService target = new UserServiceImpl();
UserService proxy = new UserServiceProxy(target);
proxy.addUser("张三");
5.2 JDK 动态代理
// ===== 核心:InvocationHandler =====
class LogInvocationHandler implements InvocationHandler {
private Object target;
public LogInvocationHandler(Object target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[AOP] 调用方法: " + method.getName());
long start = System.currentTimeMillis();
Object result = method.invoke(target, args); // 反射调用目标方法
long end = System.currentTimeMillis();
System.out.println("[AOP] " + method.getName() + " 耗时: " + (end - start) + "ms");
return result;
}
}
// ===== 使用 =====
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 目标接口(只能代理接口!)
new LogInvocationHandler(target) // 增强逻辑
);
proxy.addUser("张三");
// 输出:
// [AOP] 调用方法: addUser
// 添加用户: 张三
// [AOP] addUser 耗时: 0ms
5.3 CGLIB 动态代理
// CGLIB 通过继承目标类生成代理子类(不需要接口)
// Spring Boot 2.x+ 默认使用 CGLIB
class UserServiceCglibProxy implements MethodInterceptor {
private Object target;
public UserServiceCglibProxy(Object target) { this.target = target; }
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("[CGLIB AOP] " + method.getName() + " 调用前");
Object result = proxy.invoke(target, args); // 调用目标方法
System.out.println("[CGLIB AOP] " + method.getName() + " 调用后");
return result;
}
}
5.4 JDK 动态代理 vs CGLIB
|
对比维度 |
JDK 动态代理 |
CGLIB |
|---|---|---|
|
原理 |
运行时生成接口实现类 |
运行时生成目标类的子类 |
|
要求 |
目标类必须实现接口 |
目标类不能是 final(final 类无法继承) |
|
性能 |
创建代理快,调用略慢(反射) |
创建代理慢,调用快(直接继承) |
|
Spring 默认 |
Boot 1.x 默认 |
Boot 2.x+ 默认 |
|
典型应用 |
MyBatis Mapper、Feign 接口 |
Spring AOP 通用代理 |
5.5 代理模式在框架中的应用
|
框架/场景 |
代理方式 |
说明 |
|---|---|---|
|
Spring AOP |
CGLIB(默认)/ JDK |
|
|
MyBatis Mapper |
JDK 动态代理 |
Mapper 接口无实现类,代理对象执行 SQL |
|
Feign |
JDK 动态代理 |
接口方法 → HTTP 请求 |
|
@Transactional |
AOP 代理 |
方法前开启事务,方法后提交/回滚 |
|
@Async |
AOP 代理 |
方法提交到线程池异步执行 |
|
@Cacheable |
AOP 代理 |
方法前查缓存,方法后写缓存 |
|
RPC 远程代理 |
动态代理 |
本地调接口 → 透明地发网络请求 |
六、面试高频排序
单例(DCL+volatile) > 代理(JDK/CGLIB+Spring AOP) > 工厂(Spring BeanFactory)
> 模板方法(JdbcTemplate/AbstractApplicationContext) > 观察者(Spring Event)
面试必问三道题:
- 单例 DCL 为什么用 volatile? —
new不是原子操作,指令重排导致返回未初始化对象 - JDK 动态代理和 CGLIB 有什么区别? — JDK 需要接口,CGLIB 通过继承(final 类不行)
- Spring AOP 什么时候用 JDK 代理,什么时候用 CGLIB? — 有接口时可选 JDK,Boot 2.x 默认全用 CGLIB
七、一句话总结
|
模式 |
一句话 |
面试关键词 |
|---|---|---|
|
单例 |
全局只有一个实例 |
DCL、volatile、枚举防反射 |
|
工厂 |
把对象的创建交给工厂,调用者不关心怎么 new |
Spring IoC、BeanFactory |
|
模板方法 |
父类定流程,子类补细节 |
好莱坞原则、JdbcTemplate |
|
观察者 |
状态变了,自动通知所有订阅者 |
发布订阅、Spring Event、MQ |
|
代理 |
找个替身干活,顺便加功能 |
AOP、JDK/CGLIB、@Transactional |