WD
Classnote Docs课程课件
10

设计模式详解

学习目标:

  • 理解设计模式的价值,并能说清为什么大型项目需要设计模式
  • 掌握 GoF 23 种设计模式的分类方式与核心学习路径
  • 能结合 SOLID 原则分析代码设计是否合理
  • 掌握单例、工厂、建造者、代理、责任链等常用模式的典型实现
  • 能在实际开发、源码阅读和面试场景中识别并应用常见设计模式

本章重点:

  • 设计模式与设计原则(SOLID)的关系
  • 创建型、结构型、行为型三大类模式的划分逻辑
  • 单例、工厂、代理、责任链等重点模式的实现思路与适用场景
  • 常见设计模式在实际开发中的应用思路
01 / Section

一、设计模式简介

1.1 什么是设计模式

设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。设计模式代表了最佳的实践,通常被有经验的软件开发人员所使用。

大白话理解:设计模式是前人总结的一套经验。按照前人总结的经验去设计架构、编写代码,有很多好处:

  • 后期可维护性强 - 代码结构清晰,易于理解和修改
  • 耦合度低 - 模块之间依赖关系合理,改动一处不会牵一发而动全身
  • 代码复用性高 - 避免重复造轮子,提高开发效率
  • 代码通俗易懂 - 遵循业界公认的规范,降低团队协作成本
  • 健壮性强 - 经过大量实践验证的解决方案,稳定性更高

💡 类比理解:在建筑领域,如果你只是希望盖一个茅草屋,那么无需任何模式;但是如果你希望建造一座摩天大楼,那么必须要有设计模式和规范。

1.2 为什么学习设计模式

场景 说明
面试必备 面对面试中的设计模式问题,如单例模式的几种实现方式、懒汉式和饿汉式的区别
阅读源码 有助于阅读经典项目和优质代码实现,是进阶提升的必备技能
编写高效代码 借助设计模式可以编写出高复用性、高稳健性的代码
系统设计 在设计大型系统时,合理运用设计模式可以让系统更易于扩展和维护

1.3 设计模式的发展历史

设计模式最早并非来自软件领域,而是来自建筑领域

  1. 1977年:建筑师克里斯托佛·亚历山大在其著作《建筑模式语言》中首次提出模式概念,描述窗户高度、建筑层数、街区植被面积等设计规范。
  1. 1994年:四位软件工程师(埃里希·伽玛、约翰·弗利赛德斯、拉尔夫·约翰逊、理查德·赫尔姆)接受模式概念,出版《设计模式:可复用面向对象软件的基础》,将23个设计模式引入程序开发领域。
  1. Gang of Four (GoF):由于四位作者名字较长,业界简称"四人组"或"GoF",他们提出的23种设计模式被称为"GoF设计模式"。
GoF设计模式书籍
GoF设计模式书籍

1.4 设计模式的分类

GoF设计模式共有23种,根据用途分为三大类:

📦 创建型模式(5种)

核心作用:提供创建对象的机制,提升代码的灵活性和可复用性。

模式 英文名 一句话描述
单例模式 Singleton 保证一个类只有一个实例
工厂方法模式 Factory Method 定义创建对象的接口,让子类决定实例化哪个类
抽象工厂模式 Abstract Factory 创建相关或依赖对象的家族,而无需明确指定具体类
建造者模式 Builder 分步骤创建复杂对象
原型模式 Prototype 通过复制现有对象来创建新对象
创建型模式1
创建型模式1
创建型模式2
创建型模式2
创建型模式3
创建型模式3
创建型模式4
创建型模式4

🏗️ 结构型模式(7种)

核心作用:介绍如何将对象和类组装成较大的结构,并保持结构的灵活和高效。

模式 英文名 一句话描述
代理模式 Proxy 为其他对象提供一种代理以控制对这个对象的访问
装饰器模式 Decorator 动态地给对象添加额外的职责
适配器模式 Adapter 将一个类的接口转换成客户希望的另一个接口
桥接模式 Bridge 将抽象部分与实现部分分离,使它们都可以独立变化
外观模式 Facade 为子系统中的一组接口提供一个统一的入口
组合模式 Composite 将对象组合成树形结构以表示"部分-整体"的层次结构
享元模式 Flyweight 运用共享技术有效地支持大量细粒度的对象
结构型模式1
结构型模式1
结构型模式2
结构型模式2
结构型模式3
结构型模式3
结构型模式4
结构型模式4
结构型模式5
结构型模式5
结构型模式6
结构型模式6
结构型模式7
结构型模式7

