设计模式总结

设计原则

SRP(单一职责原则 - Single Responsibility Principle)

  • 核心:一个类应该只有一个引起它变化的原因。

  • 深度理解 :不是指一个类只能有一个方法,而是指功能的高度聚焦 。比如在 RocketMQ 中,MappedFile 只负责文件映射,而 ReputMessageService 只负责分发索引。

OCP(开闭原则 - Open/Closed Principle)

  • 核心:对扩展开放,对修改关闭。

  • 深度理解 :当需求变化时,你应该通过增加新代码来改变行为,而不是修改原有代码。接口和抽象类是实现这一原则的利器。

LSP(里氏替换原则 - Liskov Substitution Principle)

  • 核心:子类对象应该能够替换其父类对象,且程序逻辑不变。

  • 深度理解:子类可以增强父类的功能,但不能改变父类原有的行为(不要重写父类的非抽象方法)。

ISP(接口隔离原则 - Interface Segregation Principle)

  • 核心:客户端不应依赖它不需要的接口。

  • 深度理解:与其设计一个"大而全"的接口,不如将其拆分为多个"小而精"的专用接口。避免接口污染。

DIP(依赖倒置原则 - Dependency Inversion Principle)

  • 核心:高层模块不应依赖低层模块,二者都应依赖抽象;抽象不应依赖细节,细节应依赖抽象。

  • 深度理解 :这就是 IoC(控制反转) 的灵魂。比如你的业务逻辑不应直接 new 一个 RocketMQProducer,而应依赖一个 MessageSender 接口。

迪米特法则(Law of Demeter / 最少知识原则)

  • 一个对象应当对其他对象有尽可能少的了解。不要和陌生人说话,只和你的直接朋友交流。

全部设计模式一览

一、 创建型模式 (Creational Patterns)

核心: 处理对象的创建机制,将系统的"创建"与"使用"解耦。

模式 核心意图 资深工程师点评
单例 (Singleton) 保证一个类仅有一个实例,并提供全局访问点。 Spring 的 Bean 默认就是单例。注意双重检查锁(DCL)和枚举实现。
工厂方法 (Factory Method) 定义创建对象的接口,让子类决定实例化哪一个类。 典型的"解耦"。比如日志记录器,由子类决定存磁盘还是存数据库。
抽象工厂 (Abstract Factory) 提供一个创建一系列相关或相互依赖对象的接口。 用于"产品族"。比如一套 UI,可以同时生产 Mac 风格的按钮和文本框。
建造者 (Builder) 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 解决构造函数参数过多的痛苦。Lombok 的 @Builder 是标配。
原型 (Prototype) 通过拷贝现有的实例来创建新的实例。 当创建对象成本很高时(如查数据库),直接 clone()

二、 结构型模式 (Structural Patterns)

核心: 处理类或对象的组合,像搭积木一样优化系统结构。

模式 核心意图 资深工程师点评
适配器 (Adapter) 将一个类的接口转换成客户希望的另外一个接口。 解决"接口不兼容"的救火队员。类似手机转接头。
桥接 (Bridge) 将抽象部分与它的实现部分分离,使它们都可以独立地变化。 解决"多维度扩展"引起类爆炸。比如:不同颜色 + 不同形状。
组合 (Composite) 将对象组合成树形结构以表示"部分-整体"的层次结构。 处理树形数据。如:文件系统、公司组织架构。
装饰器 (Decorator) 动态地给一个对象添加一些额外的职责。 比继承更灵活。Java IO 流(BufferedInputStream)是经典案例。
外观 (Facade) 为子系统中的一组接口提供一个一致的界面。 封装复杂性。就像微服务里的 API 网关,一站式解决。
享元 (Flyweight) 运用共享技术有效地支持大量细粒度的对象。 性能优化神器。Java 的 String 常量池、整型缓存。
代理 (Proxy) 为其他对象提供一种代理以控制对这个对象的访问。 Spring AOP 的基石。用于日志、权限控制、事务管理。

三、 行为型模式 (Behavioral Patterns)

核心: 处理对象之间的通信、职责分配和算法的运行时切换。

模式 核心意图 资深工程师点评
策略 (Strategy) 定义一系列算法,把它们一个个封装起来,并使它们可相互替换。 消除 if-else 的首选。RocketMQ 的负载均衡算法。
观察者 (Observer) 定义对象间的一种一对多的依赖关系。 事件驱动架构的核心。Spring Event、MQ 消费本质都是它。
模板方法 (Template Method) 定义算法骨架,将某些步骤延迟到子类中实现。 框架的基石。Spring 的 JdbcTemplateRedisTemplate
责任链 (Chain of Responsibility) 使多个对象都有机会处理请求,直到有对象处理为止。 拦截器、过滤器。如:Servlet Filter、Dubbo Filter。
状态 (State) 允许一个对象在其内部状态改变时改变它的行为。 复杂的订单状态流转(待支付 -> 待发货 -> 待评价)。
迭代器 (Iterator) 提供一种方法顺序访问一个聚合对象中各个元素。 Java Collection 框架的标准。
中介者 (Mediator) 用一个中介对象来封装一系列的对象交互。 将"网状依赖"变为"星状依赖"。MVC 里的 Controller 就是中介。
命令 (Command) 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化。 实现"撤回(Undo)"操作、任务队列化。
备忘录 (Memento) 在不破坏封装性的前提下,捕获一个对象的内部状态并在外部保存。 游戏存档、代码编辑器里的撤销功能。
解释器 (Interpreter) 给定一个语言,定义它的文法的一种表示。 自定义 DSL 语言。如:Spring EL 表达式解析。
访问者 (Visitor) 表示一个作用于某对象结构中的各元素的操作。 最复杂的模式。用于数据结构和操作分离,常见于编译器开发。

常见的设计模式

单例模式

单例模式(Singleton Pattern)是设计模式中最简单但也最容易被误用的模式之一。在分布式系统(如 RocketMQ)或框架(如 Spring)的底层开发中,单例模式随处可见,用于管理全局状态、配置信息或高开销的资源池。

定义: 确保一个类只有一个实例,并提供一个全局访问点来访问该实例。

核心意图:

  • 控制资源的使用:通过线程池、数据库连接池等限制资源消耗。

  • 确保逻辑正确性:某些业务逻辑要求全局唯一性(如序号生成器、配置管理)。

  • 减少开销:避免频繁创建和销毁重量级对象。

单例模式的六种实现方式

单例模式的实现演进过程,本质上是在 延迟加载(Lazy Loading)线程安全(Thread Safety) 之间做权衡。

① 饿汉式(Eager Initialization)

类加载时就完成了初始化。

  • 优点:实现简单,类加载机制保证了绝对的线程安全。

  • 缺点:不支持延迟加载,如果对象很大且一直未被使用,会浪费内存。

② 懒汉式(Lazy Initialization - 线程不安全)

第一次调用 getInstance() 时才创建实例。

  • 评价 :在多线程环境下会出现多个实例,严禁在生产中使用

③ 懒汉式(线程安全 - 方法加锁)

