Java 设计模式详解 – 从理论到实战

本文系统梳理 Java 中常用的设计模式,涵盖创建型、结构型、行为型三大分类,结合 Spring Boot 实际项目(LinkForge)中的真实应用场景进行解析。

目录


一、创建型模式

创建型模式关注对象的创建机制,将对象的创建与使用分离,降低系统的耦合度。

1.1 单例模式

意图: 保证一个类仅有一个实例,并提供一个全局访问点。

适用场景: 全局配置管理器、线程池、数据库连接池、日志对象等需要全局唯一实例的场景。

1.1.1 双重检查锁(DCL)
public class Singleton {
    // volatile 禁止指令重排序,保证多线程环境下实例的可见性
    private static volatile Singleton INSTANCE = null;

    // 私有构造方法,防止外部直接 new
    private Singleton() {
    }

    // 公有静态工厂方法,返回唯一实例
    public static Singleton getInstance() {
        if (INSTANCE == null) {                   // 第一次检查(无锁,提高性能)
            synchronized (Singleton.class) {
                if (INSTANCE == null) {           // 第二次检查(锁内,保证唯一)
                    INSTANCE = new Singleton();    // 问题:此处非原子操作
                }
            }
        }
        return INSTANCE;
    }
}

为什么需要 volatile? INSTANCE = new Singleton() 这一行并非原子操作,JVM 会将其编译为三条指令:

  1. 分配内存空间
  2. 初始化对象
  3. 将引用指向内存地址

在 JDK 1.5 之前,指令 2 和 3 可能被重排序(先执行 3 再执行 2)。如果线程 A 执行完指令 3 还未执行 2 时,线程 B 判断 INSTANCE != null 直接返回,就会拿到一个尚未初始化的对象。volatile 关键字通过内存屏障(Memory Barrier)禁止这种重排序。

注意: 从 JDK 5 开始,volatile 已经能可靠地解决这个问题。在 Java 9+ 中还可以结合 VarHandle 实现更细粒度的控制,但 DCL 仍然是实践中最常用的单例写法之一。

