跳转到内容

设计模式

💡 设计模式是面试中区分初级与中高级开发的关键考点。本文覆盖面试最高频的 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

query():获取连接 → 执行SQL → 处理结果集 → 释放连接

SQL 语句、结果集映射方式

Spring AbstractApplicationContext.refresh()

13 步 IoC 容器启动流程

后置处理器的具体逻辑

HttpServlet

service() 调度到具体方法

doGet() / doPost()

MyBatis BaseExecutor

一级缓存逻辑

doQuery() 具体 SQL 执行

AQS(AbstractQueuedSynchronizer)

acquire() / release() 框架

tryAcquire() / tryRelease()


四、观察者模式(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

@EventListener + ApplicationEventPublisher,业务模块完全解耦

消息队列 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

@Transactional 代理管理事务

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)

面试必问三道题:

  1. 单例 DCL 为什么用 volatile?new 不是原子操作,指令重排导致返回未初始化对象
  2. JDK 动态代理和 CGLIB 有什么区别? — JDK 需要接口,CGLIB 通过继承(final 类不行)
  3. Spring AOP 什么时候用 JDK 代理,什么时候用 CGLIB? — 有接口时可选 JDK,Boot 2.x 默认全用 CGLIB

七、一句话总结

模式

一句话

面试关键词

单例

全局只有一个实例

DCL、volatile、枚举防反射

工厂

把对象的创建交给工厂,调用者不关心怎么 new

Spring IoC、BeanFactory

模板方法

父类定流程,子类补细节

好莱坞原则、JdbcTemplate

观察者

状态变了,自动通知所有订阅者

发布订阅、Spring Event、MQ

代理

找个替身干活,顺便加功能

AOP、JDK/CGLIB、@Transactional