通过 synchronized 关键字修饰获取实例的方法

  • 缺点:性能极差。每次访问都需要获取锁,而实际上只有第一次创建时才需要同步。

④ 双重检查锁(Double-Checked Locking, DCL)

兼顾了性能与线程安全。

java 复制代码
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;
    }
}

volatile 的作用是防止 new Singleton() 时发生指令重排。如果不加,可能出现"对象还未初始化完成就被其他线程读取"的情况。

第一次检查(Check 1)为了提高效率,避免不必要的同步 。在单例模式中,synchronized 锁的开销是比较大的。实际上,我们只需要在第一次创建对象时 进行同步。一旦对象创建完成,后续的所有调用都只需要直接返回 instance 即可。

如果少了会怎么样?

  • 后果 :程序依然是线程安全的,但性能会极度退化

  • 现象 :每一次调用 getInstance() 都会进入锁竞争。在高并发场景下(如 RocketMQ 消息处理逻辑中),大量线程会在这里阻塞排队,导致 CPU 的上下文切换开销飙升,系统吞吐量大幅下降。

第二次检查(Check 2):为了防止多次实例化,确保单例的唯一性。

  1. 假设线程 A 和线程 B 同时执行。

  2. 它们都发现 instance == null ,于是都通过了 Check 1

  3. 线程 A 抢先获得了锁 ,进入 synchronized 块,创建了对象并赋值给 instance,然后释放锁。

  4. 此时线程 B 获得锁,进入 synchronized 块。

  5. 关键点 :如果没有 Check 2 ,线程 B 会由于已经通过了 Check 1,而直接再次执行 new Singleton()

如果少了会怎么样?

  • 后果单例模式失效,内存中会出现多个实例。

  • 现象 :破坏了全局唯一性的业务逻辑。如果这个单例是管理数据库连接池或 RocketMQ 的客户端管理类(MQClientManager),重复创建会导致连接泄露或状态管理混乱。

为什么必须加 volatile

即使有了两次检查,如果变量不加 volatile 关键字,这段代码在多核 CPU 下依然是不安全的。

原因:指令重排序(Instruction Reordering) 执行 instance = new Singleton(); 这一行,在 JVM 层面其实分成了三步:

  1. 分配内存空间

  2. 调用构造函数,初始化对象

  3. instance 引用指向分配的内存地址

由于编译器或 CPU 的优化,第 2 步和第 3 步可能发生重排序

  • 如果执行顺序变成了 1 --> 3 --> 2

  • 当线程 A 执行完第 3 步(赋值)但还没执行第 2 步(初始化)时,线程 B 恰好执行到 Check 1

  • 线程 B 发现 instance != null(因为它已经指向了内存地址),于是直接返回了。

  • 结果 :线程 B 拿到了一个尚未初始化完成 的"半成品"对象,后续调用其方法时会抛出 NullPointerException 或产生不可预知的错误。

⑤ 静态内部类(Bill Pugh Singleton)

利用 Java 类加载机制来实现延迟加载和线程安全。

  • 原理 :内部类只有在 getInstance() 被调用时才会被加载。

  • 优点:代码简洁,不涉及锁,性能高。

⑥ 枚举单例(Enum Singleton)

《Effective Java》作者 Joshua Bloch 推荐的方式。

  • 优点 :天生线程安全,且能绝对防止反射和序列化破坏单例.

Spring中的单例

设计模式里的单例是类加载器(ClassLoader)级别 的强约束,而 Spring 的单例是容器(IoC Container)级别的逻辑约束。

维度 GoF 设计模式单例 Spring Bean 单例 (Default Scope)
定义范围 在同一个 ClassLoader 下,该类仅有一个实例。 在同一个 Spring IoC 容器 中,该 Bean 定义仅有一个共享实例。
控制权 类本身 控制(通过私有构造函数和静态方法)。 Spring 容器 控制(通过 BeanDefinition 和容器管理)。
唯一性 绝对唯一(除非使用反射或多个类加载器)。 相对唯一。同一个类在不同容器中可以有多个实例。

A. 容器限制 vs 类加载器限制

  • GoF 单例只要类被加载,它的实例在整个类加载器范围内就是唯一的。

  • Spring 单例 :Spring 保证的是在同一个 ApplicationContext 中,根据某个 Bean ID 获取到的对象是同一个。如果你在同一个 JVM 里启动了两个 Spring 容器,那么同一个类的 Bean 在这两个容器中会各有一个实例。

B. 实例化机制

  • GoF 单例:通常通过私有构造函数防止外部创建,由类内部自己负责实例化(如 DCL 或静态内部类)。

  • Spring 单例 :Bean 的构造函数通常是公开的 。Spring 容器利用反射机制创建对象,并将其存入一个内部的 Map<String, Object>(一级缓存)中。

C. 一个类是否可以有多个"单例"?

  • GoF 单例:不可能(除非打破封装)。

  • Spring 单例可以 。你可以在 Spring 配置文件中为一个类定义多个不同的 Bean ID(例如 userService1userService2)。虽然它们是同一个类,但 Spring 会为每个配置创建一个单例实例。

为什么Spring不直接使用设计模式的单例呢?

  1. 非侵入性:Spring 不需要你修改业务代码(不需要私有构造函数或静态方法),它能把任何普通的 POJO 变成单例管理。

  2. 灵活性:解耦了"实例的个数"和"类本身"。你可以根据配置决定它是单例(Singleton)还是原型(Prototype),而不需要改动代码。

  3. 易于测试:GoF 单例很难进行单元测试(Mock 困难),而 Spring 管理的单例 Bean 可以轻松地通过构造函数或 Setter 注入 Mock 对象。

分布式环境中保证单例

传统的 Java 单例模式(GoF 单例)绝对无法保证全局唯一。 只能保证单进程内唯一 ,无法实现跨机器唯一

单例模式的唯一性仅存在于同一个类加载器(ClassLoader)和同一个 JVM 实例中。在分布式系统中,应用运行在多个节点(多个 JVM)上,每个节点都会拥有自己独立的一个"单例"实例。

  • 隔离的内存空间:每个 JVM 进程都有自己独立的堆内存。Node A 的单例对象存储在 A 的堆中,Node B 无法感知其存在。

  • 状态不同步:如果你在 Node A 修改了单例的某个成员变量,Node B 里的实例完全不会受影响。

1. 行为单例:分布式锁 (Distributed Lock)

如果你的需求是"在整个集群中,同一时刻只有一个节点能执行某个动作",这是最常用的方案。

  • 实现机制:在执行逻辑前,先向公共组件申请锁。

  • 常用工具

    • Redis :利用 SETNX 指令或 Redlock 算法。

    • Zookeeper:利用临时顺序节点(Ephemeral Sequential Nodes)实现公平锁。

  • 应用场景:定时任务触发、全局序号生成、资源初始化。

2. 实例单例:选主机制 (Leader Election)