1.1.2 静态内部类
public class Singleton {
    // 静态内部类在外部类加载时不会被加载,实现了懒加载
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton() {
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

原理: JVM 在类的初始化阶段(即首次主动使用时)会获取锁,这个机制被 JVM 规范保证是线程安全的。静态内部类 SingletonHolder 只有在 getInstance() 首次被调用时才会加载,天然实现了懒加载,同时不需要 synchronized,性能优于 DCL。

1.1.3 枚举单例
public enum Singleton {
    INSTANCE;

    public void doSomething() {
        System.out.println("execute business logic");
    }
}

枚举实现单例的优势:

特性 枚举单例 DCL 静态内部类
线程安全 JVM 保证 需 volatile JVM 保证
防止反射攻击 天然防止(无公共构造器) 不防止 不防止
防止序列化破坏 天然防止 需 readResolve 需 readResolve
代码简洁度 最高 较复杂 中等
懒加载 否(类加载即初始化)

关于序列化: 枚举类型的序列化机制与普通类不同。JVM 规范规定,枚举的序列化只写入枚举常量的名称,反序列化时通过 Enum.valueOf() 返回已有的实例,不会创建新对象。因此枚举单例天然免疫序列化/反序列化攻击,无需额外实现 readResolve()

《Effective Java》作者 Joshua Bloch 推荐枚举作为实现单例的最佳方式。唯一的限制是枚举不支持懒加载,不过在实践中,大部分单例对象的初始化成本并不高,这一限制通常可以接受。


1.2 工厂方法模式

意图: 定义一个创建对象的接口,但让子类决定实例化哪个类。工厂方法使类的实例化延迟到子类。

适用场景: 创建逻辑复杂、需要根据条件动态选择实现类、希望与具体创建逻辑解耦时。

标准实现
// 产品接口
public interface Product {
    void use();
}

// 具体产品 A
public class ConcreteProductA implements Product {
    @Override
    public void use() {
        System.out.println("使用 ConcreteProductA");
    }
}

// 具体产品 B
public class ConcreteProductB implements Product {
    @Override
    public void use() {
        System.out.println("使用 ConcreteProductB");
    }
}

// 抽象工厂(Creator)
public abstract class Creator {
    public abstract Product factoryMethod();
}

// 具体工厂 A
public class ConcreteCreatorA extends Creator {
    @Override
    public Product factoryMethod() {
        return new ConcreteProductA();
    }
}

// 具体工厂 B
public class ConcreteCreatorB extends Creator {
    @Override
    public Product factoryMethod() {
        return new ConcreteProductB();
    }
}

客户端调用:

Creator creatorA = new ConcreteCreatorA();
Product productA = creatorA.factoryMethod();
productA.use();  // 输出: 使用 ConcreteProductA
工厂方法 vs 简单工厂 vs 抽象工厂
模式 核心特点 开闭原则 复杂度 适用场景
简单工厂 一个工厂类包含所有创建逻辑 不满足(新增产品需修改工厂) 产品种类少且稳定
工厂方法 每个产品对应一个工厂子类 满足 产品种类多且经常扩展
抽象工厂 一组相关产品由同一工厂族创建 满足 跨平台/跨主题的多产品族

在实际开发中,如果产品种类不多且变化不频繁,简单工厂往往是最务实的选择。设计模式的目的是解决问题,而不是为了使用而使用。


1.3 建造者模式

意图: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

适用场景: 对象有大量可选参数、构造过程复杂且需要分步骤、需要创建不可变对象时。

经典实现
public class HttpRequest {
    private final String url;
    private final String method;
    private final Map<String, String> headers;
    private final String body;
    private final int timeout;

    private HttpRequest(Builder builder) {
        this.url = builder.url;
        this.method = builder.method;
        this.headers = Collections.unmodifiableMap(builder.headers);
        this.body = builder.body;
        this.timeout = builder.timeout;
    }

    public static class Builder {
        // 必选参数
        private final String url;
        // 可选参数(有默认值)
        private String method = "GET";
        private Map<String, String> headers = new HashMap<>();
        private String body = null;
        private int timeout = 5000;

        public Builder(String url) {
            this.url = url;
        }

        public Builder method(String method) {
            this.method = method;
            return this;
        }

        public Builder addHeader(String key, String value) {
            this.headers.put(key, value);
            return this;
        }

        public Builder body(String body) {
            this.body = body;
            return this;
        }

        public Builder timeout(int timeout) {
            this.timeout = timeout;
            return this;
        }

        public HttpRequest build() {
            return new HttpRequest(this);
        }
    }

    // getter 方法省略...
}

客户端调用:

HttpRequest request = new HttpRequest.Builder("https://api.example.com/users")
        .method("POST")
        .addHeader("Content-Type", "application/json")
        .addHeader("Authorization", "Bearer token")
        .body("{\"name\": \"test\"}")
        .timeout(10000)
        .build();
Lombok @Builder

在实际项目中,通常使用 Lombok 的 @Builder 注解来简化建造者模式的编写:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderPaidMessage {
    private Long orderId;
    private String orderNo;
    private Long userId;
    private BigDecimal totalAmount;
    private long timestamp;
}

Lombok 会在编译期自动生成 Builder 内部类和 builder() 静态方法,用法如下:

OrderPaidMessage message = OrderPaidMessage.builder()
        .orderId(1L)
        .orderNo("ORD20260506001")
        .userId(100L)
        .totalAmount(new BigDecimal("99.00"))
        .timestamp(System.currentTimeMillis())
        .build();

建造者模式的几个要点:

  • 构造器参数设为 final,确保对象构建完成后不可变
  • build() 方法中可以做参数校验(Lombok 可通过自定义 builder 方法实现)
  • 如果参数超过 4 个且其中大部分是可选的,就应该考虑使用建造者模式

二、结构型模式

结构型模式关注类和对象的组合方式,通过继承和组合来形成更大的结构。

2.1 装饰器模式

意图: 动态地给一个对象添加一些额外的职责,比生成子类更加灵活。

适用场景: 需要在不修改原有类的前提下扩展功能、功能可以动态组合或撤销时。

标准实现
// 组件接口
public interface DataSource {
    String readData();
    void writeData(String data);
}

// 具体组件
public class FileDataSource implements DataSource {
    private final String filename;

    public FileDataSource(String filename) {
        this.filename = filename;
    }

    @Override
    public String readData() {
        // 从文件读取数据
        return "raw data from " + filename;
    }

    @Override
    public void writeData(String data) {
        // 写入文件
        System.out.println("写入文件: " + filename);
    }
}

// 装饰器基类
public abstract class DataSourceDecorator implements DataSource {
    protected final DataSource wrapped;

    protected DataSourceDecorator(DataSource source) {
        this.wrapped = source;
    }

    @Override
    public String readData() {
        return wrapped.readData();
    }

    @Override
    public void writeData(String data) {
        wrapped.writeData(data);
    }
}

// 加密装饰器
public class EncryptionDecorator extends DataSourceDecorator {
    public EncryptionDecorator(DataSource source) {
        super(source);
    }

    @Override
    public String readData() {
        String data = super.readData();
        return decrypt(data);
    }

    @Override
    public void writeData(String data) {
        super.writeData(encrypt(data));
    }

    private String encrypt(String data) { /* 加密逻辑 */ return "encrypted:" + data; }
    private String decrypt(String data) { /* 解密逻辑 */ return data.replace("encrypted:", ""); }
}

// 压缩装饰器
public class CompressionDecorator extends DataSourceDecorator {
    public CompressionDecorator(DataSource source) {
        super(source);
    }

    @Override
    public String readData() {
        String data = super.readData();
        return decompress(data);
    }

    @Override
    public void writeData(String data) {
        super.writeData(compress(data));
    }

    private String compress(String data) { /* 压缩逻辑 */ return "compressed:" + data; }
    private String decompress(String data) { /* 解压逻辑 */ return data.replace("compressed:", ""); }
}

客户端调用:

DataSource source = new FileDataSource("data.txt");
DataSource encrypted = new EncryptionDecorator(source);
DataSource encryptedAndCompressed = new CompressionDecorator(encrypted);

encryptedAndCompressed.writeData("important data");  // 先加密再压缩再写入
String result = encryptedAndCompressed.readData();     // 先读取再解压再解密

装饰器模式的核心特征:

  • 装饰器与被装饰对象实现相同的接口
  • 装饰器内部持有被装饰对象的引用
  • 装饰器可以在调用被装饰对象的前后添加额外行为
  • 装饰器可以层层嵌套,形成装饰链

与继承的对比: 装饰器模式比继承更加灵活。如果用继承实现上述加密+压缩的功能,需要 EncryptionDataSourceCompressionDataSourceEncryptionCompressionDataSource 等多个子类,组合数量随功能维度呈指数增长。装饰器模式则可以任意组合。

Java I/O 体系是装饰器模式的经典应用:BufferedInputStream(new GZIPInputStream(new FileInputStream("data.gz"))) 就是通过多层装饰实现了缓冲+解压缩+文件读取的功能组合。


2.2 桥接模式

意图: 将抽象部分与它的实现部分分离,使它们都可以独立地变化。

适用场景: 一个类存在两个或多个独立变化的维度,且这两个维度都需要扩展时。

标准实现
// 实现接口(形状的颜色)
public interface Color {
    String applyColor();
}

// 具体实现
public class Red implements Color {
    @Override
    public String applyColor() { return "红色"; }
}

public class Blue implements Color {
    @Override
    public String applyColor() { return "蓝色"; }
}

// 抽象部分(形状)
public abstract class Shape {
    protected final Color color;

    protected Shape(Color color) {
        this.color = color;
    }

    public abstract String draw();
}

// 扩展抽象
public class Circle extends Shape {
    protected Circle(Color color) {
        super(color);
    }

    @Override
    public String draw() {
        return "圆形,颜色: " + color.applyColor();
    }
}

public class Square extends Shape {
    protected Square(Color color) {
        super(color);
    }

    @Override
    public String draw() {
        return "正方形,颜色: " + color.applyColor();
    }
}

客户端调用:

Shape redCircle = new Circle(new Red());
Shape blueSquare = new Square(new Blue());

System.out.println(redCircle.draw());   // 圆形,颜色: 红色
System.out.println(blueSquare.draw());  // 正方形,颜色: 蓝色

桥接 vs 继承: 如果不使用桥接模式,要支持 N 种形状 x M 种颜色,需要 N x M 个子类。使用桥接模式后,只需要 N + M 个类。桥接的本质是"组合优于继承"这一原则在特定场景下的应用。

JDBC 中的桥接模式

JDBC 是桥接模式的一个经典应用。java.sql.Driver 是实现接口,每个数据库厂商(MySQL、PostgreSQL 等)提供自己的实现。DriverManager 是抽象部分,通过 Driver 接口与具体数据库驱动交互,应用代码只依赖 JDBC API,不关心底层驱动实现。


2.3 适配器模式

意图: 将一个类的接口转换成客户期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以协同工作。

适用场景: 需要复用已有的类但接口不匹配、集成第三方库但接口风格不一致时。

类适配器(通过继承)
// 已有的第三方接口
public class LegacyLogger {
    public void logMessage(String level, String msg) {
        System.out.println("[" + level + "] " + msg);
    }
}

// 目标接口
public interface ModernLogger {
    void info(String msg);
    void error(String msg);
}

// 适配器(继承被适配者,实现目标接口)
public class LoggerAdapter extends LegacyLogger implements ModernLogger {
    @Override
    public void info(String msg) {
        logMessage("INFO", msg);
    }

    @Override
    public void error(String msg) {
        logMessage("ERROR", msg);
    }
}
对象适配器(通过组合,推荐)
public class LoggerAdapter implements ModernLogger {
    private final LegacyLogger legacyLogger;

    public LoggerAdapter(LegacyLogger legacyLogger) {
        this.legacyLogger = legacyLogger;
    }

    @Override
    public void info(String msg) {
        legacyLogger.logMessage("INFO", msg);
    }

    @Override
    public void error(String msg) {
        legacyLogger.logMessage("ERROR", msg);
    }
}

类适配器 vs 对象适配器: 类适配器通过继承实现,可以重写被适配者的方法,但受限于 Java 的单继承;对象适配器通过组合实现,更灵活,可以适配被适配者的子类,是实践中更常用的方式。


2.4 代理模式

意图: 为其他对象提供一种代理以控制对这个对象的访问。

适用场景: 远程代理(RPC)、虚拟代理(延迟加载)、保护代理(权限控制)、智能引用(AOP)。

静态代理
public interface UserService {
    User getUserById(Long id);
    void createUser(User user);
}

public class UserServiceImpl implements UserService {
    @Override
    public User getUserById(Long id) {
        System.out.println("查询数据库获取用户: " + id);
        return new User(id, "张三");
    }

    @Override
    public void createUser(User user) {
        System.out.println("插入数据库: " + user);
    }
}

// 代理类
public class UserServiceProxy implements UserService {
    private final UserService target;

    public UserServiceProxy(UserService target) {
        this.target = target;
    }

    @Override
    public User getUserById(Long id) {
        System.out.println("[代理] 缓存检查...");
        User user = target.getUserById(id);
        System.out.println("[代理] 缓存结果...");
        return user;
    }

    @Override
    public void createUser(User user) {
        System.out.println("[代理] 权限校验...");
        target.createUser(user);
        System.out.println("[代理] 记录日志...");
    }
}

静态代理的缺点: 每个被代理的接口都需要手动编写一个代理类,接口方法变更时代理类也需要同步修改,维护成本高。

JDK 动态代理
public class JdkProxy implements InvocationHandler {
    private final Object target;

    public JdkProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("[动态代理] 前置增强");
        Object result = method.invoke(target, args);
        System.out.println("[动态代理] 后置增强");
        return result;
    }

    public static <T> T createProxy(T target) {
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new JdkProxy(target)
        );
    }
}

