← 返回文章

常用设计模式

发布于 2026/10/10

1. 完整回答

设计模式是软件开发中针对常见设计问题总结出来的一套成熟解决方案,主要用来降低代码耦合、提高复用性和扩展性,让系统在需求变化时更容易维护。

经典的 GoF 设计模式一共有 23 种,分为创建型、结构型和行为型三大类。

创建型模式主要解决对象如何创建的问题。比如单例模式保证特定范围内只有一个实例,工厂模式把对象创建过程封装起来,让业务代码减少对具体实现类的依赖。

结构型模式主要解决对象之间如何组合的问题。比如代理模式可以在调用目标对象前后增加权限校验、日志等逻辑;装饰器模式可以在保留原有接口的基础上,灵活增加功能。

行为型模式主要解决对象之间如何协作,以及算法如何切换的问题。比如策略模式把不同算法封装成独立实现,可以根据业务条件选择;观察者模式通过事件通知,让多个处理模块之间保持松耦合。

实际开发中,这些模式经常结合使用。比如在 Spring 项目里,可以通过工厂和依赖注入管理对象,通过策略模式实现不同业务规则,通过代理模式实现事务管理,通过责任链模式组织多个请求处理环节。

设计模式的核心思想是封装变化、面向抽象编程、优先使用组合降低耦合。我会根据业务复杂度和扩展需求选择合适的模式,避免为了使用设计模式而增加不必要的抽象。

2. 核心原理与关键概念

2.1 三大类设计模式

分类 解决的问题 常用模式
创建型 对象如何创建,如何隐藏创建细节 单例、工厂方法、抽象工厂、建造者
结构型 对象如何组合、包装和复用 代理、装饰器、适配器、外观
行为型 对象如何协作、分配职责和切换算法 策略、观察者、模板方法、责任链

2.2 核心设计思想

设计模式通常围绕三个思路展开。

第一,封装变化。 将容易变化的部分独立出来,比如不同支付方式、不同业务校验规则,减少修改对其他代码的影响。

第二,面向接口编程。 业务代码依赖统一抽象,通过多态调用不同实现,降低对具体类的依赖。

第三,组合优于继承。 通过持有其他对象来复用和扩展功能,通常比多层继承更灵活。

典型的设计过程是:

识别业务中的变化点 → 提取统一抽象 → 将不同实现独立封装 → 通过组合、工厂或依赖注入组织对象 → 由业务代码调用统一接口。

2.3 常用模式的工作机制

设计模式 核心机制 典型应用
单例模式 控制实例创建,提供统一访问入口 全局配置管理
工厂模式 封装具体对象的创建逻辑 多种服务实现的创建
策略模式 定义统一接口,动态选择算法实现 支付、计费、优惠计算
代理模式 通过代理对象控制目标对象的访问 Spring AOP、事务
装饰器模式 包装原对象,逐层增加功能 Java IO 流
观察者模式 发布事件,通知订阅者 Spring 应用事件
模板方法模式 固定处理流程,允许部分步骤变化 通用业务处理流程
责任链模式 按顺序传递请求,由各节点处理或放行 过滤器链、审批流程

3. 高频面试追问

3.1 单例模式有哪些实现方式?如何保证线程安全?

单例模式的核心是控制对象的实例数量,让同一个类在特定作用范围内只创建一个实例。

Java 中常见的实现方式有饿汉式、懒汉式、双重检查锁、静态内部类和枚举。

饿汉式在类初始化时创建实例,依靠 JVM 的类初始化机制保证线程安全,但如果对象始终没有被使用,也会提前占用资源。

懒汉式在第一次访问时创建实例,普通的非同步写法可能产生并发创建问题。

实际开发中,我更倾向于使用静态内部类或者枚举实现单例。 静态内部类利用 JVM 的类初始化机制实现线程安全的延迟初始化;枚举实现更简洁,还能防止常规反射创建和反序列化产生额外实例。

如果使用双重检查锁,需要结合 volatile 和 synchronized。同步锁保证只有一个线程初始化实例,volatile 保证可见性和安全发布,避免其他线程看到未正确初始化的对象。

另外,单例对象本身的线程安全仍然取决于内部状态和方法实现。