如果你的需求是"集群中只有一个特定的节点负责某项管理任务",通常采用选主方案。

  • 实现机制:所有节点在启动时向协调中心注册。通过某种共识算法(如 Raft、Paxos)或简单的"抢占注册"选出一个 Leader 节点。

  • 工作流程

    • 只有 Leader 节点会实例化并运行该单例组件。

    • 非 Leader 节点处于 Standby(热备)状态。

    • 一旦 Leader 节点宕机,协调中心会感知并触发重新选主。

  • 应用场景:Kafka 的 Controller、RocketMQ 5.x 的主从自动切换。

3. 逻辑单例:消息队列分区绑定 (Partitioning)

对于处理特定数据的逻辑,可以利用 MQ 的特性在逻辑上实现单例处理。

  • 实现机制 :利用 RocketMQ 的 MessageQueue

  • 核心逻辑

    • 一个 Topic 被拆分为多个 MessageQueue。

    • 在集群消费模式下,RocketMQ 保证一个 MessageQueue 同一时间只能被同一个 ConsumerGroup 里的一个消费者实例消费。

    • 通过顺序消息选择器,将特定 ID 的消息路由到固定的 Queue 中。

  • 效果:虽然有多个 Consumer 实例,但对于某一份特定数据,始终只有唯一的一个实例在处理它。

4. 状态单例:状态外迁 (Centralized State)

如果你的单例是为了维护一份"全局唯一的状态数据",则应该放弃"对象单例",转而使用"数据中心化"。

  • 实现机制:将单例对象中的成员变量移除,转而存储在分布式缓存(Redis)或数据库(DB)中。

  • 读写流程

    • 每次需要状态时,从远程存储拉取。

    • 修改状态时,利用数据库的乐观锁 或 Redis 的原子指令

  • 优势:彻底解决了内存同步问题,且天然支持节点扩容。

使用场景:线程池(Thread Pool)

我们不建议使用 Executors 工具类,因为它隐藏了底层细节。推荐直接使用 ThreadPoolExecutor 进行手动定制。

java 复制代码
import java.util.concurrent.*;

public class GlobalThreadPool {

    // 1. 线程池核心参数定义
    private static final int CORE_POOL_SIZE = 8;
    private static final int MAX_POOL_SIZE = 16;
    private static final long KEEP_ALIVE_TIME = 60L; // 秒
    private static final int QUEUE_CAPACITY = 1000;

    private ThreadPoolExecutor executor;

    // 2. 私有构造函数:配置线程池
    private GlobalThreadPool() {
        executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(QUEUE_CAPACITY),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread t = new Thread(r);
                        t.setName("global-worker-" + t.getId());
                        return t;
                    }
                },
                new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:由调用者运行
        );
    }

    // 3. 静态内部类:实现单例逻辑
    private static class Holder {
        private static final GlobalThreadPool INSTANCE = new GlobalThreadPool();
    }

    // 4. 全局访问点
    public static GlobalThreadPool getInstance() {
        return Holder.INSTANCE;
    }

    // 5. 提交任务的方法
    public void execute(Runnable task) {
        executor.execute(task);
    }

    // 6. 提交有返回值的任务
    public <T> Future<T> submit(Callable<T> task) {
        return executor.submit(task);
    }
}

在线程池这个场景下,静态内部类比双重检查锁(DCL)更有优势:

  • 绝对线程安全 :利用 JVM 加载类的原子性保证,无需手动加锁(synchronized)。

  • 懒加载(Lazy Loading)Holder 类只有在第一次调用 getInstance() 时才会被加载。如果应用启动后一直没用到线程池,就不会占用内存

  • 高性能:访问时不需要经过同步锁,性能开销极低。

优雅关闭

单例线程池的生命周期与 JVM 相同,但在系统停机(如热部署或重启)时,如果处理不当,队列中未完成的任务会丢失。

方案:注册 JVM 钩子(Shutdown Hook)

在构造函数中加入以下逻辑,确保停机前尝试处理完剩余任务:

java 复制代码
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    System.out.println("正在关闭全局线程池...");
    executor.shutdown(); // 不再接受新任务
    try {
        if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
            executor.shutdownNow(); // 超过30秒强行关闭
        }
    } catch (InterruptedException e) {
        executor.shutdownNow();
    }
}));

策略模式

策略模式定义了一系列算法,并将每一个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。

核心意图

  • 消除冗长的 if-elseswitch 语句

  • 解耦:将算法的选择逻辑与具体的执行逻辑分离。

  • 符合开闭原则(OCP):增加新逻辑时,不需要修改原有代码,只需增加一个新的策略类。

策略模式通常由三个角色组成:

  1. Strategy(策略接口):定义了所有支持算法的公共接口。

  2. ConcreteStrategy(具体策略类):实现了策略接口中的具体算法。

  3. Context(上下文):持有一个策略对象的引用,负责调用具体的算法。

传统 Java 实现

假设我们有一个支付系统,支持微信、支付宝。

java 复制代码
// 1. 策略接口
public interface PaymentStrategy {
    void pay(int amount);
}

// 2. 具体策略:微信支付
public class WeChatPay implements PaymentStrategy {
    public void pay(int amount) { System.out.println("使用微信支付:" + amount); }
}

// 3. 具体策略:支付宝
public class AliPay implements PaymentStrategy {
    public void pay(int amount) { System.out.println("使用支付宝支付:" + amount); }
}

// 4. 上下文
public class PaymentContext {
    private PaymentStrategy strategy;
    public PaymentContext(PaymentStrategy strategy) { this.strategy = strategy; }
    public void executePay(int amount) { strategy.pay(amount); }
}

B. Spring 环境下的实现(推荐)

在真实的 Spring 项目中,我们通常结合 工厂模式 + Map 注入,实现彻底的动态分发。

java 复制代码
@Service
public class PaymentService {
    // Spring 会自动将所有 PaymentStrategy 的实现类注入到这个 Map 中
    // Key 为 Bean 的名称,Value 为实现类
    @Autowired
    private Map<String, PaymentStrategy> strategyMap;

    public void processPayment(String type, int amount) {
        PaymentStrategy strategy = strategyMap.get(type);
        if (strategy == null) throw new RuntimeException("不支持的支付方式");
        strategy.pay(amount);
    }
}

优点:

  1. 完美避开魔数与多重判断:代码变得清爽、整洁。

  2. 扩展性极强 :新增策略只需要实现接口并加一个 @Component 注解,原有逻辑完全不动。

  3. 便于单元测试:每个策略都是独立的类,可以单独测试其逻辑。

缺点:

  1. 类膨胀:策略一多,类也会成倍增长。

  2. 调用方需要了解策略:使用者必须知道有哪些策略可用,才能进行选择(通常配合工厂模式解决)。

使用场景:布隆过滤器

在布隆过滤器的语境下,策略模式主要解决**哈希函数的"多样性"和"可替换性"**问题。

  • Strategy(策略接口)HashStrategy,定义哈希计算的标准。

  • ConcreteStrategy(具体策略) :如 MurmurHashStrategyGuavaHashStrategyFNVHashStrategy 等。

  • Context(上下文/主类)BloomFilterContext,负责管理位数组并调用策略。

第一步:定义哈希策略接口

