note · 10,494

设计模式

常见设计模式:单例、策略、工厂、观察者、责任链、建造者等,含设计原则。

设计模式笔记

设计模式

开闭原则:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭

本质是降低修改老代码带来的风险,保护系统的稳定性。

单例模式

所有单例的实现都包含以下两个相同的步骤:

  • 将默认构造函数设为私有,防止其他对象使用单例类的 new 运算符。
  • 新建一个静态构建方法作为构造函数。该函数会“偷偷”调用私有构造函数来创建对象,并将其保存在一个静态成员变量中。此后所有对于该函数的调用都将返回这一缓存对象。

饿汉式

他迫不及待。饭菜刚一做好(类一加载),他不管自己现在吃不吃,立刻就去拿碗(立刻创建实例)。

初始化(加载链接初始化)的时候就会创建这个类

public class Singleton implements Serializable{
 
    // 1.提供一个静态成员变量
    private static final Singleton INSTANCE = new Singleton();
 
    // 2.构造私有
    private Singleton(){
        System.out.println("private Singleton()");
    }
 
    // 3.公共方法名
    public static Singleton getInstance(){
        return INSTANCE;
    }
}

枚举类实现饿汉式单例

public enum Singleton {
    /**
     * 唯一的实例
     */
    INSTANCE;
 
    private Singleton(){ System.out.println("Singleton的构造函数被调用了..."); }
 
    public static void otherMethod(){};
}

懒汉式

  • 最基础的懒汉式 线程安全问题
public class LazySingletonV1 {
 
    // 1. 实例先不创建,保持为 null
    private static LazySingletonV1 instance;
 
    // 2. 构造函数私有化
    private LazySingletonV1() {}
 
    // 3. 第一次调用时,才创建实例
    public static LazySingletonV1 getInstance() {
        if (instance == null) {
            // 问题点:多线程会在这里“闯车”
            instance = new LazySingletonV1();
        }
        return instance;
    }
}
  • 版本 2:加锁的懒汉式(线程安全,但效率低 ⚠️) 只有首次才需要 synchronized
public class LazySingletonV2 {
 
    private static LazySingletonV2 instance;
    private LazySingletonV2() {}
 
    // 3. 对整个方法加锁 (synchronized)
    public static synchronized LazySingletonV2 getInstance() {
        if (instance == null) {
            instance = new LazySingletonV2();
        }
        return instance;
    }
}
  • 版本 3:“双重检查锁定” DCL (Double-Checked Locking)
public class LazySingletonDCL {
    /**
     * 1. 核心:使用 volatile 关键字
     * 这是 DCL 的灵魂,防止指令重排。
     */
    private static volatile LazySingletonDCL instance;
 
    private LazySingletonDCL() {}
 
    public static LazySingletonDCL getInstance() {
        // 第一次检查:如果实例已存在,直接返回,不加锁(效率高)
        if (instance == null) {
 
            // 第二次检查:如果实例不存在,才进入同步块
            synchronized (LazySingletonDCL.class) {
 
                // 第三次检查:进入同步块后,再次确认实例是否为 null
                // (防止多个线程同时通过了第一次检查)
                if (instance == null) {
                    instance = new LazySingletonDCL();
                }
            }
        }
        return instance; // 如果没有volatile的话,这里可能会返回一个半成品
    }
}
  1. 为什么要用static
    1. 静态方法内部只能访问静态变量,getSingleton需要添加static,不然没法获取对象
  2. 为什么要双重检查

防止多个线程在synchronized排队

  1. 为什么 volatile 是“灵魂”?
    • instance = new LazySingletonDCL(); 这行代码在 JVM 中不是原子操作
      1. instance 分配内存空间。
      2. 初始化 instance 对象(调用构造函数)。
      3. instance 变量指向分配的内存地址。
    • 没有 volatile,JVM 可能会“优化”重排指令,执行顺序变为 1 -> 3 -> 2。

单例模式的例子

@Service @RestController @Componet @Bean 默认都是单例的

线程池单例 复用线程池 创建和销毁线程、建立和断开数据库连接,都是极其消耗系统资源的操作

