# 设计模式
学习目标:
- 先知道设计模式在解决什么问题
- 先记住三大类和几个高频模式
- 先把 SOLID 当成看代码的几个提醒
一、设计模式简介
设计模式不是语法,也不是框架,而是处理常见设计问题的成熟经验。
它主要帮你处理这些情况:
- 代码越写越乱
- 模块之间耦合太深
- 新需求一来只能硬改旧代码
- 过一段时间连自己都不想看
先记住三大类就够了:
| 类别 | 关注点 |
|---|---|
| 创建型 | 对象怎么创建 |
| 结构型 | 类和对象怎么组合 |
| 行为型 | 对象之间怎么协作 |
高频模式:
- 创建型:单例、工厂、建造者
- 结构型:代理
- 行为型:责任链
看后面的每个模式时,带着这三个问题就够用了:
- 它解决什么问题?
- 不用它时,代码容易乱在哪里?
- 用了它之后,好处是什么?
二、设计模式原则(SOLID)
设计模式背后通常有一套更稳定的设计思路,最常见的就是 SOLID。
这一版先记成五个提醒就行:
| 原则 | 一句话理解 |
|---|---|
| S 单一职责 | 一个类别太杂 |
| O 开闭原则 | 新功能尽量靠扩展 |
| L 里氏替换 | 子类别把父类语义改坏 |
| I 接口隔离 | 接口别又大又全 |
| D 依赖倒置 | 尽量依赖抽象 |
简单理解:
- 单一职责:一个类里别什么都塞
- 开闭原则:加功能优先考虑新增,少直接改老代码
- 里氏替换:子类要真的能当父类来用
- 接口隔离:谁需要什么能力,就给谁那部分接口
- 依赖倒置:调用方别被某个具体实现绑死
不用一开始就背得很细,能在代码里看出下面这些问题就够了:
- 类是不是太胖了
- 继承是不是有点别扭
- 接口是不是塞了太多东西
- 实现是不是绑得太死了
原则更像方向,模式更像做法。后面学单例、工厂、代理、责任链时,再反过来体会这些原则就行。
三、创建型模式
创建型模式提供了创建对象的机制,能够提升已有代码的灵活性和可复用性。
3.1 单例模式(Singleton)
3.1.1 模式定义
单例是一种创建型设计模式,让你保证一个类只有一个实例对象,并提供了一个访问该实例对象的全局节点。

对应的UML类图:

适用场景:
- 数据库连接池
- 配置管理器
- 线程池
- 缓存对象
3.1.2 实现方式一:饿汉模式
在类加载的过程中初始化私有静态实例对象,保证了线程安全性。
package com.cskaoyan.pattern.singleton;
/**
* 饿汉式单例模式
* 特点:类加载时即创建实例,线程安全,但不支持懒加载
*/
public class Singleton1 {
// 1. 创建私有静态实例对象(类加载时初始化)
private static final Singleton1 INSTANCE = new Singleton1();
// 2. 私有化构造函数,防止外部实例化
private Singleton1() {}
// 3. 提供全局访问点
public static Singleton1 getInstance() {
return INSTANCE;
}
}特点分析:
| 优点 | 缺点 |
|---|---|
| 线程安全(类加载机制保证) | 不支持懒加载 |
| 实现简单,无需加锁 | 如果对象较大且一直未使用,浪费内存 |
| 获取对象速度快 | 无法传递参数初始化 |
3.1.3 实现方式二:懒汉模式(线程不安全)
package com.cskaoyan.pattern.singleton;
/**
* 懒汉式单例模式 - 线程不安全版本
* 特点:支持懒加载,但多线程环境下可能创建多个实例
*/
public class Singleton2 {
private static Singleton2 instance;
// 私有化构造函数
private Singleton2() {}
// 判断当前对象是否已经被创建
public static Singleton2 getInstance() {
if (instance == null) { // 线程A执行到这里被切换
instance = new Singleton2(); // 线程B执行完,线程A再次执行,又创建一个对象
}
return instance;
}
}线程安全问题演示:
使用1000个线程并发创建对象,会发现对象的hashCode不同。