由于布隆过滤器通常需要多个哈希函数来降低冲突,我们可以让策略返回一个哈希值的数组,或者根据索引生成不同的哈希值。

java 复制代码
public interface HashStrategy {
    /**
     * 根据输入数据生成多个哈希索引
     * @param data 原始数据
     * @param k 哈希函数的个数
     * @param m 位数组的长度
     * @return 位数组中的索引位置
     */
    int[] computeIndices(String data, int k, int m);
}

第二步:实现具体策略

对于追求高性能的场景,MurmurHash3 是业界的标准选择(如 Guava 和 Redis 都在用)。

java 复制代码
// MurmurHash3 实现策略
public class Murmur3HashStrategy implements HashStrategy {
    @Override
    public int[] computeIndices(String data, int k, int m) {
        int[] indices = new int[k];
        // 技巧:利用 MurmurHash 生成两个 64 位哈希值,模拟出 K 个哈希函数
        long hash1 = MurmurHash3.hash64(data.getBytes());
        long hash2 = hash1 >>> 32;
        
        for (int i = 0; i < k; i++) {
            long combinedHash = hash1 + (i * hash2);
            // 确保结果为正数并取模
            indices[i] = (int) ((combinedHash & Long.MAX_VALUE) % m);
        }
        return indices;
    }
}

第三步:构建布隆过滤器上下文

这里通过组合的方式引入策略。

java 复制代码
public class BloomFilter<T> {
    private final BitSet bitSet; // 或者使用 Redis BitMap
    private final int bitSize;
    private final int numHashFunctions;
    private final HashStrategy hashStrategy;

    public BloomFilter(int expectedInsertions, double fpp, HashStrategy strategy) {
        this.bitSize = optimalNumOfBits(expectedInsertions, fpp);
        this.numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, bitSize);
        this.bitSet = new BitSet(bitSize);
        this.hashStrategy = strategy;
    }

    public void put(String data) {
        int[] indices = hashStrategy.computeIndices(data, numHashFunctions, bitSize);
        for (int index : indices) {
            bitSet.set(index);
        }
    }

    public boolean mightContain(String data) {
        int[] indices = hashStrategy.computeIndices(data, numHashFunctions, bitSize);
        for (int index : indices) {
            if (!bitSet.get(index)) return false;
        }
        return true;
    }
}

为什么在这个场景用策略模式?(方案优势)

  1. 哈希碰撞的动态权衡 :在测试阶段,可以轻松通过切换 Murmur3CityHashSHA-256 来观察不同哈希算法对误判率(FPP)的影响,而不需要改动过滤器主体逻辑。

  2. 平滑迁移 :如果后期发现现有的哈希策略在特定数据分布下表现不佳,只需新增一个策略类即可,符合开闭原则

业务场景:物流运输方式选择

在物流领域中,不同的物流运输方式有着不同的费用和时效性。为了方便客户选择最合适的物流方式进行运输,我们需要实现一个物流系统,可以根据客户需求选择最优的物流方式进行运输。(海陆空三种形式)。

在没有使用策略模式之前,我们可能会用到很多if-else语句判断来实现三种物流运输方式的选择:

java 复制代码
public class LogisticsSystem {
    public static final int TRANSPORT_MODE_AIR = 1;
    public static final int TRANSPORT_MODE_LAND = 2;
    public static final int TRANSPORT_MODE_SEA = 3;

    public double calculateFee(int transportMode, double weight) {
        double fee = 0.0;

        if (transportMode == TRANSPORT_MODE_AIR) {
            fee = weight * 5.0;
        } else if (transportMode == TRANSPORT_MODE_LAND) {
            fee = weight * 2.0;
        } else if (transportMode == TRANSPORT_MODE_SEA) {
            fee = weight * 1.0;
        }

        return fee;
    }
}

主要问题在于,使用if-else分支语句来判断不同的运输方式,这样的代码实现方式几个缺点:

  1. 代码可读性差:随着运输方式的增多,代码的分支结构会变得越来越复杂,不利于代码的可读性和可维护性。

  2. 扩展性差:当需要增加新的运输方式时,需要修改原有的代码,并且可能会影响到原有代码的逻辑,不利于系统的扩展性和维护性。

  3. 代码重复:在if-else分支语句中,存在大量的代码重复,不利于代码的复用和维护。

使用设计模式优化后代码

策略模式+工厂模式

首先,定义一个抽象的运输策略接口TransportStrategy,包含一个calculateFee方法:

java 复制代码
public interface TransportStrategy {
    double calculateFee(double weight);
}

然后,定义三个具体的策略类:AirTransportStrategyLandTransportStrategySeaTransportStrategy,分别实现TransportStrategy接口:

java 复制代码
public class AirTransportStrategy implements TransportStrategy {
    @Override
    public double calculateFee(double weight) {
        return weight * 5.0;
    }
}

public class LandTransportStrategy implements TransportStrategy {
    @Override
    public double calculateFee(double weight) {
        return weight * 2.0;
    }
}

public class SeaTransportStrategy implements TransportStrategy {
    @Override
    public double calculateFee(double weight) {
        return weight * 1.0;
    }
}

接下来,定义一个运输策略工厂 TransportStrategyFactory,用于根据不同的运输方式创建相应的策略对象:

java 复制代码
public class TransportStrategyFactory {
    public static TransportStrategy createTransportStrategy(int transportMode) {
        switch (transportMode) {
            case LogisticsSystem.TRANSPORT_MODE_AIR:
                return new AirTransportStrategy();
            case LogisticsSystem.TRANSPORT_MODE_LAND:
                return new LandTransportStrategy();
            case LogisticsSystem.TRANSPORT_MODE_SEA:
                return new SeaTransportStrategy();
            default:
                throw new IllegalArgumentException("Invalid transport mode");
        }
    }
}

最后,调用端逻辑:

java 复制代码
public class LogisticsSystem {
    public static final int TRANSPORT_MODE_AIR = 1;
    public static final int TRANSPORT_MODE_LAND = 2;
    public static final int TRANSPORT_MODE_SEA = 3;

    public double calculateFee(int transportMode, double weight) {
        TransportStrategy strategy = TransportStrategyFactory.createTransportStrategy(transportMode);
        return strategy.calculateFee(weight);
    }
}

这样,我们就成功地使用策略设计模式和工厂模式优化了原有的代码。使用这种方式,我们可以很方便地添加、修改、删除不同的运输策略,而不需要修改原有的代码。

模板模式

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

核心意图

  • 封装不变部分,扩展可变部分把通用的逻辑抽离出来,把不确定的逻辑留给子类。

  • 提取公共代码:减少重复代码,提高代码复用性。

  • 行为控制:由父类固定执行流程,子类只需按部就班填充细节。

核心原则:好莱坞原则 (Hollywood Principle) "Don't call us, we'll call you."(别打电话给我们,有需要我们会打给你)。在模板模式中,是父类调用子类的实现,而不是子类调用父类。

