学习目标:
- 理解设计模式的价值,并能说清为什么大型项目需要设计模式
- 掌握 GoF 23 种设计模式的分类方式与核心学习路径
- 能结合 SOLID 原则分析代码设计是否合理
- 掌握单例、工厂、建造者、代理、责任链等常用模式的典型实现
- 能在实际开发、源码阅读和面试场景中识别并应用常见设计模式
本章重点:
- 设计模式与设计原则(SOLID)的关系
- 创建型、结构型、行为型三大类模式的划分逻辑
- 单例、工厂、代理、责任链等重点模式的实现思路与适用场景
- 常见设计模式在实际开发中的应用思路
一、设计模式简介
1.1 什么是设计模式
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。设计模式代表了最佳的实践,通常被有经验的软件开发人员所使用。
大白话理解:设计模式是前人总结的一套经验。按照前人总结的经验去设计架构、编写代码,有很多好处:
- ✅ 后期可维护性强 - 代码结构清晰,易于理解和修改
- ✅ 耦合度低 - 模块之间依赖关系合理,改动一处不会牵一发而动全身
- ✅ 代码复用性高 - 避免重复造轮子,提高开发效率
- ✅ 代码通俗易懂 - 遵循业界公认的规范,降低团队协作成本
- ✅ 健壮性强 - 经过大量实践验证的解决方案,稳定性更高
💡 类比理解:在建筑领域,如果你只是希望盖一个茅草屋,那么无需任何模式;但是如果你希望建造一座摩天大楼,那么必须要有设计模式和规范。
1.2 为什么学习设计模式
| 场景 | 说明 |
|---|---|
| 面试必备 | 面对面试中的设计模式问题,如单例模式的几种实现方式、懒汉式和饿汉式的区别 |
| 阅读源码 | 有助于阅读经典项目和优质代码实现,是进阶提升的必备技能 |
| 编写高效代码 | 借助设计模式可以编写出高复用性、高稳健性的代码 |
| 系统设计 | 在设计大型系统时,合理运用设计模式可以让系统更易于扩展和维护 |
1.3 设计模式的发展历史
设计模式最早并非来自软件领域,而是来自建筑领域:
- 1977年:建筑师克里斯托佛·亚历山大在其著作《建筑模式语言》中首次提出模式概念,描述窗户高度、建筑层数、街区植被面积等设计规范。
- 1994年:四位软件工程师(埃里希·伽玛、约翰·弗利赛德斯、拉尔夫·约翰逊、理查德·赫尔姆)接受模式概念,出版《设计模式:可复用面向对象软件的基础》,将23个设计模式引入程序开发领域。
- Gang of Four (GoF):由于四位作者名字较长,业界简称"四人组"或"GoF",他们提出的23种设计模式被称为"GoF设计模式"。

1.4 设计模式的分类
GoF设计模式共有23种,根据用途分为三大类:
📦 创建型模式(5种)
核心作用:提供创建对象的机制,提升代码的灵活性和可复用性。
| 模式 | 英文名 | 一句话描述 |
|---|---|---|
| 单例模式 | Singleton | 保证一个类只有一个实例 |
| 工厂方法模式 | Factory Method | 定义创建对象的接口,让子类决定实例化哪个类 |
| 抽象工厂模式 | Abstract Factory | 创建相关或依赖对象的家族,而无需明确指定具体类 |
| 建造者模式 | Builder | 分步骤创建复杂对象 |
| 原型模式 | Prototype | 通过复制现有对象来创建新对象 |




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







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










本章小结
┌─────────────────────────────────────────────────────────────┐ │ 设计模式核心要点 │ ├─────────────────────────────────────────────────────────────┤ │ • 23种GoF设计模式,分为创建型、结构型、行为型三大类 │ │ • 设计模式是前人经验的总结,不是银弹,需根据场景合理选择 │ │ • 学习设计模式有助于面试、读源码、写高质量代码 │ └─────────────────────────────────────────────────────────────┘
二、设计模式原则(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类,包含以下功能:
- 管理雇员信息(核心职责)
- 打印雇员信息(辅助功能)

问题:修改该类的原因有两个(管理信息 + 打印格式),违反单一职责原则。
优化方案:
// 雇员类 - 只负责雇员信息管理
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设计:
// 抽象接口 - 定义结构
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类型的对象,则它的行为也理应和预期的行为一致。
核心思想:
- 子类必须保持与父类行为的兼容
- 重写方法时,应该对基类行为进行扩展,而不是完全替换
- 在使用父类的程序中,替换为使用子类,程序的运行结果应该是一致的
违反案例:
// 父类:矩形
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 : "面积计算错误!";
}正确方案:
// 抽象父类
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)
定义:又叫最少知道原则,一个类/模块对于其他类/模块有越少的了解越好。
核心思想:
- 不应该有依赖关系的类之间,不要存在依赖关系
- 有依赖关系的类之间,尽量只依赖于接口
- 只与直接的朋友通信
什么是"直接的朋友"?
- ✅ 成员变量中的类
- ✅ 方法参数中的类
- ✅ 方法返回值中的类
- ❌ 局部变量中的类(不是直接朋友)
案例 - 明星与经纪人:
// 违反迪米特法则:明星亲力亲为处理所有事务
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() { /* 只专注表演 */ }
}