问题原因:
线程A执行 if(instance == null) 判断通过 → 线程切换
线程B执行 if(instance == null) 判断通过 → 创建instance对象
线程A恢复执行 → 再次创建instance对象3.1.4 实现方式三:懒汉模式(线程安全)
package com.cskaoyan.pattern.singleton;
/**
* 懒汉式单例模式 - 线程安全版本
* 特点:线程安全,但并发性能较差
*/
public class Singleton3 {
private static Singleton3 instance;
// 私有化构造函数
private Singleton3() {}
// 引入synchronized,保证多线程模式下实例对象的唯一性
public static synchronized Singleton3 getInstance() {
if (instance == null) {
instance = new Singleton3();
}
return instance;
}
}性能问题:
- 每次调用
getInstance()都需要获取锁 - 实际上只需要针对最开始创建实例时加锁
- 实例创建完成后,后续调用应该直接返回,无需加锁
3.1.5 实现方式四:双重检查(Double Check)
package com.cskaoyan.pattern.singleton;
/**
* 懒汉式单例模式 - 双重检查锁定(DCL)
* 特点:懒加载 + 线程安全 + 高性能
*/
public class Singleton4 {
// volatile关键字禁止指令重排序,保证可见性
private static volatile Singleton4 instance;
// 私有化构造函数
private Singleton4() {}
public static Singleton4 getInstance() {
// 第一次检查:避免不必要的加锁
if (instance == null) {
synchronized (Singleton4.class) {
// 第二次检查:防止多个线程通过第一次检查
if (instance == null) {
instance = new Singleton4();
}
}
}
return instance;
}
}为什么需要双重检查?
线程A执行外层if判断通过 → 线程切换
线程B执行外层if判断通过 → 获取锁 → 创建对象 → 释放锁
线程A恢复执行 → 获取锁 → 如果没有内层if,又会创建一个对象!volatile关键字的作用:
instance = new Singleton4()不是原子操作,可能指令重排序- 重排序后可能导致其他线程获取到未完全初始化的对象
volatile禁止指令重排序,保证对象的正确发布
3.1.6 实现方式五:静态内部类
/**
* 静态内部类实现单例
* 特点:懒加载 + 线程安全 + 代码简洁
* JVM保证静态内部类的初始化是线程安全的
*/
public class Singleton5 {
// 私有化构造函数
private Singleton5() {}
// 静态内部类
private static class SingletonHolder {
private static final Singleton5 INSTANCE = new Singleton5();
}
public static Singleton5 getInstance() {
return SingletonHolder.INSTANCE;
}
}原理:
- 静态内部类
SingletonHolder在第一次被引用时才加载 - JVM的类加载机制保证类初始化是线程安全的
- 兼顾了懒汉式的延迟加载和饿汉式的线程安全
3.1.7 实现方式六:枚举
/**
* 枚举实现单例
* 特点:最简单、最安全的实现方式
* 天然防止反射攻击和序列化问题
*/
public enum Singleton6 {
INSTANCE;
// 可以添加业务方法
public void doSomething() {
System.out.println("Doing something...");
}
}
// 使用
Singleton6.INSTANCE.doSomething();枚举的优势:
- ✅ 线程安全(JVM保证)
- ✅ 防止反射攻击(反射无法创建枚举实例)
- ✅ 防止序列化破坏(枚举序列化有特殊处理)
- ✅ 代码极简
3.1.8 单例模式实现方式对比
| 实现方式 | 线程安全 | 懒加载 | 性能 | 复杂度 | 推荐度 |
|---|---|---|---|---|---|
| 饿汉式 | ✅ | ❌ | ⭐⭐⭐ | ⭐ | ⭐⭐⭐ |
| 懒汉式(不安全) | ❌ | ✅ | ⭐⭐⭐ | ⭐ | ❌ |
| 懒汉式(安全) | ✅ | ✅ | ⭐ | ⭐ | ⭐ |
| 双重检查 | ✅ | ✅ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
| 静态内部类 | ✅ | ✅ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| 枚举 | ✅ | ❌ | ⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐⭐ |
【常见问题】
| 问题 | 解答 |
|---|---|
| 单例模式有什么用? | 节省资源(内存、数据库连接等),控制并发访问(如连接池) |
| 如何破坏单例? | 反射调用私有构造函数、序列化/反序列化 |
小结
- 核心目标:保证一个类只有一个实例,并提供全局访问点
- 推荐实现:枚举最简单,静态内部类更均衡
- 需要注意:反射、序列化和多线程带来的破坏风险
3.2 工厂模式(Factory)
3.2.1 模式定义
工厂模式是创建型模式中最常用的一种,其核心思想是将对象的创建和使用分离,通过专门的工厂类来创建对象。
工厂模式分为三种:
- 简单工厂模式(Simple Factory)
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
3.2.2 简单工厂模式
定义:只需要一个工厂(函数),传入不同的参数,返回不同的产品(实例)。
案例 - 特斯拉汽车工厂:
// 1. 定义产品抽象类
public abstract class Tesla {
protected String name;
public Tesla(String name) {
this.name = name;
}
public void run() {
System.out.println(name + "在路上跑");
}
}
// 2. 定义具体产品
public class Model3 extends Tesla {
public Model3() {
super("Model 3");
}
}
public class ModelS extends Tesla {
public ModelS() {
super("Model S");
}
}
public class ModelY extends Tesla {
public ModelY() {
super("Model Y");
}
}不使用工厂 - 客户端直接创建:
Scanner scanner = new Scanner(System.in);
String keyword = scanner.nextLine();
Tesla tesla = null;
// 客户端耦合了具体创建逻辑
switch (keyword) {
case "model3":
tesla = new Model3();
break;
case "modely":
tesla = new ModelY();
break;
case "models":
tesla = new ModelS();
break;
default:
tesla = new Tesla("未知车辆") {
@Override
public void run() {
System.out.println(name + "路上请注意,道路千万条,安全第一条");
}
};
}
tesla.run();使用简单工厂 - 封装创建逻辑:
/**
* 简单工厂类
* 封装了对象创建的细节,客户端只需传递参数
*/
public class SimpleTeslaFactory {
public static Tesla create(String keyword) {
switch (keyword.toLowerCase()) {
case "model3":
return new Model3();
case "modely":
return new ModelY();
case "models":
return new ModelS();
default:
return new Tesla("未知车辆") {
@Override
public void run() {
System.out.println(name + "路上请注意,道路千万条,安全第一条");
}
};
}
}
}客户端代码变得简洁:
Scanner scanner = new Scanner(System.in);
String keyword = scanner.nextLine();
Tesla tesla = SimpleTeslaFactory.create(keyword); // 一行代码获取对象
tesla.run();简单工厂优缺点:
| 优点 | 缺点 |
|---|---|
| 封装对象创建逻辑 | 增加新产品时需要修改工厂类,违反开闭原则 |
| 降低客户端与具体产品的耦合 | 工厂类职责过重 |
| 代码复用性高 | 产品类型过多时,工厂方法过于复杂 |
3.2.3 工厂方法模式
定义:定义一个创建对象的接口,让子类决定实例化哪个类。工厂方法将对象的创建延迟到子类。