🎭 行为型模式(11种)

核心作用:负责对象间的高效沟通和职责委派。

模式 英文名 一句话描述
观察者模式 Observer 定义对象间的一对多依赖,当一个对象改变时,所有依赖者都会收到通知
策略模式 Strategy 定义算法族,分别封装起来,让它们可以互相替换
模板方法模式 Template Method 定义算法骨架,将某些步骤延迟到子类中实现
责任链模式 Chain of Responsibility 为请求创建一条接收者链,沿链传递请求直到被处理
迭代器模式 Iterator 提供一种方法顺序访问聚合对象中的各个元素
状态模式 State 允许对象在内部状态改变时改变它的行为
命令模式 Command 将请求封装为对象,以便用不同请求来参数化其他对象
备忘录模式 Memento 在不破坏封装性的前提下,捕获对象的内部状态
访问者模式 Visitor 表示一个作用于某对象结构中的各元素的操作
中介者模式 Mediator 用一个中介对象来封装一系列的对象交互
解释器模式 Interpreter 给定一个语言,定义它的文法表示,并定义一个解释器
行为型模式1
行为型模式1
行为型模式2
行为型模式2
行为型模式3
行为型模式3
行为型模式4
行为型模式4
行为型模式5
行为型模式5
行为型模式6
行为型模式6
行为型模式7
行为型模式7
行为型模式8
行为型模式8
行为型模式9
行为型模式9
行为型模式10
行为型模式10

本章小结

diagram
设计模式核心要点 • 23种GoF设计模式,分为创建型、结构型、行为型三大类 • 设计模式是前人经验的总结,不是银弹,需根据场景合理选择 • 学习设计模式有助于面试、读源码、写高质量代码
┌─────────────────────────────────────────────────────────────┐
│  设计模式核心要点                                             │
├─────────────────────────────────────────────────────────────┤
│  • 23种GoF设计模式,分为创建型、结构型、行为型三大类            │
│  • 设计模式是前人经验的总结,不是银弹,需根据场景合理选择        │
│  • 学习设计模式有助于面试、读源码、写高质量代码                 │
└─────────────────────────────────────────────────────────────┘
02 / Section

二、设计模式原则(SOLID)

设计模式需要有设计原则作为指导纲领。设计模式是在设计原则的指引下设计出来的。SOLID原则是面向对象设计的五大基本原则:

原则 英文全称 缩写 核心思想
单一职责原则 Single Responsibility Principle S 一个类只负责一个功能模块
开放封闭原则 Open Close Principle O 对扩展开放,对修改封闭
里氏替换原则 Liskov Substitution Principle L 子类必须能够替换父类
接口隔离原则 Interface Segregation Principle I 客户端不应依赖它不需要的接口
依赖倒置原则 Dependency Inversion Principle D 依赖抽象,而非具体实现

📌 :迪米特法则(Law of Demeter,最少知道原则)通常也被纳入设计原则。

2.1 单一职责原则(SRP)

定义:尽量使得每个类只负责整个软件功能模块中的一个职责。

问题场景: 当程序不断壮大,类变得非常庞杂时:

  • 查找某部分代码变得非常吃力
  • 任何一处修改都会影响整个类的代码
  • 类的修改原因可能来自多个方面

案例分析

假设有一个Employee类,包含以下功能:

  1. 管理雇员信息(核心职责)
  2. 打印雇员信息(辅助功能)
违反单一职责
违反单一职责

问题:修改该类的原因有两个(管理信息 + 打印格式),违反单一职责原则。

优化方案

java
// 雇员类 - 只负责雇员信息管理
public class Employee {
    private String id;
    private String name;
    private double salary;
    
    // 雇员相关的核心业务逻辑
    public void calculateSalary() { /* ... */ }
    public void updateInfo() { /* ... */ }
}

// 打印类 - 只负责打印功能
public class EmployeePrinter {
    private Employee employee;
    
    public EmployeePrinter(Employee employee) {
        this.employee = employee;
    }
    
    public void printBasicInfo() { /* ... */ }
    public void printDetailedReport() { /* ... */ }
}
遵循单一职责
遵循单一职责

优点

  • ✅ 降低类的复杂度,职责清晰
  • ✅ 提高类的可读性和可维护性
  • ✅ 降低变更引起的风险

2.2 开放封闭原则(OCP)