模板方法模式主要包含两个角色:

  1. 抽象类 (Abstract Class)

    • 模板方法 (Template Method) :定义算法骨架,通常声明为 final,防止子类修改流程。

    • 基本方法 (Primitive Methods):由子类实现。

    • 钩子方法 (Hook Methods):父类提供默认实现或空实现,子类决定是否重写以改变算法路径。

  2. 具体类 (Concrete Class):实现父类定义的抽象方法。

模板方法 vs 策略模式

这两者最容易混淆,但侧重点完全不同:

维度 模板方法模式 (Template) 策略模式 (Strategy)
实现逻辑 基于 继承(类级别)。 基于 组合(对象级别)。
控制力 父类控制流程,子类填充空隙。 客户端选择具体策略,没有固定流程。
粒度 针对算法中的 特定步骤 针对 整个算法 的替换。
关系强度 强耦合(父子类)。 弱耦合(上下文与接口)。

使用场景:多格式数据导出报表

在实际工作中,最能体现模板模式 价值的场景通常是那些"主干流程高度一致,但个别步骤差异化 "的业务,比如:支付对账、数据导入/导出、审批流控制

假设公司需要支持将数据库中的交易数据导出为 CSVExcel 两种格式。在没有使用模板模式时,开发者往往会为了快速上线,直接写两个独立的处理器。

java 复制代码
// CSV导出类
public class CsvExporter {
    public void export() {
        System.out.println("1. 建立数据库连接,查询交易数据...");
        System.out.println("2. [差异] 将数据格式化为 CSV 逗号分隔符形式...");
        System.out.println("3. 开启文件输出流,写入磁盘...");
        System.out.println("4. 记录导出耗时与操作日志...");
    }
}

// Excel导出类
public class ExcelExporter {
    public void export() {
        System.out.println("1. 建立数据库连接,查询交易数据...");
        System.out.println("2. [差异] 调用 Apache POI 库创建 Excel 单元格并设置样式...");
        System.out.println("3. 开启文件输出流,写入磁盘...");
        System.out.println("4. 记录导出耗时与操作日志...");
    }
}

痛点分析

  • 代码冗余:步骤 1、3、4 完全一样。如果以后要修改查询逻辑或日志格式,你得在两个类里同步修改,极易漏掉。

  • 难以维护:随着导出的格式增多(如增加 PDF、JSON),你会发现系统里充满了这种逻辑高度相似的"孪生类"。

  • 缺乏约束:新来的同事可能会漏掉"记录日志"这一步,导致合规性问题。

我们提取出一个抽象父类,规定好导出的"标准动作"。

java 复制代码
// 1. 抽象骨架类
public abstract class AbstractDataExporter {

    // 模板方法:定义为 final,禁止子类修改算法骨架
    public final void exportReport() {
        queryData();       // 不变步
        formatData();      // 变化步(抽象)
        outputToFile();    // 不变步
        if (shouldCompress()) { // 钩子方法
            compressFile();
        }
        logResult();       // 不变步
    }

    private void queryData() {
        System.out.println("通用:执行 SQL 查询,获取原始数据集。");
    }

    protected abstract void formatData(); // 留给子类实现具体的格式化逻辑

    private void outputToFile() {
        System.out.println("通用:将格式化后的内容写入本地临时文件。");
    }

    protected boolean shouldCompress() { return false; } // 默认不压缩

    private void compressFile() { System.out.println("通用:正在对文件进行 ZIP 压缩..."); }

    private void logResult() {
        System.out.println("通用:记录导出成功日志。");
    }
}

具体的实现类

java 复制代码
// CSV 实现
public class CsvExporter extends AbstractDataExporter {
    @Override
    protected void formatData() {
        System.out.println("实现:将数据转换为 CSV 文本流。");
    }
}

// Excel 实现
public class ExcelExporter extends AbstractDataExporter {
    @Override
    protected void formatData() {
        System.out.println("实现:使用 POI 构建 Excel 文档并美化单元格。");
    }

    @Override
    protected boolean shouldCompress() { return true; } // 重写钩子:Excel 文件大,需要压缩
}

建造者模式

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

核心意图

  • 解决"构造函数地狱":当对象参数过多,且很多是可选参数时,避免重载过多的构造函数。

  • 分步构建:允许你按照特定的顺序、一步步地设置对象的属性。

  • 解耦:将复杂对象的创建过程封装起来,调用者不需要知道内部的装配细节。

  • 保证不可变性:通常建造者模式生成的对象是不可变的(没有 Setter),这在并发编程中非常重要。

实现方式:静态内部类

在 Java 生态中,最流行的是通过静态内部类实现的链式调用(Fluent API)。

java 复制代码
public class Computer {
    // 所有属性均为 final,保证不可变性
    private final String cpu;
    private final String ram;
    private final String gpu; // 可选
    private final String storage;

    // 私有构造函数,只允许 Builder 调用
    private Computer(Builder builder) {
        this.cpu = builder.cpu;
        this.ram = builder.ram;
        this.gpu = builder.gpu;
        this.storage = builder.storage;
    }

    // 静态内部类 Builder
    public static class Builder {
        private String cpu;
        private String ram;
        private String gpu;
        private String storage;

        public Builder cpu(String cpu) {
            this.cpu = cpu;
            return this; // 返回当前对象,实现链式调用
        }

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

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

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

        // 最终构建方法
        public Computer build() {
            // 可以在这里进行参数校验,比如 cpu 是否为空
            return new Computer(this);
        }
    }
}

客户端调用:

java 复制代码
Computer myPc = new Computer.Builder()
                    .cpu("Intel i9")
                    .ram("32GB")
                    .storage("2TB SSD") // gpu 是可选的,不传即可
                    .build();

在实际工作中,我们很少手写上述繁琐的代码,而是直接使用 Lombok 注解:

java 复制代码
@Builder
@Getter
public class User {
    private final String name;
    private final int age;
    private final String email;
}

// 使用:User.builder().name("Gemini").age(1).build();

使用场景:个人信息查询

实习在某社交平台

  • 初期阶段(MVP期) :用户系统只需要处理注册登录。表结构很简单,一个 t_user 表搞定,接口 getUserInfo 返回 5-10 个字段。

  • 成长阶段(业务爆发)

    • 财富系统接入:多了钻石、金币、余额。

    • 荣誉系统接入:多了等级、荣誉值、勋章。

    • 社交系统接入:多了礼物记录、称号、粉丝数。

  • 现状 :原本的 User 实体从 10 个字段膨胀到了 80 个甚至更多。

痛点一:查询性能的"木桶效应"

接口内部是串行查询的:

  1. 查 MySQL 获取基本信息(50ms)。

  2. 查 Redis 获取实时钻石数量(10ms)。

  3. 调用荣誉系统 RPC 接口获取称号(100ms)。

  4. 调用礼物系统获取礼物清单(150ms)。 结果:用户只想看个用户名,接口却因为加载"礼物列表"慢,导致整个页面转圈 300ms+。

痛点二:数据库与缓存的压力

  • 大表难题t_user 表字段太多,修改任何一个业务都要动这张表,DBA 压力大。

  • 缓存穿透与粒度问题:一个大 JSON 存 Redis,只要用户换个"称号",整个用户信息的缓存都要失效。