适用场景:
- 需要生产多种同类产品
- 每种类型的创建逻辑相对复杂
- 需要扩展新的产品类型而不修改现有代码
案例 - 特斯拉生产车间:
// 1. 定义工厂接口
public interface TeslaFactory {
Tesla createTesla();
}
// 2. 各车型对应的具体工厂
public class Model3Factory implements TeslaFactory {
@Override
public Tesla createTesla() {
return new Model3();
}
}
public class ModelYFactory implements TeslaFactory {
@Override
public Tesla createTesla() {
return new ModelY();
}
}
public class ModelSFactory implements TeslaFactory {
@Override
public Tesla createTesla() {
return new ModelS();
}
}
public class ModelXFactory implements TeslaFactory {
@Override
public Tesla createTesla() {
return new ModelX();
}
}客户端代码:
public class OrderTesla {
// 使用Map缓存工厂实例
private static Map<String, TeslaFactory> factoryMap = new HashMap<>();
static {
factoryMap.put("modelx", new ModelXFactory());
factoryMap.put("modely", new ModelYFactory());
factoryMap.put("models", new ModelSFactory());
factoryMap.put("model3", new Model3Factory());
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String keyword = scanner.nextLine();
TeslaFactory factory = factoryMap.get(keyword.toLowerCase());
if (factory != null) {
Tesla tesla = factory.createTesla();
tesla.run();
}
}
}简单工厂 vs 工厂方法:
| 对比项 | 简单工厂 | 工厂方法 |
|---|---|---|
| 复杂度 | 简单 | 较复杂 |
| 扩展性 | 修改工厂类 | 新增工厂类 |
| 产品数量 | 适合少量产品 | 适合大量产品 |
| 开闭原则 | 违反 | 遵循 |
3.2.4 抽象工厂模式
定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
与工厂方法的区别:
- 工厂方法:生产单一产品
- 抽象工厂:生产产品族(一系列相关产品)
案例 - 智能家居产品族:
假设有两个家电厂商(小米、海尔),都有TV和冰箱产品线。用户倾向于选择同一品牌的产品。

