内容稍长一些,但都是我结合实践总结的经验与思考,如果你也对设计模式感兴趣,欢迎耐心读完。
提到设计模式,其实有很多话题可以展开。比如,在过去的面试过程中,我经常会问候选人一些与设计模式相关的问题,结果发现不少人对设计模式的理解存在一个常见误区:
过于关注模式的“样子”,却忽略了它试图解决的“问题”。
也就是说,很多人努力去记住某个模式的结构或类图,却没有真正理解设计模式的核心价值——解决特定场景下的设计问题。
本文将从一个设计模式学习者的角度,简要分享我在学习过程中的一些体会与心得,主要包括以下几个方面:
在深入设计模式之前,我们最好先掌握哪些基础知识?比如面向对象编程的基本原则、常见的设计原则(如 SOLID)、重构手法等。
回顾自己的学习过程,大致可以分为以下四个阶段:
最后,回顾一下常见的学习误区,比如“死记硬背结构”、“脱离上下文生搬硬套”等。理解这些问题,有助于我们更有效地掌握并运用设计模式。
01
以 Java 为例,如果你还不太清楚什么是封装、继承、抽象、多态,那么建议先深入理解这些核心特性。因为在设计模式中,尤其是抽象和多态的使用非常普遍,是很多模式实现的基础。
设计模式通常通过 UML 的类图和时序图来描述结构与行为,掌握这些图示有助于更直观地理解模式中的对象关系和交互过程。
例如,在讲解观察者模式时,类图能够清晰展示观察者与被观察者的继承结构,而时序图则能揭示它们之间的消息传递过程。

、

以下是 UML 类图中几种常见关系的简要说明:
is-a 关系)。has-a 关系),其中组合关系的耦合程度更强。uses-a 关系)。而时序图主要用于展示对象在运行时的交互顺序,对于理解行为型模式(如责任链、命令、状态等)尤其重要。
如果你不熟悉这些内容,不妨先通过一些 UML 教程、相关书籍或文章打好基础。尽管 UML 并不是学习设计模式的前提条件,但它无疑会帮助你更高效地理解设计意图与实现细节。
有过实际项目经验,会让你更容易在具体情境中感知“为什么需要某个设计模式”,而不是仅仅从书本上记住“它长什么样”。只有在面对真实复杂度时,模式的价值才会真正显现。
补充说明: 即使缺乏实际项目经验,也完全可以通过阅读优秀的源码来理解设计模式在真实场景中的应用方式。 重点是观察模式在什么场景下被使用,以及它们是如何解决具体问题的。推荐的学习材料包括:JDK、Spring、MyBatis、Guava 等开源项目,这些源码中大量运用了经典的设计模式,极具参考价值。
接下来,我想结合自己的经历,聊一聊我是如何一步步学习和理解设计模式的,从入门到实践,大致经历了以下几个阶段:
02
我第一次接触设计模式,是在大学的《设计模式》课程上——当时是必修还是选修已记不清,老师选用的教材正是:

另外,我还专门购买了下面这本“砖头书”作为辅导资料。个人觉得这本书内容扎实、讲解深入,是一本非常值得参考的好书。

这个阶段的学习相对中规中矩,主要是为了应对考试的需要。课程内容涵盖了设计模式的起源、基本概念、构成要素、设计原则、分类方式,以及课堂上重点讲解的十个左右的典型设计模式。
设计模式最初源自建筑学领域,后来由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides(合称 GoF:Gang of Four)将这一思想引入软件工程。1994 年,他们总结并发表了《Design Patterns: Elements of Reusable Object-Oriented Software》一书,系统归纳了 23 种在软件开发中高频使用的设计模式。 他们的目标是通过模式建立一套通用语言,弥合面向对象方法在分析、设计和实现阶段之间的鸿沟。
设计模式本质上是对重复出现问题及其解决方案的总结与抽象。每一个模式都描述了一个常见问题的上下文及其核心解决方案,使我们可以反复使用这些方案,而无需从头思考。
通俗地说,设计模式就是“前人踩过的坑”和“走通的套路”,它们体现了面向对象设计的最佳实践。合理使用模式,能够显著提升代码的可复用性、可扩展性和可维护性。


通常,一个设计模式由以下四个要素构成:
虽然 GoF 提出的设计模式只有 23 个,但每个模式都针对一个可复用的设计问题,提供了成熟的解决方案。根据其用途和关注点的不同,设计模式通常被划分为三大类:

设计原则

更多关于设计模式原则的内容,可以参考我的文章《漫谈模式之几大设计原则》。
问题
在这个阶段,一下子好多设计模式要去接受,其实挺容易遗忘的。可以结合生活的一些示例来加深记忆。
初学阶段,需要同时接触许多设计模式,信息量较大,容易产生遗忘。为了加深理解和记忆,可以结合生活中的实际例子进行联想和学习。
比如:
建造者模式

适配器模式

中介者模式

模版方法模式

享元模式


备忘录模式