案例 - 老师让班长点名:
// ❌ 违反迪米特法则:老师直接依赖了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)
定义:一个类对另一个类的依赖应当建立在最小的接口上。
核心思想:
- 为各个类建立它们需要的专用接口
- 不要试图建立一个很庞大的接口供所有依赖它的类去调用
- 接口应该小而专,而不是大而全
违反案例:
假设开发一套云服务器整合程序,设计一个大而全的接口:
// ❌ 庞大接口,违反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("暂不支持");
}
}优化方案:
// ✅ 拆分为最小接口
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)
定义:
- 高层模块不应该依赖于底层模块,二者都应该依赖于抽象
- 抽象不应该依赖于细节,细节应该依赖于抽象
核心思想:
- 细节具有多变性,而抽象相对稳定
- 以抽象为基础搭建的架构比以细节为基础的架构更稳定
- 面向接口编程,而不是面向实现编程
违反案例:
// 具体实现类
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类!优化方案:
// ✅ 定义抽象接口
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());

本章小结
┌─────────────────────────────────────────────────────────────┐ │ SOLID原则速记表 │ ├─────────────────────────────────────────────────────────────┤ │ S - Single Responsibility 一个类只做一件事 │ │ O - Open/Closed 对扩展开放,对修改封闭 │ │ L - Liskov Substitution 子类要能替换父类 │ │ I - Interface Segregation 接口要小而专 │ │ D - Dependency Inversion 依赖抽象,不依赖具体 │ └─────────────────────────────────────────────────────────────┘
【常见问题】
| 问题 | 解答 |
|---|---|
| 原则之间有冲突怎么办? | 设计原则是指导方针,不是法律。实际应用中需要根据场景权衡,比如追求单一职责可能导致类数量增多 |
| 必须全部遵循吗? | 不是。但在代码Review时,应该能解释清楚为什么违反某个原则 |
| 如何开始学习? | 先理解概念,然后在重构代码时思考"这段代码违反了什么原则?" |
三、创建型模式
创建型模式提供了创建对象的机制,能够提升已有代码的灵活性和可复用性。
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 单例模式实现方式对比
| 实现方式 | 线程安全 | 懒加载 | 性能 | 复杂度 | 推荐度 |
|---|---|---|---|---|---|
| 饿汉式 | ✅ | ❌ | ⭐⭐⭐ | ⭐ | ⭐⭐⭐ |
| 懒汉式(不安全) | ❌ | ✅ | ⭐⭐⭐ | ⭐ | ❌ |
| 懒汉式(安全) | ✅ | ✅ | ⭐ | ⭐ | ⭐ |
| 双重检查 | ✅ | ✅ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
| 静态内部类 | ✅ | ✅ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| 枚举 | ✅ | ❌ | ⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐⭐ |
【常见问题】
| 问题 | 解答 |
|---|---|
| 单例模式有什么用? | 节省资源(内存、数据库连接等),控制并发访问(如连接池) |
| 如何破坏单例? | 反射调用私有构造函数、序列化/反序列化 |
| 如何防止破坏? | 构造函数中判断实例是否存在;实现readResolve()方法;使用枚举 |
本章小结
┌─────────────────────────────────────────────────────────────┐ │ 单例模式要点 │ ├─────────────────────────────────────────────────────────────┤ │ • 核心目标:保证一个类只有一个实例,并提供全局访问点 │ │ • 推荐实现:枚举(最简单)或静态内部类(最均衡) │ │ • 双重检查注意:必须使用volatile修饰实例变量 │ │ • 注意防止:反射攻击、序列化破坏、多线程问题 │ └─────────────────────────────────────────────────────────────┘
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 知识点回顾
┌─────────────────────────────────────────────────────────────┐ │ 设计模式全景图 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 【设计原则】 │ │ S(单一职责) O(开闭原则) L(里氏替换) I(接口隔离) D(依赖倒置) │ │ │ │ 【创建型模式】5种 │ │ 单例 工厂 建造者 原型 抽象工厂 │ │ │ │ 【结构型模式】7种 │ │ 代理 装饰器 适配器 桥接 外观 组合 享元 │ │ │ │ 【行为型模式】11种 │ │ 观察者 策略 模板 责任链 迭代器 状态 命令 备忘录 访问者 中介者 解释器 │ │ │ └─────────────────────────────────────────────────────────────┘
6.2 设计模式选择指南
| 场景 | 推荐模式 | 说明 |
|---|---|---|
| 全局只需要一个实例 | 单例 | 配置类、连接池等 |
| 根据不同条件创建对象 | 工厂 | 降低耦合,便于扩展 |
| 对象创建步骤复杂 | 建造者 | 链式调用,清晰构建 |
| 需要增强方法功能 | 代理 | 日志、事务、权限等 |
| 处理流程固定但步骤可变 | 模板方法 | 骨架固定,细节可变 |
| 需要动态替换算法 | 策略 | 避免大量if-else |
| 一对多依赖通知 | 观察者 | 事件监听、消息订阅 |
| 多层级审批/处理 | 责任链 | 动态指定处理者 |
6.3 学习建议
- 先理解原则,再学习模式:SOLID原则是设计模式的指导思想
- 不要过度设计:简单问题不要强行使用复杂模式
- 从经典实现中学习:阅读JDK和优秀开源项目中的模式应用
- 重构练习:在重构代码时思考可以应用哪些模式
6.4 参考资料
- 《设计模式:可复用面向对象软件的基础》(GoF经典)
- 《Head First 设计模式》(入门推荐)
- 《Java与模式》(阎宏,中文经典)
- JDK源码与经典开源项目示例
<!-- 实战练习内容已分离到 practices/10-design-pattern-practice.md -->
建议先回顾本章重点内容,再进入配套练习。 练习顺序建议:设计原则辨析 → 常用模式实现 → 场景分析 → 综合重构。 如果你是第一次系统学习设计模式,优先保证“能识别场景、能说清为什么用”,再追求手写复杂实现。
*本文档由JavaEE课程组编写,最后更新时间:2026-03-18*