设计模式
常见设计模式:单例、策略、工厂、观察者、责任链、建造者等,含设计原则。
设计模式
开闭原则:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
本质是降低修改老代码带来的风险,保护系统的稳定性。
单例模式
所有单例的实现都包含以下两个相同的步骤:
- 将默认构造函数设为私有,防止其他对象使用单例类的
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的话,这里可能会返回一个半成品
}
}- 为什么要用static
- 静态方法内部只能访问静态变量,getSingleton需要添加static,不然没法获取对象
- 为什么要双重检查
防止多个线程在synchronized排队
- 为什么
volatile是“灵魂”?instance = new LazySingletonDCL();这行代码在 JVM 中不是原子操作- 为
instance分配内存空间。 - 初始化
instance对象(调用构造函数)。 - 将
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);
}
}
}