... ...
经过这个阶段的学习,对设计模式已有了较为宏观的认知,能够识别出许多模式的基本结构和用法。然而,整体理解仍停留在“知道怎么写”的层面,缺乏对设计动机、适用场景和优劣权衡的深入思考。
以两个常见模式为例:
了解了单例的基本实现方式,例如:
private 构造函数防止外部实例化;getInstance() 获取唯一实例;synchronized 保证线程安全;volatile 关键字防止指令重排等。📖 推荐阅读:《漫谈模式之单例模式 —— 多种实现方式的思考》
理解了通过 Builder 构建一个完整对象的过程,知道各个组件如何组装,以及如何分离构造过程与表示逻辑。
📖 推荐阅读:《漫谈模式之建造者模式 —— 由来与通用写法》
... ...
至于为什么要这样?用在什么场景中?.... ? 缺少对模式真正在解决的问题的认识。
03
进入这一阶段后,我开始带着更多“为什么要这么做?”的问题去深入理解设计模式,尝试真正体会每种模式背后的设计思想与适用场景。为了加深理解,我主要通过以下几个方式进行学习:
选择了多个成熟的开源项目,如 JDK、Spring、MyBatis、Guava、JUnit 等,从中观察设计模式的实际应用。通过阅读源码,不仅可以看到模式在真实开发中的使用方式,还能理解它们解决了哪些具体问题、为何如此设计。
例如:
👉 在 JDK 中,
Runtime.getRuntime()就是典型的单例模式实现。 👉 在 Spring 的 Bean 生命周期管理中,可以看到工厂模式、模板方法模式、代理模式的灵活组合应用。
如:
饿汉式单例
JDK源码中的Runtime就是饿汉式单例。

枚举单例
Guava包的MoreExecutors类中,可以看到枚举单例的示例。



装饰者模式
MyBatis中的Cache采取的就是装饰者模式


访问者模式
jsqlparser比较典型的就是访问者模式



... ...
因此,我们可以在许多优秀的开源项目中看到设计模式的身影,这些实际案例有助于我们更直观地理解模式在不同场景下的应用方式与设计意图。
在这个阶段,我还带着很多“为什么”去理解。比如:
随着理解的深入,我开始关注不同场景下单例模式的实现差异,尤其是在并发环境下的安全性与性能权衡。
例如,自从 JDK 1.5 引入了 java.util.concurrent 并发包之后,我们可以尝试使用 Lock 来实现线程安全的懒加载单例:

是否可以通过CAS来实现呢?此种场景,可能会创建多个实例,但是只有一个实例会返回。

在早期JDK不支持volatile的时候,ThreadLocal也是解决D.C.L不足的一种方式。
MyBatis的ErrorContext使用ThreadLocal来完成的,其确保了线程中的唯一。

单例如何保证只创建一个对象?
以Java为例,创建对象除了New之外,还可以通过反射Reflection、克隆Clone、序列化/反序列化完成,所以,如果要保证真正只有一个对象,需要规避这些“陷阱”。
漫谈模式之单例模式(破坏和防护的思考)
... ...
比如,在阅读 Guava 源码时,我注意到 Suppliers 类中有一个 memoize 方法,其本质上也是一种懒加载的单例实现。发现这一点后,我便把它记录下来,作为对单例模式应用的一种实际补充案例。