痛点三:代码维护灾难(上帝类)

UserDTO 成了"上帝类",任何业务线都在往里塞东西。改 A 业务的字段,不小心就把 B 业务的逻辑弄挂了。

核心架构设计:按需聚合器

该方案的核心逻辑是:将枚举值映射为对应的异步查询任务,利用并行执行缩短 RT(响应时间)

第一步:定义字段枚举

每个枚举值对应一个业务领域。

java 复制代码
public enum UserProfileField {
    BASE,    // 基本信息(必查)
    ASSET,   // 资产(钻石、金币)
    HONOR,   // 荣誉(称号、等级)
    GIFT;    // 礼物(收到的数量)
}

第二步:聚合逻辑实现(核心)

利用 CompletableFutureBuilder 模式,将散落在各个微服务的数据拼装起来。

java 复制代码
@Service
public class UserInfoAggregator {

    @Autowired
    private BaseService baseService;
    @Autowired
    private AssetService assetService;
    @Autowired
    private HonorService honorService;

    public UserFullDTO getUserInfo(Long userId, List<UserProfileField> fields) {
        // 1. 启动必查任务:BaseProfile
        CompletableFuture<BaseProfile> baseFuture = CompletableFuture.supplyAsync(
                () -> baseService.queryBase(userId));

        // 2. 根据枚举动态启动可选任务
        CompletableFuture<AssetProfile> assetFuture = null;
        if (fields.contains(UserProfileField.ASSET)) {
            assetFuture = CompletableFuture.supplyAsync(() -> assetService.queryAsset(userId));
        }

        CompletableFuture<HonorProfile> honorFuture = null;
        if (fields.contains(UserProfileField.HONOR)) {
            honorFuture = CompletableFuture.supplyAsync(() -> honorService.queryHonor(userId));
        }

        // 3. 等待所有任务完成(注意:这里可以设置统一超时时间)
        CompletableFuture.allOf(
                baseFuture,
                assetFuture != null ? assetFuture : CompletableFuture.completedFuture(null),
                honorFuture != null ? honorFuture : CompletableFuture.completedFuture(null)
        ).join();

        // 4. 使用 Builder 模式优雅组装结果
        return UserFullDTO.builder()
                .base(baseFuture.join())
                .asset(assetFuture != null ? assetFuture.join() : null)
                .honor(honorFuture != null ? honorFuture.join() : null)
                .build();
    }
}

① 异常降级(Fail-safe)

场景 :如果 HonorProfile(荣誉服务)挂了,查询接口应该报错吗? 优化:不应该。荣誉信息是非核心业务。

  • 实现 :在 CompletableFuture 中使用 .exceptionally(ex -> null)。即使荣誉服务异常,接口依然能返回基础信息和资产,只是荣誉字段为空。

② 自定义线程池隔离

场景 :如果所有的查询都共用默认的 ForkJoinPool,一旦某个下游服务变慢,会把整个系统的线程池占满。 优化 :为你这个聚合业务定义一个独立的单例线程池

  • 实现supplyAsync(task, myCustomExecutor)。这样即便用户信息查询流量暴增,也不会影响到核心的登录或支付逻辑。

③ 性能监控的"木桶效应"

场景 :如何知道哪个微服务拖慢了整个聚合接口? 优化:在每个异步任务前后打点埋点。

  • 实现 :记录每个子任务的耗时,通过监控发现 RT(响应时间)的瓶颈。你会发现整个接口的 RT 总是等于 Max(各子服务耗时),这就是典型的并行聚合特征。

状态模式

允许一个对象在其内部状态改变时改变它的行为。对象看起来好像修改了它的类。

核心意图

  • 行为随状态而变:同一个动作(如"点击支付"),在"待支付"状态下和"已关闭"状态下,其执行逻辑完全不同。

  • 状态逻辑解耦:将特定状态相关的逻辑分布到独立的类中,消除巨大的分支判断语句。

  • 状态转换自动化:在处理完业务逻辑后,对象可以自动切换到下一个状态。

状态模式通常包含三个核心角色:

  1. Context(环境上下文):维护一个当前状态的实例。客户端通过环境类来操作。

  2. State(抽象状态接口):定义一个接口,封装与环境类的一个特定状态相关的行为。

  3. ConcreteState(具体状态类):实现抽象状态接口,定义在对应状态下的具体行为及状态流转逻辑。

业务场景:币商押金退回

核心实体与状态枚举 (State Pattern)

我们使用枚举来实现状态机,将逻辑内聚。

java 复制代码
/**
 * 押金订单实体
 */
@Data
public class DepositOrder {
    private Long userId;
    private DepositStatus status;
    private Integer version; // 乐观锁版本号
    private LocalDateTime updateTime;
}

/**
 * 状态机枚举
 */
public enum DepositStatus {

    // 1. 未支付
    NOT_PAID {
        @Override
        public void paySuccess(DepositOrder order) {
            order.setStatus(PAID);
        }
    },

    // 2. 已支付(拥有币商权限)
    PAID {
        @Override
        public void applyRefund(DepositOrder order) {
            order.setStatus(REFUNDING);
        }
    },

    // 3. 退回中(权限冻结,7天冷静期)
    REFUNDING {
        @Override
        public void cancelRefund(DepositOrder order) {
            order.setStatus(PAID);
        }

        @Override
        public void timeoutConfirm(DepositOrder order) {
            order.setStatus(NOT_PAID);
        }
    };

    // 基础方法,默认抛出"非法操作"异常
    public void paySuccess(DepositOrder order) { throw new BizException("当前状态无法处理支付成功"); }
    public void applyRefund(DepositOrder order) { throw new BizException("当前状态无法申请退款"); }
    public void cancelRefund(DepositOrder order) { throw new BizException("当前状态无法取消退款"); }
    public void timeoutConfirm(DepositOrder order) { throw new BizException("当前状态无法处理超时退款"); }
}

2. Redis 频次锁工具类 (Anti-Shake)

使用简单的 Redis 指令实现 10s 防抖。

java 复制代码
@Component
public class RedisLockProvider {
    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final String LOCK_PREFIX = "lock:deposit:";

    public boolean tryLock(Long userId, String action, int seconds) {
        String key = LOCK_PREFIX + action + ":" + userId;
        // SET key value EX seconds NX
        Boolean success = redisTemplate.opsForValue()
                .setIfAbsent(key, "1", Duration.ofSeconds(seconds));
        return Boolean.TRUE.equals(success);
    }

    public void unlock(Long userId, String action) {
        redisTemplate.delete(LOCK_PREFIX + action + ":" + userId);
    }
}

核心业务服务 (Service Layer)

这里整合了频次锁、事务管理、状态机、以及延迟任务

java 复制代码
@Service
@Slf4j
public class DepositServiceImpl implements DepositService {

    @Autowired
    private DepositMapper depositMapper;
    @Autowired
    private RedisLockProvider lockProvider;
    @Autowired
    private RocketMQTemplate rocketMQTemplate; // 假设使用 RocketMQ