类图关系:

// 1. 定义产品族接口
public interface TV {
void play();
}
public interface Freezer {
void freeze();
}
// 2. 定义抽象工厂
public abstract class AbstractFurnitureFactory {
public abstract TV createTV();
public abstract Freezer createFreezer();
}
// 3. 小米产品族
public class MiTV implements TV {
@Override
public void play() {
System.out.println("小米电视播放中...");
}
}
public class MiFreezer implements Freezer {
@Override
public void freeze() {
System.out.println("小米冰箱制冷中...");
}
}
public class MiFurnitureFactory extends AbstractFurnitureFactory {
@Override
public TV createTV() {
return new MiTV();
}
@Override
public Freezer createFreezer() {
return new MiFreezer();
}
}
// 4. 海尔产品族
public class HaierTV implements TV {
@Override
public void play() {
System.out.println("海尔电视播放中...");
}
}
public class HaierFreezer implements Freezer {
@Override
public void freeze() {
System.out.println("海尔冰箱制冷中...");
}
}
public class HaierFurnitureFactory extends AbstractFurnitureFactory {
@Override
public TV createTV() {
return new HaierTV();
}
@Override
public Freezer createFreezer() {
return new HaierFreezer();
}
}客户端代码:
public class OrderFurniture {
public static void main(String[] args) {
// 选择小米全屋智能
AbstractFurnitureFactory factory = new MiFurnitureFactory();
TV tv = factory.createTV();
Freezer freezer = factory.createFreezer();
tv.play();
freezer.freeze();
// 确保是同一品牌
System.out.println("tv instanceof MiTV = " + (tv instanceof MiTV));
System.out.println("freezer instanceof MiFreezer = " + (freezer instanceof MiFreezer));
}
}抽象工厂优缺点:
| 优点 | 缺点 |
|---|---|
| 确保产品族的一致性 | 扩展新产品困难(需要修改抽象工厂接口) |
| 隔离了具体类的生成 | 系统复杂度增加 |
| 符合开闭原则(扩展产品族) |
3.2.6 【常见问题】
| 问题 | 解答 |
|---|---|
| 三种工厂模式怎么选? | 产品少且固定用简单工厂;产品多且需扩展用工厂方法;有产品族概念用抽象工厂 |
| 工厂模式和单例的关系? | 工厂类本身通常用单例实现;工厂生产的对象可以是单例也可以是多例 |
小结
- 简单工厂:一个工厂方法创建多种产品,适合产品少的场景
- 工厂方法:一个工厂创建一种产品,适合产品多、需要扩展的场景
- 抽象工厂:创建一组配套产品,适合有产品族概念的场景
- 核心思想:把对象创建和对象使用分开,降低耦合
3.3 建造者模式(Builder)
3.3.1 模式定义
建造者模式也叫生成器模式,是一种分步骤创建复杂对象的设计模式。该模式允许使用相同的创建代码生成不同类型和形式的对象。