客户端调用:

UserService userService = JdkProxy.createProxy(new UserServiceImpl());
userService.getUserById(1L);  // 自动触发代理逻辑

JDK 动态代理 vs CGLIB: JDK 动态代理要求目标类实现至少一个接口,通过接口创建代理对象;CGLIB 通过生成目标类的子类来实现代理,不需要接口。Spring AOP 默认策略是:目标类实现了接口用 JDK 代理,否则用 CGLIB。在现代 Spring Boot 项目中(Spring Boot 2.0+),默认统一使用 CGLIB。

Spring AOP 中的代理模式

Spring 的 @Transactional@Cacheable@Async 等注解本质上都是通过代理模式实现的:

@Service
public class UserServiceImpl implements UserService {

    @Cacheable(value = "user", key = "#id")
    @Override
    public UserResponse getUserById(Long id) {
        // 业务逻辑
        return userMapper.selectById(id);
    }
}

当调用 getUserById() 时,实际执行的是 Spring 生成的代理对象。代理对象在方法执行前检查缓存,命中则直接返回,未命中才执行目标方法并缓存结果。这就是代理模式在 AOP 中的典型应用。


2.5 外观模式

意图: 为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,使得子系统更加容易使用。

适用场景: 子系统复杂、需要简化调用链路、需要为子系统提供分层访问入口时。