    /**
     * 申请退回押金
     */
    @Transactional(rollbackFor = Exception.class)
    public void applyRefund(Long userId) {
        // 1. 频次锁防抖 (10s)
        if (!lockProvider.tryLock(userId, "APPLY", 10)) {
            throw new BizException("操作频繁,请10秒后再试");
        }

        try {
            // 2. 加载数据(带版本号)
            DepositOrder order = depositMapper.selectByUserId(userId);
            if (order == null) throw new BizException("订单不存在");

            // 3. 执行状态转换逻辑 (PAID -> REFUNDING)
            order.getStatus().applyRefund(order);

            // 4. 乐观锁写入数据库
            int rows = depositMapper.updateWithVersion(order);
            if (rows == 0) throw new ConcurrentModificationException("数据已被修改,请重试");

            // 5. 权限处理:调用外部服务冻结币商转账能力
            // permissionService.freeze(userId);

            // 6. 发送7天延迟消息,处理自动退款
            // RocketMQ 延迟级别:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
            // 注意:真实业务中7天通常使用定时任务或专门的延迟队列插件
            sendDelayMessage(userId, 7 * 24 * 60 * 60); 

        } catch (Exception e) {
            lockProvider.unlock(userId, "APPLY"); // 失败则提前解锁
            throw e;
        }
    }

    /**
     * 取消退回申请(反悔)
     */
    @Transactional(rollbackFor = Exception.class)
    public void cancelRefund(Long userId) {
        // 同样增加 10s 防抖
        if (!lockProvider.tryLock(userId, "CANCEL", 10)) {
            throw new BizException("操作频繁,请稍后再试");
        }

        DepositOrder order = depositMapper.selectByUserId(userId);
        
        // 执行状态转换 (REFUNDING -> PAID)
        order.getStatus().cancelRefund(order);

        depositMapper.updateWithVersion(order);
        
        // 恢复权限逻辑...
    }
}

4. 延迟任务处理 (Consumer)

处理 7 天后的"自动终态"逻辑。

java 复制代码
@Component
@RocketMQMessageListener(topic = "DEPOSIT_TIMEOUT", consumerGroup = "deposit_group")
public class DepositTimeoutConsumer implements RocketMQListener<Long> {

    @Autowired
    private DepositMapper depositMapper;

    @Override
    public void onMessage(Long userId) {
        // 1. 重新查询当前最新状态
        DepositOrder order = depositMapper.selectByUserId(userId);
        
        // 2. 只有当前还是"退回中"才处理
        if (order != null && order.getStatus() == DepositStatus.REFUNDING) {
            log.info("用户 {} 7天冷静期已到,开始执行最终退款逻辑", userId);
            
            // 3. 状态转换 (REFUNDING -> NOT_PAID)
            order.getStatus().timeoutConfirm(order);
            
            // 4. 持久化并触发真实退款(原路退回)
            depositMapper.updateWithVersion(order);
            // refundProcessor.execute(order);
        } else {
            log.info("用户 {} 已手动取消退款或状态已变,跳过延迟处理", userId);
        }
    }
}

设计模式的体现

JDK

一、 创建型模式 (Creational Patterns)

1. 单例模式 (Singleton)

  • 体现: java.lang.Runtime#getRuntime()java.awt.Desktop#getDesktop()

  • 深度理解: 比如 Runtime 类,每个 Java 应用只有一个运行环境,所以它采用了饿汉式单例,确保全局唯一。

2. 建造者模式 (Builder)

  • 体现: java.lang.StringBuilder#append()java.lang.StringBufferjava.nio.ByteBuffer

  • 深度理解: 通过链式调用分步构建复杂字符串。虽然它没有严格的 build() 方法返回新对象,但其"分步装配"的理念是完全一致的。

3. 工厂方法模式 (Factory Method)

  • 体现: java.util.Calendar#getInstance()java.text.NumberFormat#getInstance()

  • 深度理解: 屏蔽了具体子类的实例化细节。比如 Calendar.getInstance() 会根据你的时区和语言环境返回具体的子类实现(如 GregorianCalendar)。

4. 原型 (Prototype)

  • 体现:java.lang.Object#clone()
  • 深度理解 :它是 Java 语言原生支持的模式。通过 Cloneable 接口和 clone() 方法,直接在内存中进行二进制流拷贝,规避了复杂的构造过程。

5. 抽象工厂 (Abstract Factory)

  • 体现:javax.xml.parsers.DocumentBuilderFactory
  • 深度理解 :这种工厂不仅能产生一个对象,还能产生一族。比如生成的 DocumentBuilder 本身又是一个能处理多种 XML 结构的工厂环境。

二、 结构型模式 (Structural Patterns)

1. 装饰器模式 (Decorator)

  • 体现: 整个 java.io

  • 典型: new BufferedInputStream(new FileInputStream(file))

  • 深度理解: 装饰器允许你在不改变原有类结构的情况下,动态地给对象增加功能(如增加缓冲区)。

2. 适配器模式 (Adapter)

  • 体现: java.util.Arrays#asList()java.io.InputStreamReader(InputStream)

  • 深度理解: InputStreamReader 是将"字节流"适配为"字符流"的桥梁。它让原本不兼容的接口可以协同工作。

3. 享元模式 (Flyweight)

  • 体现: java.lang.Integer#valueOf(int)java.lang.String 常量池。

  • 深度理解: Integer 会缓存 -128127 之间的数值对象。当你调用 valueOf 时,如果在缓存范围内,直接返回旧对象,减少内存开销。

4. 桥接模式 (Bridge)

  • 体现: JDBC (Java Database Connectivity)

  • 深度理解: 这是桥接模式最经典的工业级应用。DriverManagerDriver 接口之间就是桥接。

    • 抽象部分 :JDBC API(供开发者使用的 Connection, Statement)。

    • 实现部分:各种数据库驱动(MySQL Driver, Oracle Driver)。

    • 意义:这两者可以独立演进,你换个数据库驱动,代码逻辑完全不用动。

5. 组合模式 (Composite)

  • 体现: java.awt.Containerjava.awt.Component

  • 深度理解: 容器(Container)本身就是一个组件(Component),但它又能包含其他组件。这形成了一个树形结构,让用户可以一致地对待单个对象和组合对象。

6. 外观模式 (Facade)

  • 体现: java.util.logging 包。

  • 深度理解: 尽管日志系统内部非常复杂(包含 Handler, Filter, Formatter),但它为用户提供了一个极其简单的 Logger 类,一站式解决所有记录问题。

三、 行为型模式 (Behavioral Patterns)

这类模式关注"对象间的通信"和"职责分配"。

1. 策略模式 (Strategy)

  • 体现: java.util.Comparator#compare()

  • 深度理解:Collections.sort(list, comparator) 中,Comparator 就是一个策略。你可以根据需要传入不同的比较算法,而不需要修改排序方法本身。

