设计模式理论与实战手册
第一章 前言
1.1 手册定位
本手册打破“仅罗列模式用法”的传统思路,以架构设计视角为核心,先构建设计模式的完整理论体系(定义、核心原则、分类逻辑),再针对每一种常用设计模式,从“设计思想→架构定位→实战场景→代码落地→注意事项”五个维度展开,实现“理论指导实践、实践反哺架构理解”的目标。
手册面向后端开发工程师、架构师,既适合初学者快速掌握设计模式的核心逻辑,也适合资深开发者梳理架构设计中的模式选型思路,为项目的可扩展性、可维护性、复用性提供设计支撑,助力打造高内聚、低耦合的系统架构。
1.2 设计模式的核心价值
设计模式并非“语法技巧”,而是经过长期实践沉淀的、解决特定架构问题的“通用方案”,其核心价值体现在三个层面,贯穿系统设计、开发、维护全流程:
-
解耦架构:降低模块间的依赖,实现“高内聚、低耦合”,便于后续架构迭代和模块替换。
-
提升复用:将通用逻辑、核心流程抽象为可复用的模式,减少重复编码,降低开发成本。
-
增强可维护:统一设计规范,让代码逻辑清晰、可读性强,便于团队协作和后续问题排查、功能扩展。
1.3 适用范围与版本适配
-
适用场景:系统架构设计、模块封装、代码重构、第三方组件开发、SpringBoot/SpringCloud 项目落地。
-
适配语言:以 Java 为主(贴合 Spring 生态),核心思想可迁移至其他面向对象语言(如 Python、Go)。
-
适配框架:SpringBoot 2.x/3.x、SpringCloud,案例均贴合企业级实战场景,可直接复用或修改。
第二章 设计模式核心理论基础
设计模式的本质是“面向对象设计原则的落地”,所有模式的设计都围绕核心原则展开。在学习具体模式前,需先掌握设计模式的基础理论,理解其设计思想的根源,避免“死记硬背模式代码”。
2.1 设计模式的定义
设计模式(Design Pattern)是在特定场景下,解决某一类重复出现的架构问题的成熟方案。它不是一段可直接复制的代码,而是一种“设计思想”“解决方案模板”,可根据具体业务场景灵活调整实现细节。
核心特点:可复用、可扩展、可维护、贴合面向对象设计原则,是“理论+实践”的结合体。
2.2 面向对象设计七大原则
设计模式的所有设计思路,都源于这七大原则——它们是架构设计的“底线”,确保系统具备良好的扩展性和可维护性。以下结合架构视角,简要说明 each 原则的核心思想及实践意义:
| 设计原则 | 核心思想 | 架构实践意义 |
|---|---|---|
| 单一职责原则(SRP) | 一个类/模块只负责一项职责,职责单一 | 降低模块耦合,便于单独维护和扩展(如拆分业务模块、工具类) |
| 开放-封闭原则(OCP) | 对扩展开放,对修改关闭 | 避免修改原有代码引发风险,通过扩展实现新功能(架构迭代的核心原则) |
| 里氏替换原则(LSP) | 子类可替换父类,且不影响程序正确性 | 保障继承关系的合理性,支持多态,便于模块替换(如接口实现类的切换) |
| 依赖倒置原则(DIP) | 依赖抽象,不依赖具体实现;面向接口编程 | 降低模块间的直接依赖,提升架构灵活性(如 Spring 依赖注入的核心思想) |
| 接口隔离原则(ISP) | 拆分庞大接口为多个小型接口,接口职责单一 | 避免接口冗余,减少模块间的不必要依赖(如微服务接口设计) |
| 迪米特法则(LOD) | 一个对象尽可能少地了解其他对象,降低耦合 | 减少模块间的通信成本,避免“牵一发而动全身”(如封装工具类、中间层) |
| 合成复用原则(CRP) | 优先使用组合/聚合,而非继承实现复用 | 避免继承带来的耦合,提升代码复用的灵活性(如 Spring 中 Bean 的组合) |
核心总结:七大原则的核心目标是“解耦”,所有设计模式都是这些原则的具体落地——比如工厂模式体现依赖倒置原则,装饰器模式体现开放-封闭原则。
2.3 设计模式的分类
根据设计模式解决的“架构问题类型”,可将其分为三大类,每类对应不同的架构场景,便于选型时快速定位:
-
创建型模式(5种):解决“对象创建”的架构问题,封装对象创建的细节,降低创建逻辑与业务逻辑的耦合,提升对象创建的灵活性。核心场景:对象创建复杂、需要统一管理创建逻辑(如 Spring Bean 的创建)。
-
结构型模式(7种):解决“对象/模块组合”的架构问题,通过合理的组合方式,优化系统结构,提升模块的复用性和扩展性。核心场景:模块间耦合过高、需要灵活组合多个模块(如微服务接口适配)。
-
行为型模式(11种):解决“对象/模块间通信、行为协作”的架构问题,规范对象间的交互方式,降低通信耦合,提升行为的可复用性和可扩展性。核心场景:多个模块需要协同工作、行为逻辑复杂(如任务调度、事件通知)。
注:本手册聚焦企业级实战中最常用的15种设计模式,重点讲解其架构定位和落地方案,避免冗余的理论堆砌。
第三章 创建型模式:对象创建的标准化
创建型模式的核心架构定位:将对象的创建逻辑与业务逻辑分离,由专门的“创建器”负责对象创建,统一管理创建流程(如对象初始化、依赖注入),避免业务代码中嵌入复杂的创建逻辑,提升架构的灵活性和可维护性。
核心设计思想:遵循“依赖倒置原则”,面向抽象创建对象,而非直接 new 具体类,便于后续替换对象实现。
3.1 单例模式(Singleton Pattern)
3.1.1 设计思想与架构定位
核心思想:确保一个类在整个系统中只有一个实例,并提供一个全局唯一的访问入口,避免重复创建对象导致的资源浪费(如连接池、日志对象)。
架构定位:解决“对象重复创建”的问题,适用于全局共享、无状态的对象(如工具类、配置类、连接池),是架构中“资源复用”的基础模式。
3.1.2 实战场景
-
Spring 容器中的 Bean 默认是单例模式(无状态 Bean),全局复用一个实例,减少资源消耗。
-
系统中的日志工具类、配置工具类、连接池(如数据库连接池、Redis 连接池),需保证全局唯一实例。
-
计数器、全局缓存等,需保证数据一致性,避免多实例导致的数据混乱。
3.1.3 实战代码(企业级推荐:双重检查锁+volatile)
单例模式有多种实现方式,企业级实战中优先选择“双重检查锁+volatile”,兼顾线程安全、性能和懒加载,避免饿汉式的资源浪费和懒汉式的线程安全问题。
/**
* 单例模式实战:全局日志工具类(线程安全、懒加载、高性能)
* 架构说明:全局唯一实例,避免重复创建日志对象,统一日志输出逻辑
*/
public class LogUtils {
// volatile 防止指令重排,确保 instance 初始化完成后再被访问
private static volatile LogUtils instance;
// 私有构造方法,禁止外部 new 实例
private LogUtils() {
// 初始化日志配置(如加载日志文件、设置日志级别)
initLogConfig();
}
// 双重检查锁,兼顾线程安全和性能
public static LogUtils getInstance() {
if (instance == null) { // 第一次检查,避免不必要的锁竞争
synchronized (LogUtils.class) { // 加锁,保证线程安全
if (instance == null) { // 第二次检查,防止多线程同时进入后重复创建
instance = new LogUtils();
}
}
}
return instance;
}
// 日志初始化逻辑(业务细节)
private void initLogConfig() {
System.out.println("初始化日志配置,全局唯一实例");
}
// 日志输出方法(业务逻辑)
public void info(String message) {
System.out.println("[INFO] " + message);
}
}
Spring 中的应用:Spring 容器通过 @Bean 注解默认创建单例 Bean,底层通过单例模式管理 Bean 实例,开发者可通过 @Scope("singleton") 显式指定(默认就是单例)。
3.1.4 注意事项
-
避免单例对象持有状态:单例对象是全局共享的,若持有状态(如成员变量),多线程环境下会导致数据安全问题,建议单例对象仅提供无状态方法(如工具类)。
-
避免滥用单例:并非所有类都适合单例,若对象需要频繁创建、销毁,且资源消耗低(如普通实体类),不建议使用单例,否则会降低系统灵活性。
-
序列化问题:若单例类实现 Serializable 接口,需重写 readResolve() 方法,避免反序列化创建新实例。
3.2 工厂模式(Factory Pattern)
工厂模式分为三种:简单工厂模式、工厂方法模式、抽象工厂模式,核心思想一致,复杂度逐步提升,适配不同的架构场景。重点讲解企业级实战中最常用的工厂方法模式和抽象工厂模式。
3.2.1 设计思想与架构定位
核心思想:定义一个“创建对象的接口”,由子类决定具体创建哪个对象,将对象创建的逻辑延迟到子类,实现“创建与使用分离”。
架构定位:解决“对象创建逻辑复杂、需要灵活切换对象实现”的问题,遵循依赖倒置原则,面向抽象编程,提升架构的扩展性(如切换第三方组件、适配不同业务场景)。
3.2.2 工厂方法模式
实战首选
3.2.3 实战场景
系统需要对接多种支付方式(微信支付、支付宝支付、银联支付),每种支付方式的创建逻辑不同,但对外提供统一的支付接口,需要灵活切换支付方式,且便于后续新增支付方式(如新增苹果支付)。
3.2.4 实战代码(SpringBoot 场景)
// 1. 抽象产品:支付接口(面向抽象,定义统一行为)
public interface Payment {
// 统一支付方法
String pay(double amount);
}
// 2. 具体产品:微信支付(实现抽象接口)
public class WeChatPayment implements Payment {
@Override
public String pay(double amount) {
return "微信支付:" + amount + "元,支付成功";
}
}
// 3. 具体产品:支付宝支付(实现抽象接口)
public class AlipayPayment implements Payment {
@Override
public String pay(double amount) {
return "支付宝支付:" + amount + "元,支付成功";
}
}
// 4. 抽象工厂:支付工厂接口(定义创建产品的接口)
public interface PaymentFactory {
Payment createPayment();
}
// 5. 具体工厂:微信支付工厂(创建微信支付实例)
public class WeChatPaymentFactory implements PaymentFactory {
@Override
public Payment createPayment() {
// 微信支付的创建逻辑(如初始化配置、注入依赖)
return new WeChatPayment();
}
}
// 6. 具体工厂:支付宝支付工厂(创建支付宝支付实例)
public class AlipayPaymentFactory implements PaymentFactory {
@Override
public Payment createPayment() {
// 支付宝支付的创建逻辑
return new AlipayPayment();
}
}
// 7. 业务层:使用工厂创建支付实例(无需关注具体创建逻辑)
@Service
public class PaymentService {
// 根据支付类型,获取对应的工厂,创建支付实例
public String doPay(String paymentType, double amount) {
PaymentFactory factory;
switch (paymentType) {
case "wechat":
factory = new WeChatPaymentFactory();
break;
case "alipay":
factory = new AlipayPaymentFactory();
break;
default:
throw new IllegalArgumentException("不支持的支付方式");
}
Payment payment = factory.createPayment();
return payment.pay(amount);
}
}
3.2.5 架构优势
新增支付方式(如银联支付)时,只需新增“银联支付产品类”和“银联支付工厂类”,无需修改原有业务代码,完全符合“开放-封闭原则”,降低架构迭代风险。
3.3 抽象工厂模式
复杂场景适配
当系统需要创建“一组相关联的产品”(而非单个产品)时,使用抽象工厂模式。例如:支付系统中,除了支付方式,还有退款方式、对账方式,每种支付方式对应一套退款、对账逻辑,需要统一创建这一组产品。
3.3.1 注意事项
-
工厂模式适合“产品种类固定、新增产品频繁”的场景,若产品种类极少且不常变化,无需使用工厂模式(避免过度设计)。
-
结合 Spring 依赖注入:企业级实战中,可将工厂类、产品类注入 Spring 容器,通过 @Autowired 获取,无需手动 new 实例,提升架构的灵活性。
-
避免工厂类过于复杂:每个工厂只负责创建一类产品,遵循单一职责原则,避免一个工厂创建多种不相关的产品。
3.4 建造者模式(Builder Pattern)
3.4.1 设计思想与架构定位
核心思想:将复杂对象的创建过程拆解为多个步骤,通过“建造者”逐步构建对象,允许灵活配置对象的各个属性,避免创建过程中出现大量构造方法(如参数过多的构造器)。
架构定位:解决“复杂对象创建”的问题,适用于对象属性多、构造复杂、需要灵活配置的场景(如实体类、配置对象、DTO 构建),提升代码的可读性和可维护性。
3.4.2 实战场景
-
用户注册DTO、订单DTO等,属性较多(如用户名、手机号、邮箱、地址等),需要灵活配置部分属性,避免构造器参数过多。
-
复杂配置对象(如线程池配置、连接池配置),属性多且有默认值,需要支持自定义配置。
-
Spring 中的 Bean 构建(如 @Builder 注解,底层基于建造者模式)。
3.4.3 实战代码(Lombok 简化+原生实现)
企业级实战中,可使用 Lombok 的 @Builder 注解快速实现建造者模式,简化代码;若需自定义建造逻辑,可手动实现建造者。
// 方式1:Lombok @Builder 简化实现(推荐,减少重复代码)
import lombok.Builder;
import lombok.Data;
/**
* 订单DTO(复杂对象,属性较多)
*/
@Data
@Builder
public class OrderDTO {
private Long orderId;
private String userId;
private double amount;
private String orderStatus;
private String address;
private String phone;
// 更多属性...
// Lombok 自动生成建造者方法,使用方式:
// OrderDTO order = OrderDTO.builder()
// .orderId(1L)
// .userId("1001")
// .amount(99.9)
// .build();
}
// 方式2:手动实现建造者模式(自定义建造逻辑,如属性校验)
public class OrderDTO {
private Long orderId;
private String userId;
private double amount;
private String orderStatus;
// 私有构造方法,只能通过建造者创建
private OrderDTO(Builder builder) {
this.orderId = builder.orderId;
this.userId = builder.userId;
this.amount = builder.amount;
this.orderStatus = builder.orderStatus;
// 自定义校验逻辑(如金额不能为负)
validate();
}
// 校验逻辑
private void validate() {
if (amount < 0) {
throw new IllegalArgumentException("订单金额不能为负");
}
if (userId == null || userId.isEmpty()) {
throw new IllegalArgumentException("用户ID不能为空");
}
}
// 建造者内部类
public static class Builder {
private Long orderId;
private String userId;
private double amount;
private String orderStatus;
// 链式调用方法
public Builder orderId(Long orderId) {
this.orderId = orderId;
return this;
}
public Builder userId(String userId) {
this.userId = userId;
return this;
}
public Builder amount(double amount) {
this.amount = amount;
return this;
}
public Builder orderStatus(String orderStatus) {
this.orderStatus = orderStatus;
return this;
}
// 构建对象
public OrderDTO build() {
return new OrderDTO(this);
}
}
// 使用方式
public static void main(String[] args) {
OrderDTO order = new OrderDTO.Builder()
.orderId(1L)
.userId("1001")
.amount(99.9)
.orderStatus("PENDING")
.build();
}
}
3.4.4 注意事项
-
建造者模式适合“属性多、构造复杂”的对象,若对象属性少(3个以内),无需使用,避免过度设计。
-
添加属性校验:在建造者的 build() 方法或对象的构造方法中添加属性校验,确保对象创建的合法性。
-
与工厂模式区分:工厂模式侧重“创建不同类型的对象”,建造者模式侧重“创建同一类型、不同配置的对象”。
3.5 原型模式(Prototype Pattern)
3.5.1 设计思想与架构定位
核心思想:通过“复制现有对象”来创建新对象,而非重新 new 实例,减少对象创建的开销(尤其是对象初始化复杂、耗时的场景)。
架构定位:解决“对象创建耗时、资源消耗高”的问题,适用于频繁创建相似对象的场景(如批量创建订单、批量生成报表数据)。
3.5.2 实战场景
-
批量生成订单数据:订单对象初始化需要查询用户信息、商品信息,频繁 new 实例会消耗大量资源,通过复制原型对象提升效率。
-
缓存对象复制:缓存中存储的对象,需要频繁获取并修改部分属性,通过复制原型避免修改原缓存对象。
-
Spring 中的 Bean 作用域(prototype),每次获取 Bean 时,复制一个新实例。
3.5.3 实战代码(深拷贝+浅拷贝)
原型模式分为浅拷贝和深拷贝,浅拷贝仅复制对象本身,不复制引用类型属性;深拷贝复制对象本身及所有引用类型属性,企业级实战中多使用深拷贝,避免数据安全问题。
import java.io.*;
/**
* 原型模式实战:订单原型(深拷贝实现)
*/
public class OrderPrototype implements Cloneable, Serializable {
private Long orderId;
private String userId;
private Product product; // 引用类型属性(需要深拷贝)
// 浅拷贝(默认 clone() 方法,不复制引用类型)
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
// 深拷贝(通过序列化实现,复制所有属性,包括引用类型)
public OrderPrototype deepClone() {
try {
// 序列化:将对象写入流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 反序列化:从流中读取对象(新实例)
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (OrderPrototype) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("深拷贝失败", e);
}
}
// 静态内部类:引用类型属性(需实现 Serializable,否则深拷贝失败)
static class Product implements Serializable {
private Long productId;
private String productName;
// getter/setter 省略
}
// 实战使用
public static void main(String[] args) throws CloneNotSupportedException {
// 1. 创建原型对象
OrderPrototype prototype = new OrderPrototype();
prototype.setOrderId(1L);
prototype.setUserId("1001");
Product product = new Product();
product.setProductId(100L);
product.setProductName("手机");
prototype.setProduct(product);
// 2. 深拷贝创建新对象
OrderPrototype newOrder = prototype.deepClone();
// 3. 修改新对象的引用属性,不会影响原型对象(深拷贝特性)
newOrder.getProduct().setProductName("电脑");
System.out.println(prototype.getProduct().getProductName()); // 输出:手机
System.out.println(newOrder.getProduct().getProductName()); // 输出:电脑
}
// getter/setter 省略
}
3.5.4 注意事项
-
引用类型属性需支持深拷贝:若对象包含引用类型属性,需实现序列化(或手动复制引用对象),否则深拷贝会失败,导致数据关联问题。
-
避免滥用原型模式:若对象创建成本低、属性简单,无需使用原型模式,直接 new 实例更高效。
-
结合缓存:将常用的原型对象缓存起来,需要时直接复制,进一步提升效率(如缓存模板对象)。
第四章 结构型模式:模块组合的优化
结构型模式的核心架构定位:优化对象/模块的组合方式,通过合理的组合、继承、代理等方式,解决模块间耦合过高、功能扩展困难的问题,提升系统架构的灵活性和复用性。
核心设计思想:遵循“合成复用原则”,优先通过组合/聚合实现模块复用,而非继承,降低模块间的耦合。
4.1 代理模式(Proxy Pattern)
4.1.1 设计思想与架构定位
核心思想:为目标对象创建一个“代理对象”,代理对象负责拦截目标对象的方法调用,在方法执行前后添加额外逻辑(如日志、权限校验、缓存),而不修改目标对象本身。
架构定位:解决“在不修改原有代码的前提下,为目标对象添加额外功能”的问题,是 AOP(面向切面编程)的核心实现模式,适用于日志、权限、缓存、事务等横切逻辑的统一处理。
4.1.2 实战场景
-
接口权限校验:在调用目标接口前,通过代理对象校验用户权限,无权限则拒绝调用。
-
方法调用日志:通过代理对象记录方法的入参、出参、调用耗时,便于问题排查。
-
缓存增强:在调用目标方法前,先查询缓存,缓存存在则直接返回,不存在则调用目标方法并缓存结果。
-
Spring AOP:底层基于动态代理模式(JDK 动态代理、CGLIB 动态代理),实现横切逻辑的统一管理。
4.1.3 实战代码(JDK 动态代理+Spring AOP 落地)
代理模式分为静态代理和动态代理,企业级实战中优先使用动态代理(无需手动创建代理类,提升开发效率),JDK 动态代理基于接口,CGLIB 动态代理基于子类。
// 1. 目标接口(JDK 动态代理必须基于接口)
public interface UserService {
String getUserById(String userId);
}
// 2. 目标对象(业务逻辑实现)
@Service
public class UserServiceImpl implements UserService {
@Override
public String getUserById(String userId) {
// 模拟数据库查询
System.out.println("查询用户信息,userId:" + userId);
return "用户:" + userId + ",姓名:张三";
}
}
// 3. 动态代理处理器(实现 InvocationHandler,添加横切逻辑)
public class LogProxyHandler implements InvocationHandler {
// 目标对象
private Object target;
public LogProxyHandler(Object target) {
this.target = target;
}
// 拦截目标方法调用,添加日志逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法执行前:记录入参
System.out.println("方法 " + method.getName() + " 入参:" + args[0]);
long start = System.currentTimeMillis();
// 调用目标方法
Object result = method.invoke(target, args);
// 方法执行后:记录出参、耗时
long cost = System.currentTimeMillis() - start;
System.out.println("方法 " + method.getName() + " 出参:" + result);
System.out.println("方法调用耗时:" + cost + "ms");
return result;
}
}
// 4. 代理工厂(创建代理对象)
public class ProxyFactory {
public static Object createProxy(Object target) {
// JDK 动态代理:创建代理对象
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 目标对象实现的接口
new LogProxyHandler(target) // 代理处理器
);
}
}
// 5. 测试使用
public class ProxyTest {
public static void main(String[] args) {
// 目标对象
UserService userService = new UserServiceImpl();
// 创建代理对象
UserService proxy = (UserService) ProxyFactory.createProxy(userService);
// 调用代理对象方法(触发拦截逻辑)
proxy.getUserById("1001");
}
}
// 6. Spring AOP 简化实现(企业级首选,无需手动创建代理)
@Aspect
@Component
public class LogAspect {
// 切入点:拦截 UserService 所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")
public void logPointcut() {}
// 环绕通知:添加日志逻辑(底层自动生成代理对象)
@Around("logPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 方法执行前
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("方法 " + methodName + " 入参:" + args[0]);
long start = System.currentTimeMillis();
// 调用目标方法
Object result = joinPoint.proceed();
// 方法执行后
long cost = System.currentTimeMillis() - start;
System.out.println("方法 " + methodName + " 出参:" + result);
System.out.println("方法调用耗时:" + cost + "ms");
return result;
}
}
4.1.4 注意事项
-
代理模式的核心是“不修改目标对象”,所有额外逻辑都在代理对象中实现,符合开放-封闭原则。
-
JDK 动态代理与 CGLIB 选择:若目标对象实现了接口,优先使用 JDK 动态代理;若目标对象无接口,使用 CGLIB 动态代理(Spring AOP 自动适配)。
-
避免代理链过长:若多个代理对象嵌套(如日志代理→权限代理→缓存代理),会增加系统开销,建议合并横切逻辑,简化代理链。
4.2 装饰器模式(Decorator Pattern)
4.2.1 设计思想与架构定位
核心思想:动态地给目标对象添加额外功能,通过“装饰器类”包裹目标对象,装饰器与目标对象实现同一接口,既保留目标对象的原有功能,又可灵活添加新功能,支持多层装饰。
架构定位:解决“动态扩展对象功能”的问题,适用于需要为对象添加多种可选功能、且功能可组合的场景(如文件读写、接口增强),与代理模式的区别是:装饰器模式侧重“功能扩展”,代理模式侧重“功能拦截”。
4.2.2 实战场景
-
文件读写增强:基础文件读写功能,可通过装饰器添加“加密/解密”“日志记录”“缓存”等功能,且功能可组合(如先加密再日志)。
-
接口响应增强:为接口添加“数据格式化”“脱敏”“缓存”等功能,无需修改接口原有逻辑。
-
Java IO 中的装饰器模式:如 BufferedInputStream(缓冲装饰)、DataInputStream(数据类型装饰),都是对基础 InputStream 的功能扩展。
4.2.3 实战代码(文件读写增强场景)
// 1. 抽象组件:文件读写接口(定义核心功能)
public interface FileReader {
String read(String filePath);
}
// 2. 具体组件:基础文件读写实现(核心功能)
public class BasicFileReader implements FileReader {
@Override
public String read(String filePath) {
// 模拟基础文件读取逻辑
System.out.println("读取文件:" + filePath);
return "文件内容:Hello World";
}
}
// 3. 装饰器抽象类(实现抽象组件,持有目标对象)
public abstract class FileReaderDecorator implements FileReader {
// 持有目标对象(被装饰的对象)
protected FileReader fileReader;
public FileReaderDecorator(FileReader fileReader) {
this.fileReader = fileReader;
}
// 重写 read 方法,默认调用目标对象的方法,子类可扩展
@Override
public String read(String filePath) {
return fileReader.read(filePath);
}
}
// 4. 具体装饰器1:日志装饰器(添加日志功能)
public class LogFileReaderDecorator extends FileReaderDecorator {
public LogFileReaderDecorator(FileReader fileReader) {
super(fileReader);
}
@Override
public String read(String filePath) {
// 装饰逻辑:读取前记录日志
System.out.println("日志装饰器:开始读取文件 " + filePath);
// 调用目标对象的核心功能
String content = super.read(filePath);
// 装饰逻辑:读取后记录日志
System.out.println("日志装饰器:文件读取完成,内容长度:" + content.length());
return content;
}
}
// 5. 具体装饰器2:加密装饰器(添加加密功能)
public class EncryptFileReaderDecorator extends FileReaderDecorator {
public EncryptFileReaderDecorator(FileReader fileReader) {
super(fileReader);
}
@Override
public String read(String filePath) {
// 先调用目标对象的核心功能
String content = super.read(filePath);
// 装饰逻辑:对内容进行加密(模拟加密)
String encryptedContent = "加密后:" + content + "_encrypted";
System.out.println("加密装饰器:文件内容加密完成");
return encryptedContent;
}
}
// 6. 实战使用(多层装饰,组合功能)
public class DecoratorTest {
public static void main(String[] args) {
// 1. 基础文件读取
FileReader basicReader = new BasicFileReader();
// 2. 添加日志装饰(基础功能+日志)
FileReader logReader = new LogFileReaderDecorator(basicReader);
// 3. 添加加密装饰(基础功能+日志+加密)
FileReader encryptReader = new EncryptFileReaderDecorator(logReader);
// 4. 调用装饰后的方法,触发所有装饰逻辑
String content = encryptReader.read("D:/test.txt");
System.out.println("最终读取结果:" + content);
}
}
4.2.4 注意事项
-
装饰器与目标对象必须实现同一接口:确保装饰器可以替代目标对象,符合里氏替换原则。
-
支持多层装饰:装饰器可嵌套使用,实现功能的组合,但需避免装饰层数过多,导致代码复杂度提升。
-
与继承区分:继承是静态扩展功能,装饰器是动态扩展功能,优先使用装饰器,避免继承带来的耦合。
4.3 适配器模式(Adapter Pattern)
4.3.1 设计思想与架构定位
核心思想:将一个类的接口转换成客户端期望的另一个接口,使原本接口不兼容的类可以一起工作,解决“接口不匹配”的架构问题。
架构定位:解决“模块接口不兼容”的问题,适用于第三方组件集成、旧系统改造、跨模块接口适配等场景,是系统集成的核心模式。
4.3.2 实战场景
-
第三方组件集成:第三方接口的返回格式、方法名与本地系统不兼容,通过适配器转换接口,实现集成。
-
旧系统改造:旧系统的接口与新系统接口不兼容,无需修改旧系统代码,通过适配器适配新接口。
-
SpringMVC 中的适配器模式:HandlerAdapter 适配不同类型的处理器(如 Controller、HttpRequestHandler),统一调用方式。
4.3.3 实战代码(第三方支付接口适配场景)
// 1. 客户端期望的接口(本地系统接口)
public interface PaymentAdapter {
// 本地系统期望的支付方法:入参为金额,返回支付结果
String pay(double amount);
}
// 2. 第三方支付接口(接口不兼容,方法名、入参不同)
public class ThirdPartyPayment {
// 第三方接口方法:入参为金额字符串,返回布尔值
public boolean doPayment(String amount) {
System.out.println("第三方支付:金额 " + amount + " 元");
return true; // 支付成功返回true
}
}
// 3. 适配器类(将第三方接口适配为本地系统接口)
public class ThirdPartyPaymentAdapter implements PaymentAdapter {
// 持有第三方接口对象
private ThirdPartyPayment thirdPartyPayment;
public ThirdPartyPaymentAdapter(ThirdPartyPayment thirdPartyPayment) {
this.thirdPartyPayment = thirdPartyPayment;
}
// 适配本地接口方法,调用第三方接口
@Override
public String pay(double amount) {
// 1. 转换入参:本地double类型 → 第三方String类型
String amountStr = String.valueOf(amount);
// 2. 调用第三方接口
boolean result = thirdPartyPayment.doPayment(amountStr);
// 3. 转换返回值:第三方布尔值 → 本地字符串
return result ? "支付成功" : "支付失败";
}
}
// 4. 实战使用(客户端调用本地接口,无需关注第三方接口细节)
public class AdapterTest {
public static void main(String[] args) {
// 第三方支付对象
ThirdPartyPayment thirdPartyPayment = new ThirdPartyPayment();
// 创建适配器
PaymentAdapter adapter = new ThirdPartyPaymentAdapter(thirdPartyPayment);
// 客户端调用本地接口,适配器自动适配第三方接口
String result = adapter.pay(99.9);
System.out.println("客户端收到结果:" + result);
}
}
4.3.4 注意事项
-
适配器模式的核心是“接口转换”,不修改原有接口和实现类,符合开放-封闭原则。
-
避免过度适配:若接口差异过大,适配成本过高,建议重新设计接口,而非强行使用适配器。
-
区分适配器与装饰器:适配器侧重“接口兼容”,装饰器侧重“功能扩展”,二者场景不同,不可混淆。
4.4 组合模式(Composite Pattern)
4.4.1 设计思想与架构定位
核心思想:将对象组合成树形结构,以表示“部分-整体”的层次关系,使得客户端对单个对象和组合对象的调用方式一致,无需区分处理。
架构定位:解决“树形结构数据的统一操作”问题,适用于具有层次结构的场景(如菜单、部门、文件目录),简化客户端对树形结构的操作。
4.4.2 实战场景
-
系统菜单:菜单分为一级菜单、二级菜单、三级菜单,客户端需要统一遍历、展示所有菜单,无需区分单个菜单和菜单组。
-
部门结构:公司部门分为总部、分公司、部门、小组,需要统一统计部门人数、查询部门信息。
-
文件目录:文件夹和文件构成树形结构,客户端需要统一删除、复制、遍历所有文件和文件夹。
4.4.3 实战代码(系统菜单场景)
// 1. 抽象组件:菜单接口(定义统一操作)
public interface MenuComponent {
// 展示菜单
void show();
// 添加子菜单(组合对象需要实现,单个对象无需实现)
default void add(MenuComponent menu) {
throw new UnsupportedOperationException("不支持添加子菜单");
}
// 删除子菜单(组合对象需要实现,单个对象无需实现)
default void remove(MenuComponent menu) {
throw new UnsupportedOperationException("不支持删除子菜单");
}
// 获取子菜单(组合对象需要实现,单个对象无需实现)
default MenuComponent getChild(int index) {
throw new UnsupportedOperationException("不支持获取子菜单");
}
}
// 2. 叶子节点:单个菜单(无子女菜单)
public class LeafMenu implements MenuComponent {
private String menuName;
private String url;
public LeafMenu(String menuName, String url) {
this.menuName = menuName;
this.url = url;
}
@Override
public void show() {
System.out.println("单个菜单:" + menuName + ",URL:" + url);
}
}
// 3. 组合节点:菜单组(包含子菜单,可添加/删除子菜单)
public class CompositeMenu implements MenuComponent {
private String menuName;
// 存储子菜单(叶子节点或组合节点)
private List<MenuComponent> children = new ArrayList<>();
public CompositeMenu(String menuName) {
this.menuName = menuName;
}
@Override
public void show() {
System.out.println("菜单组:" + menuName);
// 递归展示所有子菜单
for (MenuComponent child : children) {
child.show();
}
}
@Override
public void add(MenuComponent menu) {
children.add(menu);
}
@Override
public void remove(MenuComponent menu) {
children.remove(menu);
}
@Override
public MenuComponent getChild(int index) {
return children.get(index);
}
}
// 4. 实战使用(客户端统一操作单个菜单和菜单组)
public class CompositeTest {
public static void main(String[] args) {
// 1. 创建叶子菜单(单个菜单)
MenuComponent homeMenu = new LeafMenu("首页", "/home");
MenuComponent userMenu = new LeafMenu("用户管理", "/user");
// 2. 创建菜单组(系统管理),添加子菜单
MenuComponent systemMenu = new CompositeMenu("系统管理");
MenuComponent logMenu = new LeafMenu("日志管理", "/log");
MenuComponent configMenu = new LeafMenu("配置管理", "/config");
systemMenu.add(logMenu);
systemMenu.add(configMenu);
// 3. 创建根菜单组(总菜单),添加子菜单和菜单组
MenuComponent rootMenu = new CompositeMenu("系统总菜单");
rootMenu.add(homeMenu);
rootMenu.add(userMenu);
rootMenu.add(systemMenu);
// 4. 客户端统一调用 show() 方法,无需区分单个菜单和菜单组
rootMenu.show();
}
}
4.4.4 注意事项
-
抽象组件需定义统一接口:确保叶子节点和组合节点的操作方式一致,客户端无需区分处理。
-
避免过度设计:若树形结构简单、无需频繁添加/删除子节点,无需使用组合模式,直接使用普通类即可。
-
递归操作需注意性能:组合节点的递归操作(如 show())可能导致栈溢出,需控制树形结构的深度。
第五章 行为型模式:对象协作的标准化
行为型模式的核心架构定位:规范对象/模块间的通信与协作方式,解决对象间行为耦合过高、协作逻辑混乱的问题,使对象间的交互更灵活、可复用、可扩展。
核心设计思想:将对象的“行为”与“对象本身”分离,通过引入中间层(如观察者、中介者),规范对象间的交互,降低耦合。
5.1 观察者模式(Observer Pattern)
5.1.1 设计思想与架构定位
核心思想:定义“一对多”的依赖关系,当一个对象(被观察者)的状态发生变化时,所有依赖它的对象(观察者)都会收到通知,并自动更新自身状态,无需被观察者主动调用观察者方法。
架构定位:解决“对象状态变化后,多对象需同步响应”的问题,适用于事件通知、状态同步场景,实现“发布-订阅”的解耦,让被观察者与观察者完全分离,互不依赖。
5.1.2 实战场景
-
系统通知机制:用户注册成功后,需要同步发送短信通知、邮件通知、积分赠送,这些操作作为观察者,监听用户注册(被观察者)的状态变化。
-
配置中心:配置文件更新后,所有依赖该配置的服务(观察者)自动更新本地配置,无需重启服务。
-
Spring 中的应用:Spring 的 ApplicationEvent 和 ApplicationListener 接口,底层基于观察者模式,实现事件驱动编程(如上下文刷新事件、自定义业务事件)。
5.1.3 实战代码(SpringBoot 自定义事件场景)
// 1. 定义被观察者(事件对象):用户注册事件
public class UserRegisterEvent extends ApplicationEvent {
// 事件携带的数据(用户ID)
private String userId;
// 构造方法,传入事件源和数据
public UserRegisterEvent(Object source, String userId) {
super(source);
this.userId = userId;
}
// getter 方法
public String getUserId() {
return userId;
}
}
// 2. 定义被观察者(事件发布者):用户服务(发布注册事件)
@Service
public class UserService {
// 注入 Spring 事件发布器
@Autowired
private ApplicationEventPublisher eventPublisher;
// 用户注册方法,发布事件
public void register(String userId) {
// 1. 核心业务逻辑:用户注册
System.out.println("用户 " + userId + " 注册成功");
// 2. 发布事件(被观察者状态变化,通知所有观察者)
eventPublisher.publishEvent(new UserRegisterEvent(this, userId));
}
}
// 3. 定义观察者1:短信通知观察者
@Component
public class SmsNotificationListener implements ApplicationListener<UserRegisterEvent> {
// 监听事件,触发响应逻辑
@Override
public void onApplicationEvent(UserRegisterEvent event) {
String userId = event.getUserId();
System.out.println("给用户 " + userId + " 发送注册成功短信");
}
}
// 4. 定义观察者2:邮件通知观察者
@Component
public class EmailNotificationListener implements ApplicationListener<UserRegisterEvent> {
@Override
public void onApplicationEvent(UserRegisterEvent event) {
String userId = event.getUserId();
System.out.println("给用户 " + userId + " 发送注册成功邮件");
}
}
// 5. 定义观察者3:积分赠送观察者
@Component
public class PointListener implements ApplicationListener<UserRegisterEvent> {
@Override
public void onApplicationEvent(UserRegisterEvent event) {
String userId = event.getUserId();
System.out.println("给用户 " + userId + " 赠送100注册积分");
}
}
// 6. 测试使用
@SpringBootTest
public class ObserverTest {
@Autowired
private UserService userService;
@Test
public void testRegister() {
// 调用注册方法,触发事件发布,所有观察者同步响应
userService.register("1001");
}
}
5.1.4 注意事项
-
避免观察者过多:若观察者数量过多,被观察者状态变化时,所有观察者都会被通知,会导致响应延迟,建议拆分事件,避免“一通知多”的过度耦合。
-
区分同步与异步:Spring 默认是同步通知(观察者依次执行),若观察者逻辑耗时,需改为异步通知(添加 @Async 注解),避免阻塞被观察者。
-
避免循环依赖:观察者与被观察者之间不可相互依赖,否则会导致循环调用,引发系统异常。
5.2 策略模式(Strategy Pattern)
5.2.1 设计思想与架构定位
核心思想:定义一系列算法,将每个算法封装为独立的策略类,使算法可独立于使用它的客户端而变化,客户端可根据场景动态选择不同的算法,无需修改客户端代码。
架构定位:解决“算法多样化、需要灵活切换”的问题,遵循开放-封闭原则,将算法的定义与使用分离,提升算法的可扩展性和可维护性,避免大量 if-else 分支判断。
5.2.2 实战场景
-
支付方式选择:不同用户(普通用户、VIP用户、会员用户)对应不同的支付折扣算法,根据用户类型动态选择折扣策略。
-
数据排序:系统需要支持多种排序算法(冒泡排序、快速排序、归并排序),根据数据量动态选择合适的排序策略。
-
Spring 中的应用:Spring 的 Resource 接口(不同资源类型的读取策略)、HandlerMapping(不同请求映射策略),均基于策略模式设计。
5.2.3 实战代码(支付折扣策略场景)
// 1. 抽象策略:折扣算法接口(定义统一算法规范)
public interface DiscountStrategy {
// 计算折扣后金额
double calculateDiscount(double originalAmount);
}
// 2. 具体策略1:普通用户策略(无折扣)
public class NormalUserDiscount implements DiscountStrategy {
@Override
public double calculateDiscount(double originalAmount) {
System.out.println("普通用户,无折扣");
return originalAmount;
}
}
// 3. 具体策略2:VIP用户策略(9折)
public class VipUserDiscount implements DiscountStrategy {
@Override
public double calculateDiscount(double originalAmount) {
System.out.println("VIP用户,9折优惠");
return originalAmount * 0.9;
}
}
// 4. 具体策略3:会员用户策略(8折)
public class MemberUserDiscount implements DiscountStrategy {
@Override
public double calculateDiscount(double originalAmount) {
System.out.println("会员用户,8折优惠");
return originalAmount * 0.8;
}
}
// 5. 策略上下文:统一管理策略,供客户端调用
@Service
public class DiscountContext {
// 存储所有策略(Spring 自动注入所有 DiscountStrategy 实现类)
private final Map<String, DiscountStrategy> strategyMap;
// 构造方法注入所有策略,key为策略名称(如normal、vip、member)
public DiscountContext(Map<String, DiscountStrategy> strategyMap) {
this.strategyMap = strategyMap;
}
// 客户端调用方法,根据用户类型选择策略
public double getDiscountAmount(String userType, double originalAmount) {
// 根据用户类型获取对应的策略,默认使用普通用户策略
DiscountStrategy strategy = strategyMap.getOrDefault(userType, new NormalUserDiscount());
// 执行策略,返回折扣后金额
return strategy.calculateDiscount(originalAmount);
}
}
// 6. 实战使用
@SpringBootTest
public class StrategyTest {
@Autowired
private DiscountContext discountContext;
@Test
public void testDiscount() {
double originalAmount = 100.0;
// 普通用户
double normalAmount = discountContext.getDiscountAmount("normal", originalAmount);
// VIP用户
double vipAmount = discountContext.getDiscountAmount("vip", originalAmount);
// 会员用户
double memberAmount = discountContext.getDiscountAmount("member", originalAmount);
System.out.println("普通用户实付:" + normalAmount);
System.out.println("VIP用户实付:" + vipAmount);
System.out.println("会员用户实付:" + memberAmount);
}
}
5.2.4 注意事项
-
策略类职责单一:每个策略类只实现一种算法,遵循单一职责原则,便于维护和扩展。
-
策略上下文简化:上下文仅负责策略的选择和调用,不负责算法的实现,避免上下文过于复杂。
-
避免策略过多:若策略数量过多(超过5个),建议结合工厂模式管理策略,提升代码可读性。
5.3 模板方法模式(Template Method Pattern)
5.3.1 设计思想与架构定位
核心思想:定义一个操作的核心流程(模板方法),将流程中的可变步骤延迟到子类中实现,子类可在不修改核心流程的前提下,重写可变步骤,实现流程的定制化。
架构定位:解决“流程固定、步骤可变”的问题,适用于具有统一流程但具体实现不同的场景(如业务审批、数据导入),统一流程规范,减少重复编码,提升代码复用性。
5.3.2 实战场景
-
业务审批流程:所有审批(请假审批、报销审批、采购审批)的核心流程一致(提交→审核→审批→归档),但审核、审批的具体逻辑不同。
-
数据导入流程:不同类型数据(用户数据、商品数据、订单数据)的导入流程一致(读取文件→校验数据→解析数据→入库),但校验、解析逻辑不同。
-
Spring 中的应用:Spring 的 JdbcTemplate(核心流程固定,查询、更新的具体逻辑由用户自定义),底层基于模板方法模式。
5.3.3 实战代码(数据导入场景)
// 1. 抽象模板类:定义数据导入的核心流程(模板方法)
public abstract class DataImportTemplate {
/**
* 模板方法:核心流程(固定不变)
* 用 final 修饰,防止子类修改核心流程
*/
public final void importData(String filePath) {
// 步骤1:读取文件(固定步骤)
String fileContent = readFile(filePath);
// 步骤2:校验数据(可变步骤,子类实现)
boolean validateResult = validateData(fileContent);
if (!validateResult) {
System.out.println("数据校验失败,导入终止");
return;
}
// 步骤3:解析数据(可变步骤,子类实现)
List<?> dataList = parseData(fileContent);
// 步骤4:数据入库(固定步骤)
saveData(dataList);
// 步骤5:导入完成通知(固定步骤)
notifyComplete();
}
// 固定步骤1:读取文件
private String readFile(String filePath) {
System.out.println("读取文件:" + filePath);
// 模拟读取文件内容
return "模拟文件内容:id,name,age";
}
// 可变步骤2:校验数据(抽象方法,子类实现)
protected abstract boolean validateData(String fileContent);
// 可变步骤3:解析数据(抽象方法,子类实现)
protected abstract List<?> parseData(String fileContent);
// 固定步骤4:数据入库
private void saveData(List<?> dataList) {
System.out.println("数据入库,共 " + dataList.size() + " 条数据");
}
// 固定步骤5:导入完成通知
private void notifyComplete() {
System.out.println("数据导入完成");
}
}
// 2. 具体子类1:用户数据导入(实现可变步骤)
public class UserDataImport extends DataImportTemplate {
@Override
protected boolean validateData(String fileContent) {
System.out.println("校验用户数据:检查是否包含id、name、age字段");
// 模拟校验逻辑
return fileContent.contains("id") && fileContent.contains("name") && fileContent.contains("age");
}
@Override
protected List<?> parseData(String fileContent) {
System.out.println("解析用户数据:将文件内容转换为User对象列表");
// 模拟解析逻辑
List<User> userList = new ArrayList<>();
userList.add(new User(1L, "张三", 25));
return userList;
}
}
// 3. 具体子类2:商品数据导入(实现可变步骤)
public class ProductDataImport extends DataImportTemplate {
@Override
protected boolean validateData(String fileContent) {
System.out.println("校验商品数据:检查是否包含id、productName、price字段");
// 模拟校验逻辑
return fileContent.contains("id") && fileContent.contains("productName") && fileContent.contains("price");
}
@Override
protected List<?> parseData(String fileContent) {
System.out.println("解析商品数据:将文件内容转换为Product对象列表");
// 模拟解析逻辑
List<Product> productList = new ArrayList<>();
productList.add(new Product(100L, "手机", 2999.0));
return productList;
}
}
// 4. 实战使用
public class TemplateMethodTest {
public static void main(String[] args) {
// 导入用户数据
DataImportTemplate userImport = new UserDataImport();
userImport.importData("D:/user.csv");
System.out.println("-------------------");
// 导入商品数据
DataImportTemplate productImport = new ProductDataImport();
productImport.importData("D:/product.csv");
}
}
// 辅助实体类
class User {
private Long id;
private String name;
private Integer age;
// 构造方法、getter/setter 省略
}
class Product {
private Long id;
private String productName;
private Double price;
// 构造方法、getter/setter 省略
}
5.3.4 注意事项
-
模板方法用 final 修饰:核心流程必须固定,防止子类修改流程顺序,破坏流程规范。
-
可变步骤用 protected 修饰:子类仅需重写可变步骤,无需关注固定步骤,降低子类实现成本。
-
避免模板类过于复杂:若核心流程步骤过多,建议拆分模板类,遵循单一职责原则,避免一个模板类管理多个不相关的流程。
5.4 责任链模式(Chain of Responsibility Pattern)
5.4.1 设计思想与架构定位
核心思想:将多个处理者(Handler)组成一条责任链,当请求到来时,请求会沿着责任链依次传递,直到有一个处理者能够处理该请求,处理完成后终止传递;若所有处理者都无法处理,可设置默认处理逻辑。
架构定位:解决“请求需要多个处理者依次处理”的问题,适用于请求处理流程不确定、需要动态调整处理顺序的场景(如权限校验、请求过滤、异常处理),实现处理者的解耦,便于新增和删除处理者。
5.4.2 实战场景
-
接口权限校验链:请求到来时,依次经过“登录校验→角色校验→接口权限校验→参数校验”,任意一步校验失败,直接返回错误。
-
请求日志过滤链:请求经过“请求日志打印→敏感参数过滤→请求频率限制”,每一步处理完成后,传递给下一个处理者。
-
Spring 中的应用:SpringMVC 的拦截器链(HandlerInterceptor)、Spring Security 的过滤器链,均基于责任链模式设计。
5.4.3 实战代码(接口权限校验链场景)
// 1. 抽象处理者:权限校验接口
public interface PermissionHandler {
// 设置下一个处理者(构建责任链)
void setNextHandler(PermissionHandler nextHandler);
// 处理请求(校验逻辑)
boolean handleRequest(String userId, String interfaceId);
}
// 2. 具体处理者1:登录校验(第一个处理者)
public class LoginHandler implements PermissionHandler {
private PermissionHandler nextHandler;
@Override
public void setNextHandler(PermissionHandler nextHandler) {
this.nextHandler = nextHandler;
}
@Override
public boolean handleRequest(String userId, String interfaceId) {
System.out.println("执行登录校验:检查用户 " + userId + " 是否登录");
// 模拟登录校验:userId不为空则登录成功
if (userId == null || userId.isEmpty()) {
System.out.println("登录校验失败:用户未登录");
return false;
}
// 登录校验通过,传递给下一个处理者
if (nextHandler != null) {
return nextHandler.handleRequest(userId, interfaceId);
}
// 没有下一个处理者,校验通过
return true;
}
}
// 3. 具体处理者2:角色校验(第二个处理者)
public class RoleHandler implements PermissionHandler {
private PermissionHandler nextHandler;
@Override
public void setNextHandler(PermissionHandler nextHandler) {
this.nextHandler = nextHandler;
}
@Override
public boolean handleRequest(String userId, String interfaceId) {
System.out.println("执行角色校验:检查用户 " + userId + " 是否有对应角色");
// 模拟角色校验:userId为1001的用户有角色权限
if (!"1001".equals(userId)) {
System.out.println("角色校验失败:用户无对应角色权限");
return false;
}
// 角色校验通过,传递给下一个处理者
if (nextHandler != null) {
return nextHandler.handleRequest(userId, interfaceId);
}
return true;
}
}
// 4. 具体处理者3:接口权限校验(第三个处理者)
public class InterfacePermissionHandler implements PermissionHandler {
private PermissionHandler nextHandler;
@Override
public void setNextHandler(PermissionHandler nextHandler) {
this.nextHandler = nextHandler;
}
@Override
public boolean handleRequest(String userId, String interfaceId) {
System.out.println("执行接口权限校验:检查用户 " + userId + " 是否有权访问接口 " + interfaceId);
// 模拟接口权限校验:接口id为"api/user"的接口允许访问
if (!"api/user".equals(interfaceId)) {
System.out.println("接口权限校验失败:用户无权访问该接口");
return false;
}
// 接口权限校验通过,传递给下一个处理者(若有)
if (nextHandler != null) {
return nextHandler.handleRequest(userId, interfaceId);
}
return true;
}
}
// 5. 责任链构建者(简化责任链构建)
public class PermissionHandlerChainBuilder {
// 存储所有处理者
private List<PermissionHandler> handlers = new ArrayList<>();
// 添加处理者
public PermissionHandlerChainBuilder addHandler(PermissionHandler handler) {
handlers.add(handler);
return this;
}
// 构建责任链(依次设置下一个处理者)
public PermissionHandler build() {
for (int i = 0; i < handlers.size() - 1; i++) {
handlers.get(i).setNextHandler(handlers.get(i + 1));
}
// 返回第一个处理者(责任链入口)
return handlers.get(0);
}
}
// 6. 实战使用
public class ChainOfResponsibilityTest {
public static void main(String[] args) {
// 1. 构建责任链
PermissionHandler chain = new PermissionHandlerChainBuilder()
.addHandler(new LoginHandler())
.addHandler(new RoleHandler())
.addHandler(new InterfacePermissionHandler())
.build();
// 2. 测试1:所有校验通过
System.out.println("=== 测试1:所有校验通过 ===");
boolean result1 = chain.handleRequest("1001", "api/user");
System.out.println("校验结果:" + (result1 ? "通过" : "失败"));
// 3. 测试2:角色校验失败
System.out.println("\n=== 测试2:角色校验失败 ===");
boolean result2 = chain.handleRequest("1002", "api/user");
System.out.println("校验结果:" + (result2 ? "通过" : "失败"));
// 4. 测试3:接口权限校验失败
System.out.println("\n=== 测试3:接口权限校验失败 ===");
boolean result3 = chain.handleRequest("1001", "api/order");
System.out.println("校验结果:" + (result3 ? "通过" : "失败"));
}
}
5.4.4 注意事项
-
责任链顺序:处理者的顺序至关重要,需根据业务逻辑合理排列(如先登录校验,再角色校验),避免顺序错误导致校验逻辑异常。
-
避免责任链过长:若处理者过多,会导致请求处理效率降低,建议拆分责任链,按功能模块划分多个小链条。
-
设置默认处理:若所有处理者都无法处理请求,需设置默认处理逻辑,避免请求“无响应”。
5.5 中介者模式(Mediator Pattern)
5.5.1 设计思想与架构定位
核心思想:定义一个中介者对象,封装多个对象(同事对象)之间的交互逻辑,同事对象不再直接相互通信,而是通过中介者进行通信,降低同事对象之间的耦合。
架构定位:解决“多个对象相互依赖、通信复杂”的问题,适用于对象数量多、交互频繁的场景(如系统组件通信、团队协作管理),将“多对多”的复杂依赖,转化为“一对多”的简单依赖,提升系统的可维护性。
5.5.2 实战场景
-
系统组件通信:系统中的订单组件、库存组件、支付组件、物流组件,不再直接相互调用,而是通过中介者(订单中介者)协调通信,实现订单创建、库存扣减、支付、物流的协同。
-
聊天室功能:多个用户(同事对象)之间发送消息,不再直接点对点发送,而是通过聊天室(中介者)转发消息,简化用户间的通信逻辑。
-
Spring 中的应用:Spring 容器作为中介者,管理所有 Bean 之间的依赖关系,Bean 之间通过容器进行依赖注入和通信,无需直接关联。
5.5.3 实战代码(订单协同场景)
// 1. 抽象中介者:定义中介者的核心方法(协调同事对象)
public interface OrderMediator {
// 注册同事对象
void registerColleague(Colleague colleague);
// 转发消息(协调同事对象交互)
void relayMessage(Colleague sender, String message);
}
// 2. 抽象同事对象:定义同事对象的通用方法
public abstract class Colleague {
// 持有中介者对象
protected OrderMediator mediator;
public Colleague(OrderMediator mediator) {
this.mediator = mediator;
// 注册到中介者
mediator.registerColleague(this);
}
// 发送消息(通过中介者转发)
public abstract void sendMessage(String message);
// 接收消息(处理中介者转发的消息)
public abstract void receiveMessage(String message);
}
// 3. 具体同事1:订单组件
public class OrderColleague extends Colleague {
public OrderColleague(OrderMediator mediator) {
super(mediator);
}
@Override
public void sendMessage(String message) {
System.out.println("订单组件发送消息:" + message);
// 通过中介者转发消息
mediator.relayMessage(this, message);
}
@Override
public void receiveMessage(String message) {
System.out.println("订单组件接收消息:" + message);
// 处理消息(如订单状态更新)
if ("库存扣减成功".equals(message)) {
System.out.println("订单组件:更新订单状态为「已付款」");
} else if ("物流已发货".equals(message)) {
System.out.println("订单组件:更新订单状态为「已发货」");
}
}
// 订单创建方法(触发协同流程)
public void createOrder(Long orderId) {
System.out.println("订单组件:创建订单 " + orderId);
// 发送消息,请求库存扣减
sendMessage("请求扣减订单 " + orderId + " 的库存");
}
}
// 4. 具体同事2:库存组件
public class StockColleague extends Colleague {
public StockColleague(OrderMediator mediator) {
super(mediator);
}
@Override
public void sendMessage(String message) {
System.out.println("库存组件发送消息:" + message);
mediator.relayMessage(this, message);
}
@Override
public void receiveMessage(String message) {
System.out.println("库存组件接收消息:" + message);
// 处理消息(扣减库存)
if (message.startsWith("请求扣减订单")) {
System.out.println("库存组件:扣减库存成功");
// 发送消息,通知支付组件进行支付
sendMessage("库存扣减成功,请进行支付");
}
}
}
// 5. 具体同事3:支付组件
public class PaymentColleague extends Colleague {
public PaymentColleague(OrderMediator mediator) {
super(mediator);
}
@Override
public void sendMessage(String message) {
System.out.println("支付组件发送消息:" + message);
mediator.relayMessage(this, message);
}
@Override
public void receiveMessage(String message) {
System.out.println("支付组件接收消息:" + message);
// 处理消息(进行支付)
if ("库存扣减成功,请进行支付".equals(message)) {
System.out.println("支付组件:支付成功");
// 发送消息,通知物流组件发货
sendMessage("支付成功,请安排物流发货");
}
}
}
// 6. 具体同事4:物流组件
public class LogisticsColleague extends Colleague {
public LogisticsColleague(OrderMediator mediator) {
super(mediator);
}
@Override
public void sendMessage(String message) {
System.out.println("物流组件发送消息:" + message);
mediator.relayMessage(this, message);
}
@Override
public void receiveMessage(String message) {
System.out.println("物流组件接收消息:" + message);
// 处理消息(安排发货)
if ("支付成功,请安排物流发货".equals(message)) {
System.out.println("物流组件:安排发货");
// 发送消息,通知订单组件更新状态
sendMessage("物流已发货");
}
}
}
// 7. 具体中介者:订单中介者(实现协调逻辑)
public class OrderMediatorImpl implements OrderMediator {
// 存储所有同事对象
private List<Colleague> colleagues = new ArrayList<>();
@Override
public void registerColleague(Colleague colleague) {
colleagues.add(colleague);
}
@Override
public void relayMessage(Colleague sender, String message) {
// 转发消息给所有非发送者的同事对象(可根据业务调整转发范围)
for (Colleague colleague : colleagues) {
if (!colleague.equals(sender)) {
colleague.receiveMessage(message);
}
}
}
}
// 8. 实战使用
public class MediatorTest {
public static void main(String[] args) {
// 1. 创建中介者
OrderMediator mediator = new OrderMediatorImpl();
// 2. 创建同事对象(自动注册到中介者)
OrderColleague orderColleague = new OrderColleague(mediator);
StockColleague stockColleague = new StockColleague(mediator);
PaymentColleague paymentColleague = new PaymentColleague(mediator);
LogisticsColleague logisticsColleague = new LogisticsColleague(mediator);
// 3. 触发订单创建,启动协同流程
System.out.println("=== 启动订单协同流程 ===");
orderColleague.createOrder(1001L);
}
}
5.5.4 注意事项
-
中介者职责单一:中介者仅负责协调同事对象的通信,不负责具体的业务逻辑,避免中介者过于庞大(避免“中介者臃肿”问题)。
-
避免过度依赖中介者:同事对象之间的简单通信,无需通过中介者,仅在多对象复杂交互时使用,避免过度设计。
-
同事对象解耦:同事对象仅依赖中介者,不依赖其他同事对象,便于单独修改和扩展。
第六章 设计模式选型与架构实践
前面章节详细讲解了常用设计模式的理论与实战,本章聚焦“架构选型”核心,结合不同业务场景,给出设计模式的选型建议、组合使用技巧,以及企业级项目中的落地注意事项,帮助开发者将设计模式真正融入架构设计,而非单纯的代码技巧。
6.1 设计模式选型核心原则
设计模式的选型没有“最优解”,只有“最适配”,核心遵循以下4个原则,避免为了使用模式而使用模式(过度设计):
-
贴合业务场景:选型前先明确业务需求和架构痛点,比如“对象创建复杂”用创建型模式,“模块交互复杂”用行为型模式,“接口不兼容”用适配器模式。
-
遵循设计原则:选型必须符合面向对象七大设计原则,优先保证架构的高内聚、低耦合,避免选型后导致代码冗余、耦合升高。
-
兼顾可维护性:选择团队熟悉、易于理解的模式,避免使用过于复杂的模式(如抽象工厂模式、备忘录模式),除非业务场景确实需要。
-
避免过度设计:简单场景无需使用复杂模式,比如“只有一个支付方式”无需使用工厂模式,直接使用普通类即可;“对象属性少”无需使用建造者模式。
6.2 常见业务场景与模式选型对应表
| 业务场景 | 推荐设计模式 | 选型说明 |
|---|---|---|
| 全局工具类、连接池、配置类 | 单例模式 | 确保全局唯一实例,减少资源浪费,贴合无状态对象的复用需求 |
| 多种支付方式、第三方组件切换 | 工厂方法模式、策略模式 | 工厂模式负责对象创建,策略模式负责算法/逻辑切换,组合使用更灵活 |
| 复杂对象创建(多属性、可配置) | 建造者模式 | 拆解创建步骤,支持灵活配置对象属性,提升代码可读性 |
| 日志、权限、缓存等横切逻辑 | 代理模式(Spring AOP) | 不修改原有代码,统一添加横切逻辑,符合开放-封闭原则 |
| 第三方接口集成、旧系统改造 | 适配器模式 | 解决接口不兼容问题,无需修改原有代码,快速实现集成 |
| 菜单、部门、文件目录等树形结构 | 组合模式 | 统一操作单个对象和组合对象,简化树形结构的遍历和管理 |
| 事件通知、状态同步(如用户注册后多操作) | 观察者模式 | 实现发布-订阅解耦,被观察者状态变化时,所有观察者同步响应 |
| 流程固定、步骤可变(如数据导入、审批) | 模板方法模式 | 统一流程规范,子类重写可变步骤,提升代码复用性 |
| 多处理者依次处理请求(如权限校验链) | 责任链模式 | 动态调整处理顺序,新增/删除处理者无需修改原有代码 |
| 多对象复杂交互(如订单、库存、支付协同) | 中介者模式 | 降低对象间的耦合,通过中介者协调通信,简化交互逻辑 |
6.3 设计模式组合使用技巧
实际架构设计中,单一模式往往无法满足复杂业务需求,需要多种模式组合使用,以下是最常用的4种组合方式,结合实战场景说明:
6.3.1 组合1:工厂模式 + 策略模式(最常用)
场景:多种支付方式(微信、支付宝、银联),需要灵活切换支付方式,同时统一创建支付对象。
组合逻辑:工厂模式负责创建不同类型的支付对象(微信支付、支付宝支付),策略模式负责定义不同支付方式的支付算法,客户端通过工厂获取支付对象,通过策略模式切换支付逻辑,二者结合实现“创建+切换”的双重灵活。
核心优势:新增支付方式时,只需新增工厂类和策略类,无需修改客户端代码,完全符合开放-封闭原则。
6.3.2 组合2:代理模式 + 单例模式
场景:全局日志工具类(单例),需要在日志输出前后添加耗时统计、参数打印逻辑。
组合逻辑:单例模式确保日志工具类全局唯一,代理模式为日志工具类创建代理对象,在代理对象中添加耗时统计、参数打印等横切逻辑,既保证资源复用,又实现功能扩展。
核心优势:不修改单例类的原有代码,通过代理扩展功能,避免单例类职责过重。
6.3.3 组合3:模板方法模式 + 策略模式
场景:数据导入流程(固定流程:读取→校验→解析→入库),不同数据类型的校验、解析逻辑不同,且解析算法可灵活切换。
组合逻辑:模板方法模式定义数据导入的固定流程,策略模式定义不同的解析算法(如JSON解析、CSV解析),将解析步骤作为策略,子类可根据数据类型选择不同的解析策略,实现流程与算法的解耦。
核心优势:既统一流程规范,又支持算法灵活切换,提升代码的可扩展性。
6.3.4 组合4:观察者模式 + 中介者模式
场景:系统事件通知(如订单状态变更),多个组件(订单、库存、物流、消息)需要同步响应,且组件间交互复杂。
组合逻辑:中介者模式协调多个组件的通信,观察者模式实现事件的发布与订阅,订单组件作为被观察者发布状态变更事件,中介者作为观察者接收事件,再协调其他组件(库存、物流)响应,避免组件间直接耦合。
核心优势:解决多组件复杂交互的问题,同时实现事件的同步响应,提升架构的可维护性。
6.4 企业级落地注意事项
-
避免过度设计:这是最核心的注意事项,不要为了使用设计模式而强行套用,比如简单的对象创建无需使用工厂模式,简单的接口扩展无需使用装饰器模式。
-
结合框架落地:充分利用 Spring 生态的特性,简化模式实现,比如用 Spring 的依赖注入管理工厂、策略对象,用 Spring AOP 实现代理模式,减少手动编码。
-
团队统一规范:明确项目中设计模式的使用规范,避免同一功能使用不同的模式,导致代码风格混乱(如统一用双重检查锁实现单例,统一用策略模式处理算法切换)。
-
注重代码可读性:设计模式的实现要简洁明了,添加必要的注释,说明模式的使用目的和核心逻辑,避免其他开发者难以理解。
-
适配架构迭代:设计模式的选型要考虑架构的长期迭代,比如选择工厂模式、策略模式,便于后续新增功能;避免选择耦合度高的模式(如桥接模式),增加迭代成本。
第七章 总结与拓展
7.1 核心总结
设计模式的本质是“面向对象设计原则的落地”,其核心价值是“解耦、复用、可维护”,而非单纯的代码技巧。本手册从架构视角出发,梳理了创建型、结构型、行为型三大类常用设计模式,每一种模式都围绕“解决特定架构问题”展开,结合企业级实战场景,提供了可直接复用的代码方案和选型建议。
核心要点回顾:
-
创建型模式:聚焦“对象创建”,解决创建逻辑与业务逻辑的耦合,核心是“面向抽象创建对象”。
-
结构型模式:聚焦“模块组合”,优化系统结构,解决模块间耦合过高的问题,核心是“组合复用”。
-
行为型模式:聚焦“对象协作”,规范对象间的通信方式,解决交互复杂的问题,核心是“行为解耦”。
-
选型核心:贴合业务场景、遵循设计原则、避免过度设计,优先选择简单、易维护的模式。
7.2 拓展方向
设计模式的学习是一个“理论→实践→总结”的循环过程,后续可从以下3个方向进一步拓展,深化对设计模式的理解和应用:
-
源码中的设计模式:阅读 Spring、MyBatis、OkHttp 等主流框架的源码,分析框架中设计模式的应用(如 Spring 中的单例、代理、观察者模式),理解模式在大型架构中的落地思路。
-
复杂场景的模式组合:针对更复杂的业务场景(如微服务架构、分布式系统),探索多种设计模式的组合使用(如工厂+策略+代理),提升架构设计能力。
-
设计模式与架构模式的结合:将设计模式与微服务架构、DDD(领域驱动设计)、事件驱动架构等结合,比如用聚合模式(组合模式)实现 DDD 中的聚合根,用观察者模式实现事件驱动。
7.3 寄语
设计模式不是“银弹”,无法解决所有架构问题,但它是开发者提升架构设计能力的重要工具。学习设计模式的关键,不是死记硬背代码,而是理解其设计思想和解决的架构痛点,在实际项目中灵活运用,平衡“代码质量”与“开发效率”,打造高可用、可维护、可扩展的系统架构。