标准实现
// 子系统 A:视频编解码
public class VideoEncoder {
    public byte[] encode(String format, byte[] rawData) {
        System.out.println("使用 " + format + " 编解码器编码视频");
        return rawData;  // 简化示例
    }
}

// 子系统 B:音频处理
public class AudioProcessor {
    public byte[] normalize(byte[] audioData) {
        System.out.println("音频标准化处理");
        return audioData;
    }
}

// 子系统 C:字幕合成
public class SubtitleRenderer {
    public byte[] render(byte[] videoData, String subtitleFile) {
        System.out.println("合成字幕: " + subtitleFile);
        return videoData;
    }
}

// 外观类
public class MediaConverterFacade {
    private final VideoEncoder encoder = new VideoEncoder();
    private final AudioProcessor audioProcessor = new AudioProcessor();
    private final SubtitleRenderer subtitleRenderer = new SubtitleRenderer();

    public byte[] convertToMp4(byte[] rawVideo, byte[] rawAudio, String subtitleFile) {
        byte[] encoded = encoder.encode("H.264", rawVideo);
        byte[] normalized = audioProcessor.normalize(rawAudio);
        byte[] withSubtitle = subtitleRenderer.render(encoded, subtitleFile);
        System.out.println("MP4 转换完成");
        return withSubtitle;
    }
}

客户端调用:

// 不使用外观:客户端需要了解所有子系统的 API
VideoEncoder encoder = new VideoEncoder();
AudioProcessor audio = new AudioProcessor();
SubtitleRenderer subtitle = new SubtitleRenderer();
byte[] step1 = encoder.encode("H.264", rawVideo);
byte[] step2 = audio.normalize(rawAudio);
byte[] step3 = subtitle.render(step1, subtitleFile);

// 使用外观:一个调用搞定
MediaConverterFacade converter = new MediaConverterFacade();
byte[] result = converter.convertToMp4(rawVideo, rawAudio, subtitleFile);

外观模式在框架层面也有广泛应用。Spring Boot 的自动配置(Auto-Configuration)本质上就是一种外观模式 – 它将繁琐的手动配置封装成简单的 @SpringBootApplication 启动方式,开发者不需要了解底层 DataSourceEntityManagerTransactionManager 等组件的具体配置方式。


三、行为型模式

行为型模式关注对象之间的通信机制,描述对象之间怎样协作完成任务。

3.1 模板方法模式

意图: 定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些特定步骤。

适用场景: 多个类有相似的业务流程但个别步骤不同、需要控制子类扩展点的固定算法骨架。

标准实现
public abstract class DataParser {
    // 模板方法(final 防止子类重写骨架)
    public final void parse(String filePath) {
        String rawData = readFile(filePath);
        Object parsed = parseData(rawData);
        validateData(parsed);
        saveData(parsed);
        postProcess();
    }

    // 具体方法(子类共享)
    private String readFile(String filePath) {
        System.out.println("读取文件: " + filePath);
        return "raw content";
    }

    // 抽象方法(子类必须实现)
    protected abstract Object parseData(String rawData);
    protected abstract void validateData(Object data);

    // 钩子方法(子类可选重写)
    protected void saveData(Object data) {
        System.out.println("默认保存逻辑");
    }

    protected void postProcess() {
        // 默认空实现,子类可选重写
    }
}

public class JsonDataParser extends DataParser {
    @Override
    protected Object parseData(String rawData) {
        System.out.println("JSON 格式解析");
        return new Object();
    }

