WD
Classnote Docs课程课件
10

10-Design-Pattern-Lite

# 设计模式

学习目标:

  • 先知道设计模式在解决什么问题
  • 先记住三大类和几个高频模式
  • 先把 SOLID 当成看代码的几个提醒
01 / Section

一、设计模式简介

设计模式不是语法,也不是框架,而是处理常见设计问题的成熟经验

它主要帮你处理这些情况:

  • 代码越写越乱
  • 模块之间耦合太深
  • 新需求一来只能硬改旧代码
  • 过一段时间连自己都不想看

先记住三大类就够了:

类别 关注点
创建型 对象怎么创建
结构型 类和对象怎么组合
行为型 对象之间怎么协作

高频模式:

  • 创建型:单例、工厂、建造者
  • 结构型:代理
  • 行为型:责任链

看后面的每个模式时,带着这三个问题就够用了:

  1. 它解决什么问题?
  2. 不用它时,代码容易乱在哪里?
  3. 用了它之后,好处是什么?
02 / Section

二、设计模式原则(SOLID)

设计模式背后通常有一套更稳定的设计思路,最常见的就是 SOLID。

这一版先记成五个提醒就行:

原则 一句话理解
S 单一职责 一个类别太杂
O 开闭原则 新功能尽量靠扩展
L 里氏替换 子类别把父类语义改坏
I 接口隔离 接口别又大又全
D 依赖倒置 尽量依赖抽象

简单理解:

  • 单一职责:一个类里别什么都塞
  • 开闭原则:加功能优先考虑新增,少直接改老代码
  • 里氏替换:子类要真的能当父类来用
  • 接口隔离:谁需要什么能力,就给谁那部分接口
  • 依赖倒置:调用方别被某个具体实现绑死

不用一开始就背得很细,能在代码里看出下面这些问题就够了:

  • 类是不是太胖了
  • 继承是不是有点别扭
  • 接口是不是塞了太多东西
  • 实现是不是绑得太死了

原则更像方向,模式更像做法。后面学单例、工厂、代理、责任链时,再反过来体会这些原则就行。

03 / Section

三、创建型模式

创建型模式提供了创建对象的机制,能够提升已有代码的灵活性和可复用性。

3.1 单例模式(Singleton)

3.1.1 模式定义

单例是一种创建型设计模式,让你保证一个类只有一个实例对象,并提供了一个访问该实例对象的全局节点。

单例模式UML
单例模式UML

对应的UML类图:

单例模式类图
单例模式类图

适用场景

  • 数据库连接池
  • 配置管理器
  • 线程池
  • 缓存对象

3.1.2 实现方式一:饿汉模式

在类加载的过程中初始化私有静态实例对象,保证了线程安全性。

java
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 实现方式二:懒汉模式(线程不安全)

java
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不同。

多线程问题
多线程问题

问题原因

text
线程A执行 if(instance == null) 判断通过 → 线程切换
线程B执行 if(instance == null) 判断通过 → 创建instance对象
线程A恢复执行 → 再次创建instance对象

3.1.4 实现方式三:懒汉模式(线程安全)

java
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)

java
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;
    }
}

为什么需要双重检查?

text
线程A执行外层if判断通过 → 线程切换
线程B执行外层if判断通过 → 获取锁 → 创建对象 → 释放锁
线程A恢复执行 → 获取锁 → 如果没有内层if,又会创建一个对象!

volatile关键字的作用

  • instance = new Singleton4()不是原子操作,可能指令重排序
  • 重排序后可能导致其他线程获取到未完全初始化的对象
  • volatile禁止指令重排序,保证对象的正确发布

3.1.6 实现方式五:静态内部类

java
/**
 * 静态内部类实现单例
 * 特点:懒加载 + 线程安全 + 代码简洁
 * 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 实现方式六:枚举

java
/**
 * 枚举实现单例
 * 特点:最简单、最安全的实现方式
 * 天然防止反射攻击和序列化问题
 */
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 模式定义

工厂模式是创建型模式中最常用的一种,其核心思想是将对象的创建和使用分离,通过专门的工厂类来创建对象。

工厂模式分为三种:

  1. 简单工厂模式(Simple Factory)
  2. 工厂方法模式(Factory Method)
  3. 抽象工厂模式(Abstract Factory)

3.2.2 简单工厂模式