继续追问:Spring 的 singleton 和普通单例模式有什么区别?

Spring 的 singleton 是容器级别的单例,同一个 Spring 容器中,同一个 Bean 定义默认共享一个实例。

普通单例通常通过类自身控制实例创建,作用范围通常与类加载器相关。

所以,多个 Spring 容器可以分别创建自己的单例 Bean,同一个类也可以通过不同 Bean 定义创建多个实例。

另外,Spring 单例 Bean 默认被多个线程共享,如果内部存在可变成员变量,同样需要考虑线程安全。

3.2 简单工厂、工厂方法和抽象工厂有什么区别?

三者都用于封装对象创建逻辑,主要区别是创建职责如何分配,以及支持怎样的扩展。

简单工厂通常通过一个工厂类,根据传入的类型参数决定创建哪个具体对象。例如,根据支付类型创建支付宝或微信支付对象。

工厂方法将创建对象的职责交给不同的具体工厂实现。新增产品时,可以增加对应的工厂类,减少对原有创建逻辑的修改。

抽象工厂进一步支持创建一组相互关联的对象。例如,一个数据库组件工厂同时创建对应数据库的连接组件、命令组件等,确保它们来自同一产品系列。

实际选择时,如果只有少量对象需要统一创建,我会优先考虑简单工厂;如果产品类型需要独立扩展,可以考虑工厂方法;如果要统一切换整组相关组件,就适合抽象工厂。

另外,简单工厂是一种常见的创建方式,没有被单独列入经典的 23 种 GoF 设计模式。

3.3 建造者模式解决什么问题?和构造方法有什么区别?

建造者模式主要用于创建参数较多、配置复杂的对象,让对象构建过程更清晰。

例如创建一个 HTTP 请求对象,可能包含请求地址、请求头、超时时间、认证信息等参数。

如果全部放在构造方法中,参数会很多,也容易出现顺序传错或者大量传入空值的问题。

建造者模式将构建过程拆成多个配置步骤:

创建 Builder → 设置必要参数 → 设置可选参数 → 执行参数校验 → 调用 build() 生成对象。

这样可以提高可读性,也便于在最终构建时集中校验参数。

对于字段较少、构造逻辑简单的对象,我会直接使用构造方法。复杂配置对象或者不可变对象更适合 Builder。

3.4 策略模式如何替代大量 if-else?它的局限是什么?

策略模式适合处理多种实现具有相同业务目标,但具体处理规则不同的场景。

比如订单优惠计算,可能有满减、折扣、会员优惠等方式。

如果把所有逻辑写在一个方法里,随着优惠类型增加,条件分支会越来越复杂。

我会定义一个统一的优惠计算接口,让每种优惠规则实现自己的计算逻辑,再由业务代码根据优惠类型选择对应的策略。

执行流程是:

定义策略接口 → 实现不同策略 → 注册策略 → 根据业务条件选择策略 → 调用统一接口。

在 Spring 中,可以通过依赖注入结合 Map 建立策略类型和实现之间的映射,避免在业务代码中不断增加条件分支。

不过,策略模式并不意味着完全消除条件判断。系统仍然需要决定使用哪个策略,只是把选择逻辑集中管理,把具体算法独立出来。

如果只有两种非常简单、几乎不会变化的逻辑,直接使用条件判断可能更清晰。

3.5 代理模式的原理是什么?Spring AOP 如何实现动态代理?

代理模式通过代理对象间接访问目标对象,可以在目标方法执行前后增加统一处理逻辑。

常见的代理方式有静态代理和动态代理。

静态代理需要提前编写代理类,代理类和目标对象通常实现相同接口,由代理类控制对目标对象的调用。

动态代理可以在运行时生成代理类,减少重复编写代理代码。

Spring AOP 主要使用两种动态代理机制:

  • JDK 动态代理:基于接口生成代理对象,通过 InvocationHandler 处理方法调用。

  • CGLIB 代理:通过生成目标类的子类实现代理,依靠方法重写完成拦截。

Spring 的代理方式还受框架配置影响,例如 Spring Boot 中可能默认启用基于类的代理。

以事务管理为例:

调用代理对象 → 事务拦截器开启事务 → 执行目标方法 → 成功后提交事务,异常时根据事务规则回滚。