定义:软件设计中的对象、类、模块以及函数等对于扩展是开放的,但是对于修改是封闭的

核心思想

  • 已有功能模块开发、测试完毕后,直接修改代码风险很大
  • 新功能应该通过扩展现有代码来实现,而非修改现有代码
  • 使用抽象定义结构,用具体实现扩展细节

⚠️ 例外:如果代码中存在缺陷、Bug,则应该直接修复。

经典案例 - JDBC设计

java
// 抽象接口 - 定义结构
public interface Connection {
    Statement createStatement();
    PreparedStatement prepareStatement(String sql);
    // ...
}

// 具体实现1 - MySQL
public class MySQLConnection implements Connection {
    @Override
    public Statement createStatement() {
        return new MySQLStatement();
    }
    // ...
}

// 具体实现2 - Oracle
public class OracleConnection implements Connection {
    @Override
    public Statement createStatement() {
        return new OracleStatement();
    }
    // ...
}

// 使用方 - 依赖抽象
public class UserDao {
    private Connection connection;
    
    public UserDao(Connection connection) {
        this.connection = connection; // 可以传入MySQL或Oracle
    }
    
    // 无需修改UserDao,即可支持新的数据库
}
开放封闭原则
开放封闭原则

优点

  • ✅ 提高系统的可扩展性
  • ✅ 减少修改带来的风险
  • ✅ 便于单元测试和回归测试

2.3 里氏替换原则(LSP)

定义:由芭芭拉·利斯科夫(Barbara Liskov)于1987年提出:

如果S是T的子类型,对于S类型的任意对象,如果将它们看作是T类型的对象,则它的行为也理应和预期的行为一致。

核心思想

  • 子类必须保持与父类行为的兼容
  • 重写方法时,应该对基类行为进行扩展,而不是完全替换
  • 在使用父类的程序中,替换为使用子类,程序的运行结果应该是一致的

违反案例

java
// 父类:矩形
public class Rectangle {
    protected int width;
    protected int height;
    
    public void setWidth(int width) { this.width = width; }
    public void setHeight(int height) { this.height = height; }
    public int getArea() { return width * height; }
}

// 子类:正方形(违反LSP)
public class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        this.width = width;
        this.height = width; // 强制相等
    }
    
    @Override
    public void setHeight(int height) {
        this.width = height; // 强制相等
        this.height = height;
    }
}

// 测试
public void testRectangle(Rectangle rect) {
    rect.setWidth(5);
    rect.setHeight(4);
    // 期望面积 = 20,但Square实际返回 = 16
    assert rect.getArea() == 20 : "面积计算错误!";
}

正确方案

java
// 抽象父类
public abstract class Shape {
    public abstract int getArea();
}

public class Rectangle extends Shape {
    private int width;
    private int height;
    // ... 正常实现
}

public class Square extends Shape {
    private int side;
    // ... 独立实现,不继承Rectangle
}
里氏替换原则
里氏替换原则
里氏替换原则改进
里氏替换原则改进

2.4 迪米特法则(LoD)

定义:又叫最少知道原则,一个类/模块对于其他类/模块有越少的了解越好。

核心思想

  • 不应该有依赖关系的类之间,不要存在依赖关系
  • 有依赖关系的类之间,尽量只依赖于接口
  • 只与直接的朋友通信

什么是"直接的朋友"?

  • ✅ 成员变量中的类
  • ✅ 方法参数中的类
  • ✅ 方法返回值中的类
  • ❌ 局部变量中的类(不是直接朋友)

案例 - 明星与经纪人

java
// 违反迪米特法则:明星亲力亲为处理所有事务
public class StarBad {
    public void meetFans() { /* ... */ }
    public void negotiateBusiness() { /* ... */ }
    public void handleContract() { /* ... */ }
    public void attendShow() { /* ... */ }
    // 依赖过多,精力分散
}

// 遵循迪米特法则:通过经纪人处理
public class Agent {
    private Star star;
    private Fans fans;
    private Company company;
    
    public void arrangeMeeting() {
        // 安排粉丝见面会
    }
    
    public void negotiateContract() {
        // 与商家洽谈
    }
}

public class Star {
    private Agent agent; // 只依赖直接朋友Agent
    
    public void perform() { /* 只专注表演 */ }
}
违反迪米特法则
违反迪米特法则
遵循迪米特法则
遵循迪米特法则

案例 - 老师让班长点名