定义:只需要一个工厂(函数),传入不同的参数,返回不同的产品(实例)。

案例 - 特斯拉汽车工厂

java
// 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");
    }
}

不使用工厂 - 客户端直接创建:

java
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();

使用简单工厂 - 封装创建逻辑:

java
/**
 * 简单工厂类
 * 封装了对象创建的细节,客户端只需传递参数
 */
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 + "路上请注意,道路千万条,安全第一条");
                    }
                };
        }
    }
}

客户端代码变得简洁:

java
Scanner scanner = new Scanner(System.in);
String keyword = scanner.nextLine();
Tesla tesla = SimpleTeslaFactory.create(keyword); // 一行代码获取对象
tesla.run();

简单工厂优缺点

优点 缺点
封装对象创建逻辑 增加新产品时需要修改工厂类,违反开闭原则
降低客户端与具体产品的耦合 工厂类职责过重
代码复用性高 产品类型过多时,工厂方法过于复杂

3.2.3 工厂方法模式

定义:定义一个创建对象的接口,让子类决定实例化哪个类。工厂方法将对象的创建延迟到子类。

工厂方法模式UML
工厂方法模式UML

适用场景

  • 需要生产多种同类产品
  • 每种类型的创建逻辑相对复杂
  • 需要扩展新的产品类型而不修改现有代码

案例 - 特斯拉生产车间

java
// 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();
    }
}

客户端代码:

java
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和冰箱产品线。用户倾向于选择同一品牌的产品。

抽象工厂UML
抽象工厂UML

类图关系:

抽象工厂类图
抽象工厂类图
java
// 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();
    }
}

客户端代码:

java
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 模式定义

建造者模式也叫生成器模式,是一种分步骤创建复杂对象的设计模式。该模式允许使用相同的创建代码生成不同类型和形式的对象。

建造者模式UML
建造者模式UML

适用场景

  • 对象构建步骤复杂,需要分步完成
  • 需要构建不同表示的相同对象
  • 需要避免构造方法参数过多(" telescoping constructor"问题)

3.3.2 经典实现

案例 - 手机组装

手机由多个组件构成:屏幕、电池、摄像头、系统、颜色等。

java
// 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;
    }
}

客户端使用:

java
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 变种:带校验的建造者

java
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

java
// 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
  • 和工厂的区别:工厂关注“创建什么”,建造者关注“怎么一步步构建”
04 / Section

四、结构型模式

4.1 代理模式(Proxy)

4.1.1 模式定义

代理模式是一种结构型设计模式,让你能够提供对象的替代品或其占位符。代理控制着对原对象的访问,并允许在将请求提交给对象前后进行一些处理。

代理模式UML
代理模式UML

代理结构:

代理结构
代理结构

代理模式类型

  1. 静态代理:手动编写代理类
  2. JDK动态代理:基于接口的动态代理
  3. Cglib动态代理:基于继承的动态代理

常见应用场景

  • 日志记录、性能监控
  • 事务管理、权限控制
  • 延迟加载(如Hibernate的懒加载)
  • RPC远程调用

4.1.2 静态代理

java
// 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("【后置】代理之后打印一个日志");
    }
}

测试代码:

java
@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[]) 定义代理逻辑
java
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;
                }
            }
        );
    }
}

测试代码:

java
@Test
public void testJdkProxy() {
    UserService userService = new UserServiceImpl();
    
    // 创建代理对象
    UserService proxy = new JdkProxyFactory(userService).getProxy();
    
    // 执行代理方法
    proxy.insert();
}

生成的代理类源码(反编译):

java
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依赖

xml
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
java
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;
    }
}

目标类(不实现接口):

java
public class UserServiceImpl {
    public String getName() {
        System.out.println("目标方法执行");
        return "zhangsan";
    }
}

测试代码:

java
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参数:

bash
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中添加:

text
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/sun.net.util=ALL-UNNAMED
Cglib兼容性
Cglib兼容性

4.1.5 三种代理对比

特性 静态代理 JDK动态代理 Cglib动态代理
实现方式 手动编写 动态生成字节码 动态生成字节码
目标类要求 必须实现接口 不能是final类
性能 中(反射调用) 高(使用MethodProxy)
代码耦合
适用范围 少量固定场景 有接口的类 无接口的类

4.1.7 【常见问题】

