为什么设计模式是面试的核心?

在Java高级开发及架构师岗位的面试中,Java设计模式几乎是绕不开的话题。面试官不仅考察你是否知道某种模式的名称,更关注你是否理解其背后的设计思想,以及如何在实际项目中灵活运用。设计模式不仅是代码组织的艺术,更是解决复杂业务逻辑、降低系统耦合度、提高代码可维护性的关键手段。

许多开发者虽然在日常工作中使用了某种模式,却未能将其理论化。本页面旨在通过梳理高频考点,结合源码级代码示例,帮助你建立完整的设计模式知识体系。我们将重点探讨单例模式、工厂模式、观察者模式、策略模式和代理模式等在实际面试中出现频率最高的模式,并深入分析其优缺点及适用场景。

单例模式 (Singleton Pattern):面试的“敲门砖”

单例模式是Java面试中最基础也最常考的模式之一。它的核心目标是确保一个类只有一个实例,并提供一个全局访问点。虽然概念简单,但在多线程环境下的实现却大有学问。

1. 饿汉式 vs 懒汉式

饿汉式(Eager Initialization)在类加载时就创建实例,线程安全但可能浪费资源;懒汉式(Lazy Initialization)在第一次使用时创建实例,节省资源但需处理线程安全问题。

2. 双重检查锁定 (DCL)

这是面试中的高频追问点。通过 synchronized 关键字和 volatile 关键字的结合,实现既线程安全又高效的懒汉式单例。

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;
    }
}

3. 枚举单例

Effective Java 作者推荐的方式。枚举类型天然线程安全,且能防止反序列化破坏单例,是防御性编程的最佳实践。

public enum SingletonEnum {
    INSTANCE;
    public void doSomething() {
        // 业务逻辑
    }
}

✅ 优点

  • 内存中只有一个实例,减少内存开销。
  • 避免对资源的多重占用。
  • 配置全局唯一,便于管理。

❌ 缺点

  • 扩展困难,不支持继承。
  • 职责过重,可能违背单一职责原则。
  • 在多线程环境下需要小心处理同步。

工厂模式 (Factory Pattern):解耦的艺术

工厂模式定义了创建对象的接口,让子类决定实例化哪一个类。它将对象的创建与使用分离,极大地提高了系统的灵活性和可扩展性。在Spring框架中,BeanFactory 就是工厂模式的典型应用。

简单工厂
工厂方法
抽象工厂

简单工厂模式 (Simple Factory)

由一个工厂类根据传入的参数,决定创建出哪一个产品类的实例。它不属于GoF 23种设计模式之一,但非常实用。

public class ProductFactory {
    public static Product createProduct(String type) {
        if ("A".equals(type)) {
            return new ProductA();
        } else if ("B".equals(type)) {
            return new ProductB();
        }
        return null;
    }
}

适用场景:工厂类负责创建的对象比较少,客户端只需要传入工厂类的参数,对于如何创建对象不关心。

工厂方法模式 (Factory Method)

定义一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法模式让类的实例化推迟到子类。

public interface ProductFactory {
    Product createProduct();
}
public class ConcreteFactoryA implements ProductFactory {
    @Override
    public Product createProduct() {
        return new ProductA();
    }
}

优点:符合开闭原则,增加新产品无需修改现有代码,只需增加新的工厂类。

抽象工厂模式 (Abstract Factory)

为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。适用于有多个产品系列的系统。

经典案例:Java中的 javax.xml.parsers.DocumentBuilderFactory 就是抽象工厂的应用。

观察者模式 (Observer Pattern):事件驱动的核心

观察者模式定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。在Java中,java.util.Observable 类(虽已废弃)和事件监听机制(如Swing、Spring Event)都是其典型应用。

核心组件

Java中的实际应用

在Spring框架中,ApplicationEventPublisher 和 @EventListener 注解极大地简化了观察者模式的实现。这种发布-订阅机制广泛应用于解耦业务逻辑,例如:用户注册成功后发送欢迎邮件、记录日志等。

特性 观察者模式 发布-订阅模式
依赖关系 观察者直接依赖主题 通过消息中间件解耦
触发机制 同步调用 异步消息传递
典型场景 GUI事件处理、Spring Event RabbitMQ、Kafka

策略模式 (Strategy Pattern):算法的灵活切换

策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。它完美体现了“开闭原则”和“依赖倒置原则”。

代码示例:支付策略

假设我们需要支持支付宝、微信支付和银联支付。使用策略模式可以避免大量的 if-else 判断。

// 策略接口
public interface PayStrategy {
    void pay(double amount);
}
// 具体策略
public class AlipayStrategy implements PayStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("支付宝支付: " + amount);
    }
}
// 上下文
public class PayContext {
    private PayStrategy strategy;
    public PayContext(PayStrategy strategy) {
        this.strategy = strategy;
    }
    public void executePay(double amount) {
        strategy.pay(amount);
    }
}

面试陷阱:策略模式 vs 状态模式

许多开发者容易混淆这两种模式。策略模式侧重于算法的替换,客户端主动选择策略;而状态模式侧重于对象内部状态的改变导致行为的改变,状态转换通常由对象自身管理。在面试中,清晰地阐述这一区别是加分项。

代理模式 (Proxy Pattern):AOP的基石

代理模式为其他对象提供一种代理以控制对这个对象的访问。在Java中,代理模式主要分为静态代理和动态代理。Spring AOP 的核心就是动态代理。

1. 静态代理

代理类和目标类在编译期就确定好了。需要为每个目标类编写一个代理类,代码冗余度高。

2. 动态代理 (JDK & CGLIB)

在运行期动态生成代理类。JDK动态代理基于接口,要求目标类实现接口;CGLIB基于继承,通过字节码技术生成目标类的子类。Spring默认使用JDK动态代理,如果目标类没有接口,则自动切换为CGLIB。

// JDK动态代理示例
public class ProxyHandler implements InvocationHandler {
    private Object target;
    public ProxyHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method");
        Object result = method.invoke(target, args);
        System.out.println("After method");
        return result;
    }
}
// 使用
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new ProxyHandler(target)
);

Java设计模式面试高频Q&A

以下是网友们在面试中最常遇到的问题,以及深度解答:

Q1: 为什么推荐枚举单例?

枚举单例不仅线程安全,而且能防止反射和反序列化攻击。反射可以通过 setAccessible(true) 破坏普通单例,反序列化可以生成新实例,而枚举类型天然禁止这些操作。

Q2: 工厂模式和抽象工厂模式的区别?

工厂方法模式针对的是一个产品等级结构(如:只生产手机),而抽象工厂模式针对的是多个产品等级结构(如:同时生产手机和电脑)。抽象工厂是工厂方法的升级。

Q3: 策略模式如何与Spring结合?

可以将每个策略实现类注册为Spring Bean,然后通过 Map<String, Strategy> 注入到上下文中,根据key动态获取策略实现,避免大量的if-else。

Q4: 代理模式和装饰器模式的区别?

代理模式主要控制对对象的访问(增强权限、延迟加载等),不改变对象的核心功能;装饰器模式旨在增强对象的功能,通常通过包装对象来增加新的行为。