java
// ❌ 违反迪米特法则:老师直接依赖了Student
public class TeacherBad {
    public void command() {
        // 直接创建Student(局部变量,不是直接朋友)
        List<Student> students = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            students.add(new Student());
        }
        
        // 依赖StudentLeader
        StudentLeader leader = new StudentLeader();
        System.out.println("清点人数完毕,总共有:" + leader.counts(students) + "人");
    }
}

// ✅ 遵循迪米特法则:老师只依赖班长
public class Teacher {
    // 仅耦合StudentLeader(方法参数,直接朋友)
    public void command(StudentLeader leader) {
        System.out.println("清点人数完毕,总共有:" + leader.counts() + "人");
    }
}

public class StudentLeader {
    private List<Student> students; // Student是成员变量,直接朋友
    
    public StudentLeader(List<Student> students) {
        this.students = students;
    }
    
    public int counts() {
        return students.size();
    }
}

// 测试
public class TaskTest {
    public static void main(String[] args) {
        System.out.println("周末收假,学校领导命令老师去点名.....");
        List<Student> students = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            students.add(new Student());
        }
        Teacher teacher = new Teacher();
        teacher.command(new StudentLeader(students));
    }
}

2.5 接口隔离原则(ISP)

定义:一个类对另一个类的依赖应当建立在最小的接口上。

核心思想

  • 为各个类建立它们需要的专用接口
  • 不要试图建立一个很庞大的接口供所有依赖它的类去调用
  • 接口应该小而专,而不是大而全

违反案例

假设开发一套云服务器整合程序,设计一个大而全的接口:

java
// ❌ 庞大接口,违反ISP
public interface CloudProvider {
    void createServer();      // 创建服务器
    void deleteServer();      // 删除服务器
    void createDatabase();    // 创建数据库(阿里云支持,腾讯云暂不支持)
    void createCache();       // 创建缓存
    void createCDN();         // 创建CDN
    void createKubernetes();  // 创建K8s(阿里云支持,腾讯云暂不支持)
}

// 腾讯云实现类 - 被迫实现不支持的方法
public class TencentCloud implements CloudProvider {
    @Override
    public void createServer() { /* ... */ }
    
    @Override
    public void deleteServer() { /* ... */ }
    
    @Override
    public void createDatabase() {
        throw new UnsupportedOperationException("暂不支持");
    }
    
    @Override
    public void createCache() { /* ... */ }
    
    @Override
    public void createCDN() { /* ... */ }
    
    @Override
    public void createKubernetes() {
        throw new UnsupportedOperationException("暂不支持");
    }
}

优化方案

java
// ✅ 拆分为最小接口
public interface ComputeService {
    void createServer();
    void deleteServer();
}

public interface DatabaseService {
    void createDatabase();
}

public interface CacheService {
    void createCache();
}

public interface CDNService {
    void createCDN();
}

// 阿里云实现 - 实现需要的接口
public class AliyunProvider implements ComputeService, DatabaseService, 
                                       CacheService, CDNService {
    // 实现所有方法
}

// 腾讯云实现 - 只实现支持的接口
public class TencentProvider implements ComputeService, CacheService, CDNService {
    // 无需实现不支持的功能
}
违反接口隔离原则
违反接口隔离原则
遵循接口隔离原则
遵循接口隔离原则

2.6 依赖倒置原则(DIP)

定义

  1. 高层模块不应该依赖于底层模块,二者都应该依赖于抽象
  2. 抽象不应该依赖于细节,细节应该依赖于抽象

核心思想

  • 细节具有多变性,而抽象相对稳定
  • 以抽象为基础搭建的架构比以细节为基础的架构更稳定
  • 面向接口编程,而不是面向实现编程

违反案例

java
// 具体实现类
public class IntelCPU {
    public void work() { System.out.println("Intel CPU working..."); }
}

public class KingstonMemory {
    public void load() { System.out.println("Kingston Memory loading..."); }
}

public class SeagateDisk {
    public void read() { System.out.println("Seagate Disk reading..."); }
}

// ❌ 高层模块直接依赖底层模块
public class Computer {
    private IntelCPU cpu;           // 直接依赖具体类
    private KingstonMemory memory;  // 直接依赖具体类
    private SeagateDisk disk;       // 直接依赖具体类
    
    public void start() {
        cpu.work();
        memory.load();
        disk.read();
    }
}
// 更换AMD CPU?需要修改Computer类!

优化方案

java
// ✅ 定义抽象接口
public interface CPU {
    void work();
}

public interface Memory {
    void load();
}