另外,也可以通过ppt在组内分享或者写Blog/公众号来加强设计模式的记忆。如:
分享一个PPT: 聊聊设计模式
漫谈模式之创建型模式小结
漫谈模式之结构型模式小结
回过头看,感觉当时写的真的好简单。
进入这个阶段之后,我对设计模式的理解更加深入,不仅掌握了其结构和实现方式,也逐渐体会到每种模式真正解决的问题以及适用的场景。与此同时,也开始带着更多思考去探究背后的设计哲学。
比如:
这些问题促使我不再满足于“用法”,而是进一步追问“为什么要这样设计?还有没有其他可能?”,也正是在这样的反思中,设计模式的“味道”才真正开始显现出来。
04
模板方法模式
曾经在实际项目中遇到过这样一个场景:我们需要对接多个不同的税务扣缴渠道,每个渠道的输入报文格式各不相同,但整体的处理流程却高度相似。
1、必要的账号有效性检查
2、个别场景特殊性的验证
3、插入扣税记录
4、调用扣税服务(如dubbo)
5、根据不同的调用结果,处理不一样的逻辑
... ... 其他流程,如发送MQ消息如果每个渠道的交易逻辑都单独编写一套流程,不仅会造成大量冗余代码,还容易引发流程不一致、维护成本高等问题。
为此,我们需要从业务的共性出发,进行如下改造:
PayDetailRecord,作为整个处理流程中的核心数据结构。PayDetailRecord 的部分。...
下面是一个简化的伪代码示例:
统一扣税对象
package com.wangmengjun.learning.designpattern.cases.template.v2;
import java.io.Serializable;
public class PayDetailRecord implements Serializable {
private static final long serialVersionUID = 8693597750287467725L;
... ...
}模版
package com.wangmengjun.learning.designpattern.cases.template.v2;
public abstract class AbstractPayTaxTemplate {
public void handle(PayCallback callback, PayContext payContext, PayDetailRecord record) {
//1、必要的账号有效性校验
this.processBasicValidation(payContext, record);
//2、子类个性化校验
this.processCustomizedValidation(payContext, record);
//3、插入扣税记录日志
this.insertPayRecordLog(payContext, record);
//4、调用扣税是服务, 如dubbo服务
int result = this.callPayService(callback, payContext, record);
/**
* 5、根据不同的调用结果,处理不一样的逻辑
*/
if(result == 0) {
//调用结果未知, 插入一个异步任务继续重新去调度调用扣税服务
this.insertAsyncTask(payContext, record);
}else {
//使用callback
callback.updatePayResult(payContext, record, result);
}
... ...
}
//public abstract void doUpdatePayResult( PayContext payContext, PayDetailRecord record, int result);
protected void processBasicValidation( PayContext payContext, PayDetailRecord record) {
System.out.println("AbstractPayTaxTemplate# processBasicValidation ... ... ");
}
protected void processCustomizedValidation( PayContext payContext, PayDetailRecord record) {
//留空,子类根据需要重写即可
//System.out.println("AbstractPayTaxTemplate# processCustomizedValidation ... ... ");
}
protected void insertAsyncTask( PayContext payContext, PayDetailRecord record) {
System.out.println("AbstractPayTaxTemplate# insertAsyncTask ... ... ");
}
protected void insertPayRecordLog( PayContext payContext, PayDetailRecord record) {
System.out.println("AbstractPayTaxTemplate# insertPayRecordLog ... ... ");
}
protected int callPayService(PayCallback callback, PayContext payContext, PayDetailRecord record) {
System.out.println("AbstractPayTaxTemplate# callPayService ... ... ");
return 1;
}
}
回调函数
package com.wangmengjun.learning.designpattern.cases.template.v2;
public interface PayCallback {
void updatePayResult( PayContext payContext, PayDetailRecord record, int result);
}
简单示例:
public class Main {
public static void main(String[] args) {
AbstractPayTaxTemplate template = new Trade001();
template.handle(new PayTaxCallback(), new PayContext(), new PayDetailRecord());
}
}经过这样的改造后,每个渠道的扣税交易只需继承统一的模板类(如 AbstractPayTaxTemplate),并根据需要重写个性化的方法即可。这样不仅减少了重复代码,也大大提升了系统的可扩展性与可维护性。
这正是模板方法模式的一种典型应用:提炼通用流程,封装变化点,在复用的同时保留必要的灵活性。
再举一个例子:
某次,开发团队搭建了一个统一的日志中心,希望各个应用在日志输出上保持一致性。为此,他们在设计日志工具类 LogUtil 时,并不提供传统的 info(String message) 方法。
为什么?
因为单个字符串参数往往难以满足实际业务中多样的日志需求,尤其是当不同应用对日志字段的关注点各不相同时,简单的多参数方法也会显得力不从心。
于是,日志中心统一定义了一个标准的 LogRecord 对象,来承载结构化日志信息。该对象包含了如:
traceId(链路追踪)bizType(业务类型)elapsedTime(耗时)通过统一封装后的 LogRecord,不仅便于日志分析、归档、监控,还支持各业务模块灵活扩展字段,兼顾规范性与灵活性。
public class LogUtils {
... ...
public static void info(LogRecord record) {
... ...
}
... ...
}此时,LogRecord 的构建就可以采用建造者模式(Builder Pattern)来实现,使各应用能够根据自身需求灵活设置不同字段,既清晰又具备良好的可扩展性。
当然,设计模式的应用远不止于此,还有很多典型的场景值得探讨。本文不再逐一展开,后续将通过其他专题文章进行深入分享。
05
进入这一阶段后,更重要的是对所学内容进行归纳整理,逐步构建出一套属于自己的设计模式认知框架。以创建型设计模式为例,我的理解如下:
关注创建什么对象(What)。
关注如何一步步构建对象(How)。
确保只创建一个对象(Only One)。
ThreadLocal 实现;通过克隆(Clone)的方式创建对象。
... ...
可以将设计模式理解为在特定问题中反复验证有效的解决“套路”。随着业务复杂度的提升和系统架构的演进,GoF 提出的 23 种经典模式已经难以覆盖所有实际场景。
因此,在 GoF 模式的基础上,业界也不断衍生出一系列具有指导意义的“非经典”模式,用于解决更细分或更现代化的问题。例如:
……
这些补充型模式为我们提供了更多维度的思考方式,在面对复杂或新型业务需求时,能够帮助我们找到更加合适的设计方案。
📌 通用方法
比如单例模式是否可以定义一个抽象类来实现,把创建类型交由子类实现,如:
public abstract class Singleton<T> {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
}
return mInstance;
}
}再比如:观察者模式,能不能用一个抽象类呢? 多个主题topic,可以采用ConcurrentHashMap来存储,等等实现。
... ...
06
不知不觉已经写了不少内容,最后再补充几点关于常见误区与学习建议,希望能对大家有所帮助。
就先写到这里吧。如果你也在学习设计模式,欢迎留言交流心得,互相启发、共同进步。
/ END /