    @Override
    protected void validateData(Object data) {
        System.out.println("JSON Schema 校验");
    }

    @Override
    protected void saveData(Object data) {
        System.out.println("保存到 MongoDB");
    }
}

public class CsvDataParser extends DataParser {
    @Override
    protected Object parseData(String rawData) {
        System.out.println("CSV 格式解析");
        return new Object();
    }

    @Override
    protected void validateData(Object data) {
        System.out.println("CSV 列数校验");
    }
}
模板方法中的方法类型
方法类型 说明 是否必须重写
模板方法 定义算法骨架,通常是 final 不允许重写
抽象方法 算法中的必要步骤 子类必须实现
钩子方法 提供默认实现,子类可选重写 可选
具体方法 所有子类共享的通用逻辑 不应该重写
MyBatis 中的模板方法

MyBatis 的 BaseExecutor 是模板方法模式的经典实现。BaseExecutor 定义了 SQL 执行的核心骨架(update()query() 方法),其中 doUpdate()doQuery() 等是抽象方法,由 SimpleExecutorReuseExecutorBatchExecutor 等子类实现具体的执行逻辑。同时 SqlSession 的标准使用流程(获取 SqlSession -> 获取 Mapper -> 执行 SQL -> 提交/回滚 -> 关闭)也是一个典型的模板方法。


3.2 策略模式

意图: 定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。本模式使得算法可以独立于使用它的客户端变化。

适用场景: 同一个任务有多种处理方式、需要在运行时动态切换算法、避免大量条件判断语句时。

标准实现
// 策略接口
public interface PaymentStrategy {
    void pay(BigDecimal amount);
}

// 具体策略
public class AlipayStrategy implements PaymentStrategy {
    @Override
    public void pay(BigDecimal amount) {
        System.out.println("支付宝支付: " + amount + " 元");
    }
}

public class WechatPayStrategy implements PaymentStrategy {
    @Override
    public void pay(BigDecimal amount) {
        System.out.println("微信支付: " + amount + " 元");
    }
}

public class CreditCardStrategy implements PaymentStrategy {
    @Override
    public void pay(BigDecimal amount) {
        System.out.println("信用卡支付: " + amount + " 元");
    }
}

// 上下文
public class PaymentContext {
    private PaymentStrategy strategy;

    public void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void executePayment(BigDecimal amount) {
        strategy.pay(amount);
    }
}

客户端调用:

PaymentContext context = new PaymentContext();

context.setStrategy(new AlipayStrategy());
context.executePayment(new BigDecimal("99.00"));     // 支付宝支付

context.setStrategy(new WechatPayStrategy());
context.executePayment(new BigDecimal("49.50"));     // 微信支付
策略模式 vs if-else

策略模式的核心价值在于消除冗长的 if-else / switch 分支。当 if-else 中的每个分支都代表一种独立的算法时,就应该考虑将其提取为策略:

// 策略模式通常配合工厂 + 枚举/Map 来消除条件判断
public enum PaymentType {
    ALIPAY, WECHAT, CREDIT_CARD
}

public class PaymentStrategyFactory {
    private static final Map<PaymentType, PaymentStrategy> STRATEGIES = Map.of(
        PaymentType.ALIPAY, new AlipayStrategy(),
        PaymentType.WECHAT, new WechatPayStrategy(),
        PaymentType.CREDIT_CARD, new CreditCardStrategy()
    );

    public static PaymentStrategy getStrategy(PaymentType type) {
        return STRATEGIES.get(type);
    }
}

3.3 观察者模式

意图: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

适用场景: 事件驱动架构、消息通知、数据变更同步、解耦生产者和消费者。

标准实现
// 观察者接口
public interface EventListener {
    void onEvent(Object event);
}

// 事件发布者
public class EventBus {
    private final List<EventListener> listeners = new CopyOnWriteArrayList<>();

    public void register(EventListener listener) {
        listeners.add(listener);
    }

    public void unregister(EventListener listener) {
        listeners.remove(listener);
    }

    public void publish(Object event) {
        for (EventListener listener : listeners) {
            listener.onEvent(event);
        }
    }
}

// 具体观察者
public class EmailNotificationListener implements EventListener {
    @Override
    public void onEvent(Object event) {
        System.out.println("发送邮件通知: " + event);
    }
}

public class LogListener implements EventListener {
    @Override
    public void onEvent(Object event) {
        System.out.println("记录日志: " + event);
    }
}

客户端调用:

EventBus bus = new EventBus();
bus.register(new EmailNotificationListener());
bus.register(new LogListener());

bus.publish("用户注册成功");  // 自动通知所有观察者
Spring 事件机制

Spring 的 ApplicationEvent 体系是观察者模式的内置实现,支持同步和异步两种模式:

// 定义事件(继承 ApplicationEvent)
@Getter
public class UserCreatedEvent extends ApplicationEvent {
    private final Long userId;
    private final String username;

    public UserCreatedEvent(Object source, Long userId, String username) {
        super(source);
        this.userId = userId;
        this.username = username;
    }
}

// 发布事件(通过 ApplicationEventPublisher)
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
    private final ApplicationEventPublisher eventPublisher;

    public Long createUser(UserCreateRequest request) {
        // ... 业务逻辑
        eventPublisher.publishEvent(
            new UserCreatedEvent(this, user.getId(), user.getUsername())
        );
        return user.getId();
    }
}