策略模式

  • 策略模式的核心是“封装变化”,目的是把具体业务的实现逻辑和调用逻辑解耦,遵循开闭原则 (OCP)
  • 场景:比如 AI 多模型聊天系统,或者支付系统的多种支付渠道接入
  • 利用 Spring 的 Map 自动注入特性。只要把策略类都加上 @Service 并在注解里起好名字,Spring 启动时就会自动把它们按 Key-Value 的形式收集到一个 Map<String, Strategy> 中。

核心:策略接口 具体策略 上下文

第一步:定义“策略接口” (Strategy)

所有具体的模型,都必须遵循同一个行为标准。

public interface LlmStrategy {
    // 定义统一的生成回复方法
    String generateReply(String message);
}

第二步:实现“具体策略” (Concrete Strategy)

为每一个模型写一个实现类,专注搞定自己的逻辑。

@Service("fast")
public class FastModelStrategy implements LlmStrategy {
 
    @Override
    public String generateReply(String message) {
        // 这里可以写几十行极速模型专属的 API 调用、Token 处理等逻辑
        System.out.println("【极速模型】正在快速生成中...");
        return "响应: " + message + " (耗时 0.5s)";
    }
}
 
@Service("reasoning")
public class ReasoningModelStrategy implements LlmStrategy {
 
    @Override
    public String generateReply(String message) {
        // 这里可以写复杂的思维链(CoT)逻辑、外部工具调用等
        System.out.println("【推理模型】正在深度思考中...");
        return "响应: 经过严密推理,结论是... (耗时 5.0s)";
    }

第三步:构建“上下文” (Context)

让我们的核心业务类摆脱具体的实现细节,它只负责维护一个策略接口的引用,并在需要时调用它。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Map;
 
@Service
public class ChatRoomContext {
 
    // Spring 会自动将所有实现 LlmStrategy 的 Bean 注入到这个 Map 中
    // Key: @Service("xxx") 中定义的名称
    // Value: 具体的策略实例
    @Autowired
    private Map<String, LlmStrategy> strategyMap;
 
    public String handleUserMessage(String modelType, String message) {
        // 1. 动态查找具体策略(依赖注入的 Map 完美替代了 switch-case)
        LlmStrategy strategy = strategyMap.get(modelType);
 
        // 2. 容错处理
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的模型类型: " + modelType);
        }
 
        // 3. 委托给具体的策略去执行
        return strategy.generateReply(message);
    }
}
// 强行与ApplicationContext耦合在了一起
@Service
public class ChatService implements ApplicationContextAware {
    private ApplicationContext context;
 
    @Override
    public void setApplicationContext(ApplicationContext context) {
        this.context = context;
    }
 
    public String ask(String modelType, String question) {
        // 运行时去 Spring 容器里“捞”对象
        LlmStrategy strategy = context.getBean(modelType, LlmStrategy.class);
        return strategy.generateReply(question);
    }
}

工厂模式

核心思想:专业的人做专业的事。把“创建对象”和“使用对象”彻底分开。

简单工厂 (Simple Factory) —— 最常用,但不太完美

我们先快速复习一下简单工厂。假设我们要导出数据,有导出 PDF 和导出 CSV 两种格式。

简单工厂是这么写的:

public class ExportFactory {
    // 所有的逻辑全塞在这个静态方法里
    public static FileExporter create(String type) {
        if ("PDF".equals(type)) {
            return new PdfExporter();
        } else if ("CSV".equals(type)) {
            return new CsvExporter();
        }
        return null;
    }
}

工厂方法模式 抽象产品-抽象工厂-具体工厂产生具体产品

// 1. 抽象产品(标准)
public interface LlmClient {
    void invoke(String prompt);
}
 
// 2. 抽象工厂(标准) -> 这是核心,定义了规范但不实现!
public interface LlmFactory {
    LlmClient createClient();
}
 
// ---------------- 以下为扩展区(对修改关闭,对扩展开放) ----------------
 
// 具体产品A & 具体工厂A
public class DeepSeekClient implements LlmClient {
    public void invoke(String prompt) { /* ... */ }
}
@Component("deepseek")
public class DeepSeekFactory implements LlmFactory {
    @Override
    public LlmClient createClient() {
        // 封装极其复杂的初始化:鉴权、配置超时、建立长连接
        return new DeepSeekClient();
    }
}
 