问题 解答
JDK代理和Cglib哪个好? 各有优劣。目标类有接口时通常优先考虑JDK动态代理,无接口时再考虑Cglib
代理对象调用自身方法会走代理吗? 不会,this引用指向的是目标对象本身
如何获取真实对象? 通常应在代理创建阶段保留目标对象引用,避免在业务代码中反向查找真实对象
Java 17中Cglib还能用吗? 可以,但需要添加--add-opens参数开放模块访问权限

小结

  • 核心思想:给目标对象套一层代理,用来控制访问或做功能增强
  • 静态代理:手动编写,简单直观,但代码容易重复
  • JDK 动态代理:基于接口,目标类需要实现接口
  • Cglib 动态代理:基于继承,目标类可以没有接口,但不能是 final
  • 典型应用:日志增强、权限控制、延迟加载、远程调用
05 / Section

五、行为型模式

5.1 责任链模式(Chain of Responsibility)

5.1.1 模式定义

责任链是一种行为设计模式,允许你将请求沿着处理者链进行发送。收到请求后,每个处理者均可对请求进行处理,或将其传递给链上的下一个处理者。

责任链UML
责任链UML

类图关系:

责任链类图
责任链类图

适用场景

  • 多个对象可以处理同一请求,但具体由哪个对象处理在运行时确定
  • 需要动态指定处理请求的对象集合
  • 需要在不明确指定接收者的情况下,向多个对象中的一个提交请求

常见应用

  • Servlet Filter链
  • 多级审批链
  • 审批流程系统
  • 日志记录器(Logger级别链)

5.1.2 经典实现

案例 - 三级审批流程

java
/**
 * 抽象处理者
 */
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();
}
java
/**
 * 一级处理器
 */
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();
        }
    }
}

测试代码:

java
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();
    }
}

输出:

text
Level1Handler → Level2Handler
Level2Handler → Level3Handler
【一级处理器】正在处理...
【二级处理器】正在处理...
【三级处理器】正在处理...

5.1.5 【常见问题】

问题 解答
责任链和装饰器有什么区别? 责任链是"一个处理完可能传递给下一个",装饰器是"层层包装增强"
链太长会影响性能吗? 会的,需要合理设计链长度,或设置最大遍历次数
如何确保请求被处理? 可以在链尾添加默认处理器,或检查返回值
责任链可以循环吗? 理论上可以,但会导致死循环,应避免

小结

  • 核心思想:让请求沿着处理者链向后传递,直到被处理
  • 主要优点:解耦发送者和接收者,也方便动态调整链结构
  • 典型应用:Filter 链、Interceptor 链、审批流程
  • 注意事项:避免链过长,也要防止循环依赖
06 / Section

六、课程总结

6.1 知识点回顾

  • 设计原则部分,先记住 SOLID 是“看代码设计是否顺眼”的几个角度
  • 创建型里重点掌握:单例、工厂、建造者
  • 结构型里重点掌握:代理
  • 行为型里重点掌握:责任链
  • 学到这里,最重要的不是背名字,而是知道“什么场景下为什么要这么设计”

6.2 设计模式选择指南

场景 推荐模式 说明
全局只需要一个实例 单例 配置类、连接池等
根据不同条件创建对象 工厂 降低耦合,便于扩展
对象创建步骤复杂 建造者 链式调用,清晰构建
需要增强方法功能 代理 日志、事务、权限等
多层级审批/处理 责任链 动态指定处理者

6.3 学习建议

  1. 先理解原则,再学习模式:SOLID原则是设计模式的指导思想
  2. 不要过度设计:简单问题不要强行使用复杂模式
  3. 从经典实现中学习:阅读JDK和优秀开源项目中的模式应用
  4. 重构练习:在重构代码时思考可以应用哪些模式

6.4 参考资料

  • 《设计模式:可复用面向对象软件的基础》(GoF经典)
  • 《Head First 设计模式》(入门推荐)
  • 《Java与模式》(阎宏,中文经典)
  • JDK源码与经典开源项目示例

<!-- 实战练习内容已分离到 practices/10-design-pattern-practice.md -->

建议先回顾本章重点内容,再进入配套练习。 练习顺序建议:设计原则辨析 → 常用模式实现 → 场景分析 → 综合重构。 如果你是第一次系统学习设计模式,优先保证“能识别场景、能说清为什么用”,再追求手写复杂实现。

*本文档由JavaEE课程组编写,最后更新时间:2026-03-18*