// 监听事件(通过 @EventListener)
@Component
public class UserEventListener {
    @Async
    @EventListener
    public void handleUserCreated(UserCreatedEvent event) {
        // 异步发送欢迎邮件、初始化默认数据等
        System.out.println("用户注册通知: " + event.getUsername());
    }
}
观察者模式的变体对比
变体 通信范围 持久化 可靠性 典型实现
进程内观察者 单 JVM 内 JVM 崩溃即丢失 Spring ApplicationEvent
跨进程发布订阅 多 JVM / 服务间 消息持久化到 Broker 高(支持重试、死信队列) Kafka、RabbitMQ
分布式事件驱动 跨网络 持久化 最高(支持 exactly-once) Kafka Streams、EventBridge

在单体应用中,Spring Event 足以应对大部分异步解耦需求;在微服务架构下,通常需要引入 Kafka 或 RabbitMQ 实现跨进程的事件驱动。


3.4 责任链模式

意图: 将请求的发送者和接收者解耦,使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。

适用场景: 请求需要经过多个处理步骤、处理步骤可以动态增减、每个步骤可以决定放行或终止请求时。

标准实现
// 抽象处理器
public abstract class Handler {
    protected Handler next;

    public Handler setNext(Handler next) {
        this.next = next;
        return next;  // 支持链式调用
    }

    public abstract boolean handle(String request);
}

// 具体处理器
public class AuthHandler extends Handler {
    @Override
    public boolean handle(String request) {
        if (!request.contains("token")) {
            System.out.println("认证失败:缺少 token");
            return false;
        }
        System.out.println("认证通过");
        return next == null || next.handle(request);
    }
}

public class RateLimitHandler extends Handler {
    @Override
    public boolean handle(String request) {
        if (request.contains("blocked")) {
            System.out.println("限流拦截:请求过于频繁");
            return false;
        }
        System.out.println("限流通过");
        return next == null || next.handle(request);
    }
}

public class LogHandler extends Handler {
    @Override
    public boolean handle(String request) {
        System.out.println("记录访问日志: " + request);
        return next == null || next.handle(request);
    }
}

客户端调用:

Handler auth = new AuthHandler();
Handler rateLimit = new RateLimitHandler();
Handler log = new LogHandler();

auth.setNext(rateLimit).setNext(log);

auth.handle("request with token");         // 认证通过 -> 限流通过 -> 记录日志
auth.handle("blocked request");             // 认证通过 -> 限流拦截
auth.handle("request without token");       // 认证失败
Servlet Filter 与 Spring Security FilterChain

Servlet 规范中的 Filter 和 Spring Security 的 SecurityFilterChain 是责任链模式在 Web 开发中最典型的应用。每个 HTTP 请求都会依次经过 Filter 链中的所有过滤器,任何一个过滤器都可以决定放行(chain.doFilter())或终止(直接返回响应)。

HTTP 请求
    |
    v
[TraceFilter]           -- 生成 TraceId,写入 MDC
    |
    v
[RateLimitInterceptor]  -- 接口限流检查
    |
    v
[JwtAuthenticationFilter] -- JWT 认证
    |
    v
[SecurityFilterChain]   -- Spring Security 授权
    |
    v
[Controller]            -- 业务处理
    |
    v
HTTP 响应(原路返回)

四、分析实际项目中的设计模式

以下基于 LinkForge(Spring Boot + MyBatis + Redis + Kafka 技术栈)项目中的实际代码,分析设计模式在真实业务场景中的应用。
LinkForge项目GitHub地址.

4.1 适配器模式 – Jackson3JsonRedisSerializer

项目自定义了 Jackson3JsonRedisSerializer,实现 Spring Data Redis 的 RedisSerializer<Object> 接口。这个类的职责是将 Java 对象"适配"成 Redis 能存储的字节数组格式。

public class Jackson3JsonRedisSerializer implements RedisSerializer<Object> {

    private final ObjectMapper mapper;

    public Jackson3JsonRedisSerializer() {
        this.mapper = JsonMapper.builder()
                .findAndAddModules()
                .activateDefaultTyping(
                        BasicPolymorphicTypeValidator.builder()
                                .allowIfBaseType(Object.class)
                                .build(),
                        DefaultTyping.NON_FINAL
                )
                .build();
    }

    @Override
    public byte[] serialize(Object value) throws SerializationException {
        if (value == null) return new byte[0];
        return mapper.writeValueAsBytes(value);  // Object -> JSON bytes
    }

    @Override
    public Object deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length == 0) return null;
        return mapper.readValue(bytes, Object.class);  // JSON bytes -> Object
    }
}

模式分析: RedisSerializer<Object> 是目标接口,Jackson3JsonRedisSerializer 是适配器,ObjectMapper(Jackson 3)是被适配者。Spring Data Redis 只需要知道 RedisSerializer 接口,不关心底层具体用哪种 JSON 序列化库。

RedisConfig 中使用:

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new Jackson3JsonRedisSerializer());  // 注入适配器
        template.afterPropertiesSet();
        return template;
    }
}

4.2 代理模式 – Spring AOP 的实际应用

项目中大量使用了 Spring AOP 提供的声明式注解,这些注解的底层实现都是代理模式:

@Service
public class UserServiceImpl implements UserService {

    @Cacheable(value = "user", key = "#id", unless = "#result == null")
    @Override
    public UserResponse getUserById(Long id) {
        // 实际执行时,调用的是 Spring 生成的代理对象
        // 代理对象会先查 Redis 缓存,命中则直接返回
        User user = userMapper.selectById(id);
        return toResponse(user);
    }

    @Transactional(rollbackFor = Exception.class)
    public Long createUser(UserCreateRequest request) {
        // 代理对象负责开启事务、提交/回滚
        userMapper.insert(user);
        return user.getId();
    }
}

模式分析: 当调用 getUserById() 时,实际执行的并非 UserServiceImpl 本身的方法,而是 Spring 通过 CGLIB 生成的代理对象的方法。代理对象在目标方法执行前后插入缓存查询、事务管理等横切逻辑。调用方完全感知不到代理的存在,这就是代理模式的精髓。

4.3 观察者/发布订阅模式 – Spring Event 与 Kafka

项目中同时使用了两种层次的发布订阅机制:

进程内:Spring ApplicationEvent

用户注册成功后发布事件,异步执行非核心逻辑(发送欢迎邮件、初始化默认数据):

// 事件定义
@Getter
public class UserCreatedEvent extends ApplicationEvent {
    private final Long userId;
    private final String username;
    private final String nickname;
    // ...
}

// 事件发布(在 Service 中)
eventPublisher.publishEvent(
    new UserCreatedEvent(this, user.getId(), user.getUsername(), user.getNickname())
);

// 事件监听(异步处理)
@Async
@EventListener
public void handleUserCreated(UserCreatedEvent event) {
    log.info("[异步] 用户注册成功,发送欢迎通知: userId={}", event.getUserId());
}

@Async
@EventListener
public void handleUserCreated_InitDefaults(UserCreatedEvent event) {
    log.info("[异步] 初始化用户默认数据: userId={}", event.getUserId());
}
跨进程:Kafka

订单支付/取消事件通过 Kafka 实现跨进程的发布订阅:

// 生产者:发送消息到 Kafka
@Component
@RequiredArgsConstructor
public class OrderKafkaProducer {
    private final KafkaTemplate<String, Object> kafkaTemplate;

    public void sendOrderPaid(OrderPaidMessage message) {
        kafkaTemplate.send("order-paid",
                String.valueOf(message.getUserId()),  // partition key,保证同一用户消息有序
                message);
    }
}

// 消费者:从 Kafka 消费消息
@KafkaListener(topics = "${order.topic.paid:order-paid}",
               groupId = "order-notification-group")
public void handleOrderPaid(@Payload OrderPaidMessage message,
                            Acknowledgment ack) {
    // 通知仓库备货、发送短信、更新积分...
    ack.acknowledge();  // 手动 ACK
}

两者的选择逻辑:

维度 Spring Event Kafka
通信范围 单 JVM 内 跨 JVM / 服务
消息持久化 否(JVM 崩溃即丢失) 是(持久化到 Broker)
可靠性 中(可配合 @TransactionalEventListener 高(支持手动 ACK + 重试 + 死信队列)
适用场景 同一服务内的异步解耦 微服务间的事件驱动、需要持久化的异步处理

项目中,用户注册通知使用 Spring Event(轻量、快速),订单支付通知使用 Kafka(需要持久化、可能被其他微服务消费),这是根据实际需求做出的合理选择。

4.4 策略模式 – RateLimitInterceptor 限流策略

RateLimitInterceptor 对不同接口使用不同的限流策略,虽然实现上是用 if-else 路由到不同的 LoadingCache,但本质上是策略模式的思想:

@Component
public class RateLimitInterceptor implements HandlerInterceptor {

    // 策略 1:登录接口限流(5次/分钟/IP)
    private final LoadingCache<String, RateLimiter> loginRateLimiters = CacheBuilder.newBuilder()
            .maximumSize(10_000)
            .expireAfterAccess(1, TimeUnit.HOURS)
            .build(new CacheLoader<>() {
                @Override
                public RateLimiter load(String key) {
                    return RateLimiter.create(5.0 / 60.0);  // 5 次/60秒
                }
            });

    // 策略 2:注册接口限流(10次/小时/IP)
    private final LoadingCache<String, RateLimiter> registerRateLimiters = CacheBuilder.newBuilder()
            .maximumSize(10_000)
            .expireAfterAccess(2, TimeUnit.HOURS)
            .build(new CacheLoader<>() {
                @Override
                public RateLimiter load(String key) {
                    return RateLimiter.create(10.0 / 3600.0);  // 10 次/3600秒
                }
            });

    // 策略 3:通用接口限流(100 QPS/IP)
    private final LoadingCache<String, RateLimiter> globalRateLimiters = CacheBuilder.newBuilder()
            .maximumSize(10_000)
            .expireAfterAccess(1, TimeUnit.HOURS)
            .build(new CacheLoader<>() {
                @Override
                public RateLimiter load(String key) {
                    return RateLimiter.create(100.0);
                }
            });

    // 策略路由
    private boolean isAllowed(String ip, String uri, String method) throws ExecutionException {
        if ("/api/auth/login".equals(uri) && "POST".equalsIgnoreCase(method)) {
            return loginRateLimiters.get(ip).tryAcquire();
        }
        if ("/api/users".equals(uri) && "POST".equalsIgnoreCase(method)) {
            return registerRateLimiters.get(ip).tryAcquire();
        }
        return globalRateLimiters.get(ip).tryAcquire();
    }
}

模式分析: 每种限流策略(登录/注册/通用)对应一个 LoadingCache<RateLimiter>,它们共享相同的接口(tryAcquire()),但配置不同的速率参数。当接口数量增加时,可以将路由逻辑提取为 Map<String, RateLimiterSupplier> 进一步优化。

4.5 责任链模式 – Spring Security 过滤器链

项目的 SecurityConfig 配置了标准的过滤器链,HTTP 请求依次经过认证过滤器:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(AbstractHttpConfigurer::disable)
            .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers("/api/users").permitAll()
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }
}