// 具体产品B & 具体工厂B
public class GptClient implements LlmClient {
    public void invoke(String prompt) { /* ... */ }
}
 
@Component("gpt")
public class GptFactory implements LlmFactory {
    @Override
    public LlmClient createClient() {
        return new GptClient();
    }
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Map;
 
@Service
public class AiChatService {
 
    // 【核心精髓】:注入的是工厂的 Map,而不是产品的 Map!
    // Key: @Component 指定的名字 (如 "deepseek", "gpt")
    // Value: 具体的工厂实例
    @Autowired
    private Map<String, LlmFactory> factoryMap;
 
    /**
     * 对外提供的通用聊天方法
     * @param modelType 前端传来的模型标识
     * @param prompt 用户的提问
     */
    public void chat(String modelType, String prompt) {
 
        // 1. 根据模型类型,动态获取对应的【专属工厂】
        LlmFactory factory = factoryMap.get(modelType);
 
        if (factory == null) {
            throw new IllegalArgumentException("不支持的模型类型: " + modelType);
        }
 
        // 2. 命令工厂:给我造一个客户端出来!
        // 此时 AiChatService 根本不知道造出来的是 DeepSeek 还是 GPT
        LlmClient client = factory.createClient();
 
        // 3. 使用创建出来的客户端执行业务逻辑
        client.invoke(prompt);
    }
}

抽象工厂模式

  • 工厂方法模式:它解决的是单一产品的创建。比如“造汽车”,奥迪工厂造奥迪车,宝马工厂造宝马车。
  • 抽象工厂模式:它解决的是一系列配套产品(产品族)的创建。

观察者模式( 发布-订阅模式 )

  • 业务小、单机跑 → 用 Spring Event 充当总线。
  • 业务大、微服务 → 用 Kafka/RabbitMQ 充当总线,并辅以事务一致性保障。

Spring Event

第一步:定义“事件” (Event)

这就相当于发布者大吼的那句话(携带了事件发生时的数据)。

Java

// 这是一个普通的 Java 对象,用于承载事件数据
public class UserRegisteredEvent {
    private Long userId;
    private String email;
 
    public UserRegisteredEvent(Long userId, String email) {
        this.userId = userId;
        this.email = email;
    }
    // getters...
}

第二步:改造发布者 (UserService 彻底解脱)

UserService 只需要注入 Spring 提供的超级大喇叭 ApplicationEventPublisher

Java

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
 
@Service
public class UserService {
 
    @Autowired
    private ApplicationEventPublisher eventPublisher;
 
    public void register(User user) {
        // 1. 核心逻辑:保存用户
        userDao.save(user);
 
        // 2. 核心大招:发布事件!然后就没我的事了。
        UserRegisteredEvent event = new UserRegisteredEvent(user.getId(), user.getEmail());
        eventPublisher.publishEvent(event);
 
        System.out.println("用户注册核心流程结束!");
    }
}

第三步:编写观察者 (Listeners)

各个边缘业务自己去监听这个事件,不需要 UserService 去调用它们。

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
 
// 邮件服务:监听用户注册事件
@Component
public class EmailListener {
 
    // 这个注解就是魔法!当 UserRegisteredEvent 被发布时,这个方法会被自动调用
    @EventListener
    public void onUserRegistered(UserRegisteredEvent event) {
        System.out.println("监听到用户注册,准备发送邮件至: " + event.getEmail());
    }
}
 
// 优惠券服务:同样监听该事件
@Component
public class CouponListener {
 