public interface Disk {
    void read();
}

// 具体实现
public class IntelCPU implements CPU {
    @Override
    public void work() { System.out.println("Intel CPU working..."); }
}

public class AMDCPU implements CPU {
    @Override
    public void work() { System.out.println("AMD CPU working..."); }
}

// 高层模块依赖抽象
public class Computer {
    private CPU cpu;       // 依赖抽象
    private Memory memory; // 依赖抽象
    private Disk disk;     // 依赖抽象
    
    public Computer(CPU cpu, Memory memory, Disk disk) {
        this.cpu = cpu;
        this.memory = memory;
        this.disk = disk;
    }
    
    public void start() {
        cpu.work();
        memory.load();
        disk.read();
    }
}

// 使用 - 可以轻松更换组件
Computer computer1 = new Computer(new IntelCPU(), new KingstonMemory(), new SeagateDisk());
Computer computer2 = new Computer(new AMDCPU(), new ADATAMemory(), new SanDiskDisk());
违反依赖倒置
违反依赖倒置
遵循依赖倒置
遵循依赖倒置

本章小结

diagram
SOLID原则速记表 S - Single Responsibility 一个类只做一件事 O - Open/Closed 对扩展开放,对修改封闭 L - Liskov Substitution 子类要能替换父类 I - Interface Segregation 接口要小而专 D - Dependency Inversion 依赖抽象,不依赖具体
┌─────────────────────────────────────────────────────────────┐
│  SOLID原则速记表                                            │
├─────────────────────────────────────────────────────────────┤
│  S - Single Responsibility    一个类只做一件事                │
│  O - Open/Closed             对扩展开放,对修改封闭           │
│  L - Liskov Substitution     子类要能替换父类                 │
│  I - Interface Segregation   接口要小而专                     │
│  D - Dependency Inversion    依赖抽象,不依赖具体             │
└─────────────────────────────────────────────────────────────┘

【常见问题】

问题 解答
原则之间有冲突怎么办? 设计原则是指导方针,不是法律。实际应用中需要根据场景权衡,比如追求单一职责可能导致类数量增多
必须全部遵循吗? 不是。但在代码Review时,应该能解释清楚为什么违反某个原则
如何开始学习? 先理解概念,然后在重构代码时思考"这段代码违反了什么原则?"
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 单例模式实现方式对比

实现方式 线程安全 懒加载 性能 复杂度 推荐度
饿汉式 ⭐⭐⭐ ⭐⭐⭐
懒汉式(不安全) ⭐⭐⭐
懒汉式(安全)
双重检查 ⭐⭐⭐ ⭐⭐ ⭐⭐⭐
静态内部类 ⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐
枚举 ⭐⭐⭐ ⭐⭐⭐⭐⭐

【常见问题】

问题 解答
单例模式有什么用? 节省资源(内存、数据库连接等),控制并发访问(如连接池)
如何破坏单例? 反射调用私有构造函数、序列化/反序列化
如何防止破坏? 构造函数中判断实例是否存在;实现readResolve()方法;使用枚举

本章小结

diagram
单例模式要点 • 核心目标:保证一个类只有一个实例,并提供全局访问点 • 推荐实现:枚举(最简单)或静态内部类(最均衡) • 双重检查注意:必须使用volatile修饰实例变量 • 注意防止:反射攻击、序列化破坏、多线程问题
┌─────────────────────────────────────────────────────────────┐
│  单例模式要点                                               │
├─────────────────────────────────────────────────────────────┤
│  • 核心目标:保证一个类只有一个实例,并提供全局访问点          │
│  • 推荐实现:枚举(最简单)或静态内部类(最均衡)              │
│  • 双重检查注意:必须使用volatile修饰实例变量                 │
│  • 注意防止:反射攻击、序列化破坏、多线程问题                 │
└─────────────────────────────────────────────────────────────┘

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 【常见问题】

问题 解答
三种工厂模式怎么选? 产品少且固定用简单工厂;产品多且需扩展用工厂方法;有产品族概念用抽象工厂
工厂模式和单例的关系? 工厂类本身通常用单例实现;工厂生产的对象可以是单例也可以是多例

本章小结