其中 JwtAuthenticationFilter 的实现遵循责任链模式中的"放行或终止"逻辑:

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String token = extractToken(request);
        if (StringUtils.hasText(token) && jwtUtil.validateToken(token)) {
            // 认证通过,设置 SecurityContext
            Long userId = jwtUtil.getUserId(token);
            UsernamePasswordAuthenticationToken auth =
                new UsernamePasswordAuthenticationToken(username, null,
                    List.of(new SimpleGrantedAuthority("ROLE_USER")));
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        // 放行到下一个过滤器
        filterChain.doFilter(request, response);
    }
}

请求经过过滤器链的顺序:TraceFilter(生成 TraceId) -> RateLimitInterceptor(限流检查) -> JwtAuthenticationFilter(JWT 认证) -> AuthorizationFilter(权限校验) -> Controller。任何一个环节都可以决定终止请求。

4.6 单例模式 – Spring Bean 与 SnowflakeIdGenerator

Spring 容器管理的 Bean 默认作用域就是单例(singleton)。项目中的 SnowflakeIdGenerator 通过 @Component 注册为 Spring Bean,由容器保证全局唯一实例:

@Component  // Spring 默认 singleton 作用域
public class SnowflakeIdGenerator {
    // 雪花算法生成分布式唯一 ID
    // 1位符号位 + 41位时间戳 + 10位机器ID + 12位序列号
    private final long workerId;
    private final long datacenterId;

    public synchronized long nextId() {
        // ...
    }
}

模式分析: 这里不需要手动实现单例模式。Spring 的 IoC 容器本身就是一个"单例工厂" – 通过 @Component@Service@Configuration 等注解声明的 Bean 默认都是单例的,由容器保证线程安全和全局唯一。这也体现了"不要重复造轮子"的原则:在框架已经提供了单例管理能力的情况下,手动实现 DCL 或静态内部类反而多余。

4.7 建造者模式 – Lombok @Builder 在 DTO 中的应用

项目中大量使用 Lombok @Builder 简化复杂对象的构建过程,以 OrderPaidMessage 为例:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderPaidMessage {
    private Long orderId;
    private String orderNo;
    private Long userId;
    private BigDecimal totalAmount;
    private long timestamp;
}

使用时通过链式调用构建对象:

OrderPaidMessage message = OrderPaidMessage.builder()
        .orderId(order.getId())
        .orderNo(order.getOrderNo())
        .userId(order.getUserId())
        .totalAmount(order.getTotalAmount())
        .timestamp(System.currentTimeMillis())
        .build();

相比调用全参构造器或逐步 setter,Builder 的可读性更好,参数含义一目了然,且不会出现参数顺序传错的问题。


五、总结与思考

设计模式的本质

设计模式的本质是对面向对象设计原则的实践总结,是前人解决重复问题的经验沉淀。GoF 23 种设计模式并不是需要死记硬背的公式,而是理解其背后思想后在合适场景下的自然运用。

使用原则

  1. 不要为了使用而使用: 设计模式解决的是特定问题。如果问题不存在,引入模式只会增加不必要的复杂度。
  2. 识别问题,再找模式: 遇到"需要解耦"时想到观察者模式,遇到"需要统一处理"时想到拦截器/责任链,遇到"需要灵活替换"时想到策略模式。
  3. 框架已经帮你做了很多: Spring 框架内置了大量设计模式的实现(单例、代理、观察者、工厂方法、模板方法等),实际开发中更重要的是理解框架的设计思想,而不是手动重复实现。
  4. 适度即可: 3 层继承已经够复杂了,不需要为了"扩展性"预留 7 层抽象。YAGNI(You Aren’t Gonna Need It)原则同样适用于设计模式的使用。

项目中的设计模式总结

分类 模式 项目中的体现
创建型 单例模式 Spring Bean 默认单例(@Component
创建型 建造者模式 Lombok @Builder 在 DTO 中的应用
结构型 适配器模式 Jackson3JsonRedisSerializer 适配 Redis 序列化接口
结构型 代理模式 Spring AOP(@Cacheable@Transactional
行为型 观察者模式 Spring ApplicationEvent + Kafka 发布订阅
行为型 策略模式 RateLimitInterceptor 多策略限流
行为型 责任链模式 Spring Security 过滤器链
行为型 模板方法模式 MyBatis BaseExecutor 执行骨架

更多推荐