2. 模板方法模式 (Template Method)

  • 体现: java.util.AbstractListjava.util.AbstractMapjava.util.concurrent.locks.AbstractQueuedSynchronizer (AQS)。

  • 深度理解: AQS 定义了同步状态的获取和释放流程(骨架),而具体的加锁逻辑由子类(如 ReentrantLock)实现。

3. 迭代器模式 (Iterator)

  • 体现: java.util.Iterator

  • 深度理解: 这是 JDK 中存在感最强的模式之一。它提供了一种统一的方法来遍历各种集合(List, Set),而无需暴露集合的内部结构。

4. 责任链模式 (Chain of Responsibility)

  • 体现: java.util.logging.Logger#log()ClassLoader 的双亲委派模型。

  • 深度理解: ClassLoader 在加载类时,先交给父类处理,父类处理不了再自己处理,这就是典型的责任链传递。

5. 命令模式 (Command)

  • 体现: java.lang.Runnable

  • 深度理解: 一个 Runnable 实例就是一个"命令"。它把请求封装成对象,你可以将其丢进线程池执行、放入队列、或者记录日志,实现了"发送者(Thread)"与"接收者(具体的业务逻辑)"的完全解耦。

6. 中介者模式 (Mediator)

  • 体现: java.util.Timer

  • 深度理解: Timer 类就像一个调度中心,它协调 TimerTask(执行者)和后台线程。各个任务之间互不感知,所有协作都通过 Timer 这个中介完成。

7. 解释器模式 (Interpreter)

  • 体现: java.util.regex.Pattern

  • 深度理解: 正则表达式就是一种"小语言"。Pattern 类将正则表达式字符串解析成一套语法树,并执行匹配逻辑。

8. 访问者模式 (Visitor)

  • 体现: java.nio.file.FileVisitor

  • 深度理解: 当你遍历文件夹时,FileVisitor 定义了访问文件和文件夹的行为(visitFile, preVisitDirectory),而文件树的遍历逻辑(Files.walkFileTree)保持不变。

9. 备忘录模式 (Memento)

  • 体现: java.io.Serializable

  • 深度理解: 通过序列化机制,你可以捕获对象的内部状态并将其保存到磁盘,之后通过反序列化"恢复"到之前的状态,这本质上就是备忘录模式的工程化体现。

Spring

一、 创建型模式 (Creational Patterns)

Spring 作为一个"大管家",最擅长的就是如何优雅地创建和管理对象。

1. 单例模式 (Singleton)

  • 体现: Spring Bean 的默认 Scope(作用域)

  • 深度解析: Spring 的单例与传统的 getInstance() 不同,它是通过 单例池 (Singleton Objects Cache) 管理的。Spring 容器确保对于每个 Bean ID,在同一个 IoC 容器中只存在一个共享的实例。

  • 优点: 减少了频繁创建对象的开销,减轻了 GC 压力。

2. 工厂模式 (Factory Method / Abstract Factory)

  • 体现: BeanFactoryApplicationContext

  • 深度解析: * 工厂方法: FactoryBean 接口。如果你想自定义某个复杂对象的创建过程,可以实现这个接口。

    • 抽象工厂: BeanFactory 是最顶层的抽象工厂,它定义了如何获取 Bean 的标准,而具体的实现类负责生产不同种类的对象。

3. 原型模式 (Prototype)

  • 体现: @Scope("prototype")

  • 深度解析: 当你需要每次从容器获取 Bean 时都返回一个新的实例时,Spring 就会使用原型模式。它通过内存拷贝(克隆)或重新执行初始化逻辑来生成新对象。

二、 结构型模式 (Structural Patterns)

这是 Spring 实现 AOP(面向切面编程)MVC 的基石。

1. 代理模式 (Proxy) ------ Spring 的灵魂

  • 体现: Spring AOP。

  • 深度解析: Spring 提供了两种动态代理实现:

    • JDK 动态代理: 针对有接口的类。

    • CGLIB 字节码增强: 针对没有接口的类(通过继承实现)。

  • 应用场景: 声明式事务 @Transactional、日志切面、权限校验。

2. 适配器模式 (Adapter)

  • 体现: Spring MVC 中的 HandlerAdapter

  • 深度解析: DispatcherServlet 需要调用各种不同类型的控制器(Controller、HttpRequestHandler、Servlet)。为了统一调用接口,Spring 为每种控制器都准备了一个适配器。

  • 优点: 极大地增强了 Spring MVC 的扩展性。

3. 装饰器模式 (Decorator)

  • 体现: TransactionAwareCacheDecorator

  • 深度解析: 在集成缓存(Cache)时,Spring 使用装饰器来确保缓存的操作与事务同步。只有当事务提交后,装饰器才会去更新缓存。

三、 行为型模式 (Behavioral Patterns)

这些模式赋予了 Spring 强大的灵活性和可扩展性。

1. 模板方法模式 (Template Method)

  • 体现: JdbcTemplateRestTemplateTransactionTemplate

  • 深度解析: 这是 Spring 中最常用的模式。父类封装了繁琐的固定流程 (如:获取连接、异常处理、释放连接),而将变化的部分(如:SQL 执行、结果集映射)通过回调或抽象方法留给开发者。

2. 策略模式 (Strategy)

  • 体现: Resource 接口、InstantiationStrategy

  • 深度解析: * Resource 接口有多种实现:UrlResourceClassPathResourceFileSystemResource。Spring 根据资源的协议头(如 classpath:http:)自动选择对应的加载策略。

    • Bean 的实例化策略:是反射创建还是 CGLIB 创建,也是一种策略切换。

3. 观察者模式 (Observer)

  • 体现: Spring 事件机制 (ApplicationEvent, ApplicationListener)。

  • 深度解析: 典型的发布-订阅模型。当容器启动完成或刷新时,会发布一个 ContextRefreshedEvent,所有监听该事件的 Bean 都会被触发。

4. 责任链模式 (Chain of Responsibility)

  • 体现: HandlerInterceptor(拦截器)和 Filter(过滤器)。

  • 深度解析: 请求在进入 Controller 之前,会经过一连串的拦截器链。每个拦截器都可以决定是拦截请求还是传递给下一个拦截器。

相关推荐
J_liaty18 小时前
23种设计模式一代理模式
设计模式·代理模式
苏渡苇1 天前
优雅应对异常,从“try-catch堆砌”到“设计驱动”
java·后端·设计模式·学习方法·责任链模式
短剑重铸之日1 天前
《设计模式》第十一篇:总结
java·后端·设计模式·总结
feasibility.1 天前
AI 编程助手进阶指南:从 Claude Code 到 OpenCode 的工程化经验总结
人工智能·经验分享·设计模式·自动化·agi·skills·opencode
BD_Marathon1 天前
七大设计原则介绍
设计模式
YigAin1 天前
Unity23种设计模式之 享元模式
设计模式·享元模式
范纹杉想快点毕业2 天前
实战级ZYNQ中断状态机FIFO设计
java·开发语言·驱动开发·设计模式·架构·mfc
茂桑2 天前
DDD领域驱动设计-基础设施层
设计模式·架构
小温冲冲2 天前
通俗且全面精讲工厂设计模式
设计模式