设计模式

设计模式简介

什么是设计模式

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

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

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

为什么学习设计模式

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

设计模式的发展历史

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

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

设计模式的分类

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给定一个语言,定义它的文法表示,并定义一个解释器

设计模式原则(SOLID)

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

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

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

单一职责原则(SRP)

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

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

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

案例分析

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

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

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

优化方案

// 雇员类 - 只负责雇员信息管理
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() { /* ... */ }
}
遵循单一职责
遵循单一职责

优点

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

开放封闭原则(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,即可支持新的数据库
}
开放封闭原则
开放封闭原则

优点

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

里氏替换原则(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
}
里氏替换原则
里氏替换原则
里氏替换原则改进
里氏替换原则改进

迪米特法则(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));
    }
}

接口隔离原则(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 {
    // 无需实现不支持的功能
}
违反接口隔离原则
违反接口隔离原则
遵循接口隔离原则
遵循接口隔离原则

依赖倒置原则(DIP)

定义

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

核心思想

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

违反案例

// 具体实现类
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    依赖抽象,不依赖具体             │
└─────────────────────────────────────────────────────────────┘

创建型模式

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

单例模式(Singleton)

模式定义

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

单例模式UML
单例模式UML

对应的UML类图:

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

适用场景

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

实现方式一:饿汉模式

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

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

特点分析

优点缺点
线程安全(类加载机制保证)不支持懒加载
实现简单,无需加锁如果对象较大且一直未使用,浪费内存
获取对象速度快无法传递参数初始化

实现方式二:懒汉模式(线程不安全)

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先通过空判断

还没来得及创建实例就被切换走。线程 B也通过空判断

创建了实例并返回。

线程 A 恢复后再次创建对象,最终出现多个实例。

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

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()都需要获取锁
  • 实际上只需要针对最开始创建实例时加锁
  • 实例创建完成后,后续调用应该直接返回,无需加锁

实现方式四:双重检查(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禁止指令重排序,保证对象的正确发布

实现方式五:静态内部类

/**
 * 静态内部类实现单例
 * 特点:懒加载 + 线程安全 + 代码简洁
 * 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的类加载机制保证类初始化是线程安全的
  • 兼顾了懒汉式的延迟加载和饿汉式的线程安全

实现方式六:枚举

/**
 * 枚举实现单例
 * 特点:最简单、最安全的实现方式
 * 天然防止反射攻击和序列化问题
 */
public enum Singleton6 {
    INSTANCE;

    // 可以添加业务方法
    public void doSomething() {
        System.out.println("Doing something...");
    }
}

// 使用
Singleton6.INSTANCE.doSomething();

枚举的优势

  • ✅ 线程安全(JVM保证)
  • ✅ 防止反射攻击(反射无法创建枚举实例)
  • ✅ 防止序列化破坏(枚举序列化有特殊处理)
  • ✅ 代码极简

单例模式实现方式对比

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

【常见问题】

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

工厂模式(Factory)

模式定义

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

工厂模式分为三种:

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

简单工厂模式

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

案例 – 特斯拉汽车工厂

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

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

工厂方法模式

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

适用场景

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

案例 – 特斯拉生产车间

// 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 工厂方法

对比项简单工厂工厂方法
复杂度简单较复杂
扩展性修改工厂类新增工厂类
产品数量适合少量产品适合大量产品
开闭原则违反遵循

抽象工厂模式

定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

与工厂方法的区别

  • 工厂方法:生产单一产品
  • 抽象工厂:生产产品族(一系列相关产品)

案例 – 智能家居产品族

假设有两个家电厂商(小米、海尔),都有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));
    }
}

抽象工厂优缺点

优点缺点
确保产品族的一致性扩展新产品困难(需要修改抽象工厂接口)
隔离了具体类的生成系统复杂度增加
符合开闭原则(扩展产品族)

常见问题

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

建造者模式(Builder)

模式定义

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

适用场景

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

经典实现

案例 – 手机组装

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

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

变种:带校验的建造者

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

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

常见问题

问题解答
建造者和工厂的区别?工厂关注”创建什么对象”,建造者关注”如何一步步构建对象”
什么时候用建造者?构造方法参数过多(超过4个)时;对象构建步骤复杂时
建造者必须链式调用吗?不是必须,但链式调用是建造者模式的典型特征

结构型模式

代理模式(Proxy)

模式定义

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

代理结构:

代理结构
代理结构

代理模式类型

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

常见应用场景

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

静态代理

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

静态代理优缺点

优点缺点
实现简单直观代码冗余,每代理一个类需编写一个代理类
不修改目标类即可扩展功能接口变更时,代理类和目标类都要修改

JDK动态代理

原理:在运行时动态生成代理类的字节码,无需手动编写代理类。

限制条件:目标类必须实现接口

核心API

类/接口方法说明
java.lang.reflect.ProxynewProxyInstance(ClassLoader, Class<?>[], InvocationHandler)创建代理对象
java.lang.reflect.InvocationHandlerinvoke(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);
        }
    }
}

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
Cglib兼容性
Cglib兼容性

三种代理对比

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

常见问题

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

行为型模式

责任链模式(Chain of Responsibility)

模式定义

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

类图关系:

责任链类图
责任链类图

适用场景

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

常见应用

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

经典实现

案例 – 三级审批流程

/**
 * 抽象处理者
 */
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
【一级处理器】正在处理...
【二级处理器】正在处理...
【三级处理器】正在处理...

常见问题

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

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