适用场景:
- 对象构建步骤复杂,需要分步完成
- 需要构建不同表示的相同对象
- 需要避免构造方法参数过多(" telescoping constructor"问题)
3.3.2 经典实现
案例 - 手机组装:
手机由多个组件构成:屏幕、电池、摄像头、系统、颜色等。
// 1. 定义产品类
@Data // Lombok注解,自动生成getter/setter/toString
public class Phone {
private String battery; // 电池
private String screen; // 屏幕
private String os; // 操作系统
private String camera; // 摄像头
private String color; // 颜色
}
// 2. 定义建造者类
public class PhoneBuilder {
private Phone phone = new Phone();
// 链式调用设置属性
public PhoneBuilder color(String color) {
this.phone.setColor(color);
return this;
}
public PhoneBuilder battery(String battery) {
this.phone.setBattery(battery);
return this;
}
public PhoneBuilder screen(String screen) {
this.phone.setScreen(screen);
return this;
}
public PhoneBuilder os(String os) {
this.phone.setOs(os);
return this;
}
public PhoneBuilder camera(String camera) {
this.phone.setCamera(camera);
return this;
}
// 构建最终对象
public Phone build() {
return this.phone;
}
}客户端使用:
public class UseBuilder {
public static void main(String[] args) {
Phone phone = new PhoneBuilder()
.battery("5000毫安大容量")
.camera("徕卡顶级镜头")
.color("尊贵黑")
.screen("2K高清分辨率")
.os("Android 14")
.build();
System.out.println("phone = " + phone);
}
}3.3.3 变种:带校验的建造者
public class PhoneBuilder {
private Phone phone = new Phone();
public PhoneBuilder battery(String battery) {
this.phone.setBattery(battery);
return this;
}
// ... 其他setter方法
public Phone build() {
// 构建前进行校验
if (phone.getBattery() == null) {
throw new IllegalStateException("电池必须设置");
}
if (phone.getScreen() == null) {
throw new IllegalStateException("屏幕必须设置");
}
return phone;
}
}3.3.4 【实战案例】StringBuilder/Lombok @Builder
// JDK的StringBuilder就是建造者模式
StringBuilder sb = new StringBuilder();
String result = sb.append("Hello")
.append(" ")
.append("World")
.toString();
// Lombok的@Builder注解自动生成建造者
@Builder
public class User {
private String name;
private int age;
private String email;
}
// 使用生成的建造者
User user = User.builder()
.name("张三")
.age(25)
.email("zhangsan@example.com")
.build();3.3.5 【常见问题】
| 问题 | 解答 |
|---|---|
| 建造者和工厂的区别? | 工厂关注"创建什么对象",建造者关注"如何一步步构建对象" |
| 什么时候用建造者? | 构造方法参数过多(超过4个)时;对象构建步骤复杂时 |
| 建造者必须链式调用吗? | 不是必须,但链式调用是建造者模式的典型特征 |
小结
- 核心思想:分步骤构建复杂对象,通常配合链式调用
- 主要优点:可读性更好,也方便加入校验逻辑
- 典型应用:
StringBuilder、Lombok@Builder - 和工厂的区别:工厂关注“创建什么”,建造者关注“怎么一步步构建”
四、结构型模式
4.1 代理模式(Proxy)
4.1.1 模式定义
代理模式是一种结构型设计模式,让你能够提供对象的替代品或其占位符。代理控制着对原对象的访问,并允许在将请求提交给对象前后进行一些处理。

代理结构:

代理模式类型:
- 静态代理:手动编写代理类
- JDK动态代理:基于接口的动态代理
- Cglib动态代理:基于继承的动态代理
常见应用场景:
- 日志记录、性能监控
- 事务管理、权限控制
- 延迟加载(如Hibernate的懒加载)
- RPC远程调用
4.1.2 静态代理
// 1. 定义接口
public interface UserService {
void insert();
}
// 2. 目标类(被代理对象)
public class UserServiceImpl implements UserService {
@Override
public void insert() {
System.out.println("目标类执行了insert方法");
}
}
// 3. 代理类
public class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target; // 注入目标对象
}
@Override
public void insert() {
System.out.println("【前置】代理之前打印一个日志");
target.insert(); // 调用目标方法
System.out.println("【后置】代理之后打印一个日志");
}
}测试代码:
@Test
public void testStaticProxy() {
UserServiceProxy proxy = new UserServiceProxy(new UserServiceImpl());
proxy.insert();
}静态代理优缺点:
| 优点 | 缺点 |
|---|---|
| 实现简单直观 | 代码冗余,每代理一个类需编写一个代理类 |
| 不修改目标类即可扩展功能 | 接口变更时,代理类和目标类都要修改 |
4.1.3 JDK动态代理
原理:在运行时动态生成代理类的字节码,无需手动编写代理类。
限制条件:目标类必须实现接口。
核心API:
| 类/接口 | 方法 | 说明 |
|---|---|---|
java.lang.reflect.Proxy |
newProxyInstance(ClassLoader, Class<?>[], InvocationHandler) |
创建代理对象 |
java.lang.reflect.InvocationHandler |
invoke(Object, Method, Object[]) |
定义代理逻辑 |
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* JDK动态代理工厂
*/
public class JdkProxyFactory {
private Object target;
public JdkProxyFactory(Object target) {
this.target = target;
}
@SuppressWarnings("unchecked")
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 目标类实现的接口
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("【前置】方法执行前...");
Object result = method.invoke(target, args); // 调用目标方法
System.out.println("【后置】方法执行后...");
return result;
}
}
);
}
}测试代码:
@Test
public void testJdkProxy() {
UserService userService = new UserServiceImpl();
// 创建代理对象
UserService proxy = new JdkProxyFactory(userService).getProxy();
// 执行代理方法
proxy.insert();
}生成的代理类源码(反编译):
public final class $Proxy0 extends Proxy implements UserService {
private static Method m3; // insert方法
public $Proxy0(InvocationHandler h) {
super(h);
}
static {
try {
m3 = Class.forName("com.example.UserService")
.getMethod("insert", new Class[0]);
} catch (Exception e) {
throw new NoSuchMethodError(e.getMessage());
}
}
@Override
public final void insert() {
try {
// 调用InvocationHandler的invoke方法
this.h.invoke(this, m3, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}4.1.4 Cglib动态代理
原理:通过继承目标类,生成子类作为代理类。
优势:目标类无需实现接口。
限制:目标类不能是final类,目标方法不能是final方法。
Maven依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Cglib动态代理工厂
*/
public class CglibProxyFactory implements MethodInterceptor {
private Object target;
public CglibProxyFactory(Object target) {
this.target = target;
}
@SuppressWarnings("unchecked")
public <T> T getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 设置父类(目标类)
enhancer.setCallback(this); // 设置回调
return (T) enhancer.create(); // 创建代理对象
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("【前置】Cglib代理...");
Object result = method.invoke(target, args); // 调用目标方法
System.out.println("【后置】Cglib代理...");
return result;
}
}目标类(不实现接口):
public class UserServiceImpl {
public String getName() {
System.out.println("目标方法执行");
return "zhangsan";
}
}测试代码:
public class ProxyTest {
public static void main(String[] args) {
UserServiceImpl target = new UserServiceImpl();
// 创建Cglib代理
UserServiceImpl proxy = new CglibProxyFactory(target).getProxy();
String name = proxy.getName();
System.out.println("返回值: " + name);
}
}JDK 17+ 兼容性配置:
在Java 9+中,由于模块系统的限制,运行Cglib需要添加JVM参数:
java --add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/sun.net.util=ALL-UNNAMED \
-jar your-application.jar或在IDEA的VM Options中添加:
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/sun.net.util=ALL-UNNAMED
4.1.5 三种代理对比
| 特性 | 静态代理 | JDK动态代理 | Cglib动态代理 |
|---|---|---|---|
| 实现方式 | 手动编写 | 动态生成字节码 | 动态生成字节码 |
| 目标类要求 | 无 | 必须实现接口 | 不能是final类 |
| 性能 | 高 | 中(反射调用) | 高(使用MethodProxy) |
| 代码耦合 | 高 | 低 | 低 |
| 适用范围 | 少量固定场景 | 有接口的类 | 无接口的类 |
4.1.7 【常见问题】
| 问题 | 解答 |
|---|---|
| JDK代理和Cglib哪个好? | 各有优劣。目标类有接口时通常优先考虑JDK动态代理,无接口时再考虑Cglib |
| 代理对象调用自身方法会走代理吗? | 不会,this引用指向的是目标对象本身 |
| 如何获取真实对象? | 通常应在代理创建阶段保留目标对象引用,避免在业务代码中反向查找真实对象 |
| Java 17中Cglib还能用吗? | 可以,但需要添加--add-opens参数开放模块访问权限 |
小结
- 核心思想:给目标对象套一层代理,用来控制访问或做功能增强
- 静态代理:手动编写,简单直观,但代码容易重复
- JDK 动态代理:基于接口,目标类需要实现接口
- Cglib 动态代理:基于继承,目标类可以没有接口,但不能是
final - 典型应用:日志增强、权限控制、延迟加载、远程调用
五、行为型模式
5.1 责任链模式(Chain of Responsibility)
5.1.1 模式定义
责任链是一种行为设计模式,允许你将请求沿着处理者链进行发送。收到请求后,每个处理者均可对请求进行处理,或将其传递给链上的下一个处理者。

类图关系:

适用场景:
- 多个对象可以处理同一请求,但具体由哪个对象处理在运行时确定
- 需要动态指定处理请求的对象集合
- 需要在不明确指定接收者的情况下,向多个对象中的一个提交请求
常见应用:
- Servlet Filter链
- 多级审批链
- 审批流程系统
- 日志记录器(Logger级别链)
5.1.2 经典实现
案例 - 三级审批流程:
/**
* 抽象处理者
*/
public abstract class AbstractHandler {
protected AbstractHandler next; // 下一个处理者
// 设置下一个处理者
public void setNext(AbstractHandler next) {
System.out.println(this.getClass().getSimpleName() + " → " + next.getClass().getSimpleName());
this.next = next;
}
// 处理请求(抽象方法)
public abstract void handle();
}/**
* 一级处理器
*/
public class Level1Handler extends AbstractHandler {
@Override
public void handle() {
System.out.println("【一级处理器】正在处理...");
// 如果无法处理,传递给下一个
if (next != null) {
next.handle();
}
}
}
/**
* 二级处理器
*/
public class Level2Handler extends AbstractHandler {
@Override
public void handle() {
System.out.println("【二级处理器】正在处理...");
if (next != null) {
next.handle();
}
}
}
/**
* 三级处理器
*/
public class Level3Handler extends AbstractHandler {
@Override
public void handle() {
System.out.println("【三级处理器】正在处理...");
if (next != null) {
next.handle();
}
}
}测试代码:
public class ChainExecution {
public static void main(String[] args) {
// 创建处理器
Level1Handler level1 = new Level1Handler();
Level2Handler level2 = new Level2Handler();
Level3Handler level3 = new Level3Handler();
// 组装责任链
level1.setNext(level2);
level2.setNext(level3);
// 从链头开始处理
level1.handle();
}
}输出:
Level1Handler → Level2Handler
Level2Handler → Level3Handler
【一级处理器】正在处理...
【二级处理器】正在处理...
【三级处理器】正在处理...5.1.5 【常见问题】
| 问题 | 解答 |
|---|---|
| 责任链和装饰器有什么区别? | 责任链是"一个处理完可能传递给下一个",装饰器是"层层包装增强" |
| 链太长会影响性能吗? | 会的,需要合理设计链长度,或设置最大遍历次数 |
| 如何确保请求被处理? | 可以在链尾添加默认处理器,或检查返回值 |
| 责任链可以循环吗? | 理论上可以,但会导致死循环,应避免 |
小结
- 核心思想:让请求沿着处理者链向后传递,直到被处理
- 主要优点:解耦发送者和接收者,也方便动态调整链结构
- 典型应用:Filter 链、Interceptor 链、审批流程
- 注意事项:避免链过长,也要防止循环依赖
六、课程总结
6.1 知识点回顾
- 设计原则部分,先记住 SOLID 是“看代码设计是否顺眼”的几个角度
- 创建型里重点掌握:单例、工厂、建造者
- 结构型里重点掌握:代理
- 行为型里重点掌握:责任链
- 学到这里,最重要的不是背名字,而是知道“什么场景下为什么要这么设计”
6.2 设计模式选择指南
| 场景 | 推荐模式 | 说明 |
|---|---|---|
| 全局只需要一个实例 | 单例 | 配置类、连接池等 |
| 根据不同条件创建对象 | 工厂 | 降低耦合,便于扩展 |
| 对象创建步骤复杂 | 建造者 | 链式调用,清晰构建 |
| 需要增强方法功能 | 代理 | 日志、事务、权限等 |
| 多层级审批/处理 | 责任链 | 动态指定处理者 |
6.3 学习建议
- 先理解原则,再学习模式:SOLID原则是设计模式的指导思想
- 不要过度设计:简单问题不要强行使用复杂模式
- 从经典实现中学习:阅读JDK和优秀开源项目中的模式应用
- 重构练习:在重构代码时思考可以应用哪些模式
6.4 参考资料
- 《设计模式:可复用面向对象软件的基础》(GoF经典)
- 《Head First 设计模式》(入门推荐)
- 《Java与模式》(阎宏,中文经典)
- JDK源码与经典开源项目示例
<!-- 实战练习内容已分离到 practices/10-design-pattern-practice.md -->
建议先回顾本章重点内容,再进入配套练习。 练习顺序建议:设计原则辨析 → 常用模式实现 → 场景分析 → 综合重构。 如果你是第一次系统学习设计模式,优先保证“能识别场景、能说清为什么用”,再追求手写复杂实现。
*本文档由JavaEE课程组编写,最后更新时间:2026-03-18*