这样可以把事务、日志、权限校验等通用逻辑集中处理,减少业务代码中的重复实现。

CGLIB 由于依赖继承,无法代理 final 类,也无法拦截 final 方法和 private 方法。

继续追问:为什么 Spring 同一个类内部调用事务方法,@Transactional 可能失效?

因为 Spring 声明式事务通常通过 AOP 代理实现。

当外部对象调用代理方法时,请求会经过事务拦截器。但如果类内部直接通过 this 调用另一个事务方法,这次调用直接发生在目标对象内部,没有经过代理,事务拦截器就无法针对这次调用生效。

比较常见的解决方式是将事务方法拆分到另一个 Spring Bean 中,通过注入的 Bean 调用,让调用经过代理对象。

3.6 装饰器模式和代理模式有什么区别?

两者通常都通过包装对象实现,核心区别在于设计目的。

装饰器模式主要用于动态扩展对象功能,可以通过多层包装组合不同能力。

例如 Java IO 中,可以通过 BufferedInputStream 包装一个 FileInputStream,在原有文件读取能力上增加缓冲功能。

代理模式更关注控制目标对象的访问过程,例如权限检查、延迟加载、事务控制等。

从结构上看,两者都可能持有目标对象并实现相同接口。

因此,判断一个包装类更接近哪种模式,主要看它是为了组合新功能,还是为了控制对目标对象的访问。

3.7 观察者模式的原理是什么?它一定是异步的吗?

观察者模式通过发布事件和通知订阅者,实现对象之间的松耦合。

一般包含事件发布者和观察者两部分。

发布者维护订阅关系,当事件发生时通知对应观察者,观察者再执行各自的处理逻辑。

基本流程是:

观察者注册监听 → 发布者产生事件 → 通知对应观察者 → 观察者处理事件。

例如订单创建成功后,需要执行通知用户、记录操作日志、更新统计信息等操作。

可以把这些后续操作拆成多个事件监听器,订单核心流程只负责发布事件。

观察者模式本身没有规定必须同步或异步执行。

例如,Spring 默认的应用事件监听是同步执行的,发布事件的方法需要等待监听器完成。也可以配置异步处理。

如果需要跨服务通信、消息持久化、失败重试等能力,通常会引入 Kafka、RabbitMQ 等消息中间件。

另外,涉及事务提交后的业务操作时,需要考虑事件发布时机,避免事务最终回滚,但外部通知已经发送。

3.8 模板方法模式的核心原理是什么?和策略模式怎么选择?

模板方法模式适合业务处理流程基本固定,但其中部分步骤存在差异的场景。

它通常通过抽象父类定义完整流程,再把部分具体步骤交给子类实现。

例如不同类型的文件导入,都需要执行:

校验文件 → 解析内容 → 转换数据 → 保存数据 → 返回处理结果。

其中整体处理顺序固定,但不同文件格式的解析逻辑不同。

我会在抽象父类中定义统一流程,让不同子类实现各自的解析方法。

模板方法模式主要依靠继承扩展流程中的步骤;策略模式主要依靠组合替换独立算法。

如果业务流程和执行顺序固定,我会优先考虑模板方法;如果要在运行时灵活切换整个算法实现,策略模式会更合适。

3.9 责任链模式是怎么工作的?有哪些实际应用?

责任链模式把多个处理节点连接起来,让请求依次经过这些节点,从而避免调用方依赖具体处理类。

每个节点只负责自己的处理逻辑,并决定是否继续传递请求。

基本流程是:

接收请求 → 节点 A 处理 → 节点 B 处理 → 节点 C 处理 → 返回结果。

例如接口请求可以依次经过参数校验、身份认证、权限校验等环节。

如果认证失败,可以直接终止后续流程;如果通过,就继续交给下一个节点。

Java Servlet Filter 和 Spring Security 的过滤器链都体现了这种链式处理思想。

责任链模式的优点是处理节点可以独立维护、灵活组合,缺点是调用链过长时,排查问题会比较困难。

因此,实际使用时需要明确节点执行顺序、终止条件以及异常处理规则。

3.10 实际项目中如何选择设计模式?如何避免过度设计?

我会先看业务的变化点和重复逻辑,再决定是否需要引入设计模式。