diagram
工厂模式要点 • 简单工厂:一个工厂方法创建多种产品(适合产品少) • 工厂方法:一个工厂创建一种产品(适合产品多、需扩展) • 抽象工厂:创建产品族(适合有配套概念的场景) • 核心思想:将对象创建和使用分离,降低耦合
┌─────────────────────────────────────────────────────────────┐
│  工厂模式要点                                               │
├─────────────────────────────────────────────────────────────┤
│  • 简单工厂:一个工厂方法创建多种产品(适合产品少)           │
│  • 工厂方法:一个工厂创建一种产品(适合产品多、需扩展)        │
│  • 抽象工厂:创建产品族(适合有配套概念的场景)               │
│  • 核心思想:将对象创建和使用分离,降低耦合                   │
└─────────────────────────────────────────────────────────────┘

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个)时;对象构建步骤复杂时
建造者必须链式调用吗? 不是必须,但链式调用是建造者模式的典型特征

本章小结

diagram
建造者模式要点 • 核心思想:分步骤构建复杂对象,链式调用 • 主要优点:可读性好、可以加入校验逻辑 • 典型应用:StringBuilder、Lombok @Builder • 与工厂区别:工厂关注创建,建造者关注构建过程
┌─────────────────────────────────────────────────────────────┐
│  建造者模式要点                                             │
├─────────────────────────────────────────────────────────────┤
│  • 核心思想:分步骤构建复杂对象,链式调用                   │
│  • 主要优点:可读性好、可以加入校验逻辑                     │
│  • 典型应用: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参数开放模块访问权限

本章小结

diagram
代理模式要点 • 核心思想:为目标对象提供一个代理,控制访问并增强功能 • 静态代理:手动编写,简单但代码冗余 • JDK动态代理:基于接口,目标必须实现接口 • Cglib动态代理:基于继承,目标无需接口但不能是final • 典型应用:日志增强、权限控制、延迟加载、远程调用
┌─────────────────────────────────────────────────────────────┐
│  代理模式要点                                               │
├─────────────────────────────────────────────────────────────┤
│  • 核心思想:为目标对象提供一个代理,控制访问并增强功能       │
│  • 静态代理:手动编写,简单但代码冗余                         │
│  • 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 【常见问题】

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

本章小结

diagram
责任链模式要点 • 核心思想:将请求沿处理者链传递,直到被处理 • 主要优点:解耦发送者和接收者、可动态调整链结构 • 典型应用:Filter链、Interceptor链、审批流程 • 注意事项:避免链过长、防止循环依赖
┌─────────────────────────────────────────────────────────────┐
│  责任链模式要点                                             │
├─────────────────────────────────────────────────────────────┤
│  • 核心思想:将请求沿处理者链传递,直到被处理               │
│  • 主要优点:解耦发送者和接收者、可动态调整链结构            │
│  • 典型应用:Filter链、Interceptor链、审批流程              │
│  • 注意事项:避免链过长、防止循环依赖                        │
└─────────────────────────────────────────────────────────────┘
06 / Section

六、课程总结

6.1 知识点回顾

diagram
设计模式全景图 【设计原则】 S(单一职责) O(开闭原则) L(里氏替换) I(接口隔离) D(依赖倒置) 【创建型模式】5种 单例 工厂 建造者 原型 抽象工厂 【结构型模式】7种 代理 装饰器 适配器 桥接 外观 组合 享元 【行为型模式】11种 观察者 策略 模板 责任链 迭代器 状态 命令 备忘录 访问者 中介者 解释器
┌─────────────────────────────────────────────────────────────┐
│                    设计模式全景图                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   【设计原则】                                                │
│   S(单一职责) O(开闭原则) L(里氏替换) I(接口隔离) D(依赖倒置)  │
│                                                             │
│   【创建型模式】5种                                           │
│   单例 工厂 建造者 原型 抽象工厂                               │
│                                                             │
│   【结构型模式】7种                                           │
│   代理 装饰器 适配器 桥接 外观 组合 享元                       │
│                                                             │
│   【行为型模式】11种                                          │
│   观察者 策略 模板 责任链 迭代器 状态 命令 备忘录 访问者 中介者 解释器 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

6.2 设计模式选择指南

场景 推荐模式 说明
全局只需要一个实例 单例 配置类、连接池等
根据不同条件创建对象 工厂 降低耦合,便于扩展
对象创建步骤复杂 建造者 链式调用,清晰构建
需要增强方法功能 代理 日志、事务、权限等
处理流程固定但步骤可变 模板方法 骨架固定,细节可变
需要动态替换算法 策略 避免大量if-else
一对多依赖通知 观察者 事件监听、消息订阅
多层级审批/处理 责任链 动态指定处理者

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*