    @EventListener
    public void handleCouponDispatch(UserRegisteredEvent event) {
        System.out.println("监听到用户注册,准备发放新人优惠券给 UserID: " + event.getUserId());
    }
}

责任链模式

责任链模式 (Chain of Responsibility) 就是解决“一条流水线上的闯关游戏”

Java Web (Servlet) 的 Filter: 我们在实现 javax.servlet.Filter 时,方法签名 doFilter(ServletRequest request, ServletResponse response, FilterChain chain),这简直和我们下面写的代码一模一样!

第一步:定义“关卡”的标准 (Handler 接口)

// 责任链上的处理器标准
public interface OrderFilter {
    /**
     * @param request 请求上下文
     * @param chain   链条调度器(控制流程往下走)
     */
    void doFilter(OrderRequest request, OrderFilterChain chain);
}

第二步:实现具体的关卡 (Concrete Handlers)

每个关卡只负责自己那一点点事,极其内聚。

Java

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
 
// 关卡 1:参数校验 (@Order 定义了在链条中的执行顺序)
@Component
@Order(1)
public class ParamValidateFilter implements OrderFilter {
    @Override
    public void doFilter(OrderRequest request, OrderFilterChain chain) {
        if (request.getUserId() == null) {
            throw new RuntimeException("参数校验失败");
        }
        System.out.println("1. 参数校验通过!");
        // 核心:自己处理完了,通知链条继续往下走!
        chain.doFilter(request, chain);
    }
}
 
// 关卡 2:风控校验
@Component
@Order(2)
public class RiskControlFilter implements OrderFilter {
    @Override
    public void doFilter(OrderRequest request, OrderFilterChain chain) {
        System.out.println("2. 风控检查通过!");
        chain.doFilter(request, chain); // 放行给下一个
    }
}

第三步:构建“链条管理器” (Chain Manager)

我们在学 Spring 注入时讲过,@Autowired 可以注入 Map同样也可以注入 List Spring 会自动把所有实现了 OrderFilter 的类收集起来,并且按照 @Order 的数字从小到大排好序

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
 
@Component
public class OrderFilterChain {
 
    // 绝杀:Spring 自动将所有关卡按 @Order 排序注入到 List 中!
    @Autowired
    private List<OrderFilter> filters;
 
    // 记录当前执行到了第几个关卡
    private int index = 0;
 
    // 驱动链条运转的马达
    public void doFilter(OrderRequest request, OrderFilterChain chain) {
        // 如果所有关卡都走完了,执行最终的业务逻辑
        if (index == filters.size()) {
            System.out.println("所有关卡全部通过,开始执行真正的下单落库逻辑!");
            return;
        }
 
        // 获取当前关卡,并将游标 +1
        OrderFilter currentFilter = filters.get(index++);
 
        // 执行当前关卡,并把链条调度器本身传进去
        currentFilter.doFilter(request, chain);
    }
}

第四步:客户端调用

核心的 OrderService 瞬间变得极其干净。

@Service
public class OrderService {
    @Autowired
    private OrderFilterChain filterChain;
 
    public void placeOrder(OrderRequest request) {
        // 直接把请求扔给责任链,它会自动过关斩将
        filterChain.doFilter(request, filterChain);
    }
}

建造者模式

为了解决“这辆车我要红色的漆、V8 的引擎、真皮的座椅、还要加装一个全景天窗(极度复杂的参数装配)”的问题 痛点:被构造函数和 Setter 支配的恐惧

Lombok 的 @Builder (实习降维打击)

手写代码如下

public class CloudServer {
    // 1. 所有属性全部声明为 final,保证创建后绝对不可变!没有 setter!
    private final String os;
    private final int cpu;
    private final int memory;
    private final boolean hasPublicIp;
 
    // 2. 私有化构造函数,只能让 Builder 来调用
    private CloudServer(Builder builder) {
        this.os = builder.os;
        this.cpu = builder.cpu;
        this.memory = builder.memory;
        this.hasPublicIp = builder.hasPublicIp;
    }
 
    // 3. 静态内部类 Builder
    public static class Builder {
        // 必填参数(可以在 Builder 构造时传入)
        private String os;
        private int cpu;
        private int memory;
        // 选填参数(给个默认值)
        private boolean hasPublicIp = false;
 
        public Builder(String os, int cpu, int memory) {
            this.os = os;
            this.cpu = cpu;
            this.memory = memory;
        }
 
        // 核心:每个装配方法都返回 Builder 自己 (this),实现链式调用
        public Builder enablePublicIp(boolean enable) {
            this.hasPublicIp = enable;
            return this;
        }
 
        // 终极武器:校验参数并生成最终对象
        public CloudServer build() {
            if (this.memory < 2) {
                throw new IllegalArgumentException("服务器内存至少需要 2GB");
            }
            return new CloudServer(this);
        }
    }
}