比如设计一个订单结算模块,系统需要支持普通会员、VIP 会员和活动用户的不同优惠计算规则。

最初可以使用简单的条件判断。如果后续优惠规则不断增加,而且每种规则都有独立的计算过程,我会考虑使用策略模式。

具体可以定义统一的优惠计算接口,将不同规则封装成独立实现,再通过 Spring 依赖注入管理这些策略,根据用户类型和活动信息选择对应实现。

这样新增优惠规则时,主要增加一个实现类,原有结算流程基本不用修改。

如果还需要创建不同类型的复杂优惠对象,可以结合工厂模式;如果结算成功后需要触发多个独立操作,可以使用观察者模式或事件机制。

不过,我会控制抽象层级。对于只有一两种实现、变化可能性很低的功能,直接编写清晰的业务代码通常更容易维护。

4. 易混淆概念对比

4.1 简单工厂 vs 工厂方法 vs 抽象工厂

对比项 简单工厂 工厂方法 抽象工厂
核心作用 集中创建不同对象 由不同具体工厂决定创建对象 创建一组相关对象
实现方式 一个工厂根据条件选择 定义工厂接口或抽象类,由子类实现 定义多个相关产品的创建接口
扩展方式 通常修改工厂的判断逻辑 增加具体工厂 增加新的产品族工厂
适用场景 少量支付方式 可独立扩展的产品类型 数据库组件、跨平台 UI 组件

4.2 策略模式 vs 状态模式

对比项 策略模式 状态模式
核心目的 切换不同算法 根据内部状态改变行为
切换依据 业务条件或外部选择 状态变化和状态转换规则
实现特点 各策略通常相互独立 状态实现可能参与状态转换
应用场景 支付方式、优惠计算 订单状态流转、工作流

核心区别: 策略模式关注算法选择,状态模式关注状态变化及对应的行为。

4.3 代理模式 vs 装饰器模式

对比项 代理模式 装饰器模式
核心目的 控制对象访问 扩展对象功能
主要特点 调用前后增加控制逻辑 支持多层功能包装
应用场景 Spring AOP、权限控制 IO 流、功能增强
设计关注点 如何访问目标对象 如何组合附加功能

4.4 策略模式 vs 模板方法模式

对比项 策略模式 模板方法模式
实现基础 组合与接口 继承与方法重写
变化部分 整个算法实现 固定流程中的部分步骤
运行时切换 可以替换策略对象 通常通过选择不同子类实现
适用场景 动态选择计算规则 固定业务处理流程

4.5 适配器模式 vs 外观模式

对比项 适配器模式 外观模式
核心目的 解决接口不兼容问题 简化复杂子系统的调用
处理方式 将现有接口转换为目标接口 对多个内部组件提供统一入口
应用场景 对接不同第三方接口 统一封装复杂业务调用
典型例子 将第三方支付 API 适配为统一支付接口 一个接口统一完成下单、库存和支付相关调用

4.6 观察者模式 vs 发布订阅模式

对比项 观察者模式 发布订阅模式
核心机制 发布者通知已注册的观察者 通过事件通道分发消息
对象关系 发布者通常维护观察者引用 可以通过事件总线或消息代理进一步解耦
典型范围 常见于进程内部 可以支持进程内或跨服务
常见应用 Spring 应用事件、GUI 事件 Kafka、RabbitMQ、事件总线

两者都可以实现事件驱动,同步和异步并非它们的严格分界线。具体能力取决于实现机制。

5. 总结

设计模式是针对常见软件设计问题形成的一套可复用解决方案,核心作用是降低耦合、封装变化、提高系统可扩展性。

从机制上看,创建型模式负责对象创建,结构型模式负责对象组合与功能包装,行为型模式负责算法切换和对象协作。

实际开发中,我比较常用工厂模式封装对象创建,策略模式处理不同业务规则,代理模式实现事务和权限控制,观察者模式处理事件通知,责任链模式组织多个处理节点。

这些模式的共同思想是面向抽象编程,通过封装和组合隔离变化。不过,设计模式会引入额外的抽象和类结构,实际使用时需要结合业务复杂度选择,以代码清晰、容易扩展和方便维护为主要目标。