说实话,第一次准备设计模式考试的时候,我真被23种设计模式吓了一跳。每种模式有名字、有结构图、有适用场景、有优缺点,硬背两三天,考完就忘。后来在项目和面试里反复用到,才慢慢总结出一套自己的速记方法——先搭框架,再记细节,最后用口诀把名字串起来。这篇文章就把这套方法整理出来,无论你是期末复习、面试突击,还是想系统建立对设计模式的整体认知,都能直接拿去用。我会尽量用大白话讲清楚每种模式的“一句话本质”,再配上高频考点的代码示例和易错点,争取让你看完就能照着复现。
设计模式这东西,本质上就是前辈们在长期开发中总结出来的“套路”。它不是语法,不是框架,而是一套解决特定问题的代码组织方式。学会它,你看到一段代码就能猜出作者想干什么;不会它,你看谁写的代码都像一团乱麻。所以不管是应付考试,还是真想提升内功,这23个模式都值得花点时间吃透。
1. 先建框架:一张思维导图看懂23种设计模式
1.1 设计模式到底在解决什么问题
设计模式的鼻祖是GoF(Gang of Four,四人组),他们在《设计模式:可复用面向对象软件的基础》里总结了23种模式。这23种模式看着多,但归类之后其实非常清晰:它不是按难度分的,而是按“解决的问题类型”分的。
- 创建型模式:解决“对象怎么创建”的问题。说白了就是怎么new对象更合理、更灵活。
- 结构型模式:解决“类和对象怎么组合”的问题。就是怎么把多个类组织起来,形成更大的结构。
- 行为型模式:解决“对象之间怎么协作”的问题。就是多个对象互相调用、互相通知时,怎么把耦合降到最低。
判断方法也简单:你写代码时如果头疼的是“这个对象怎么来”,那就是创建型;如果头疼的是“这些类关系太乱、耦合太重”,那就是结构型;如果头疼的是“对象之间互相调用太复杂、状态老变”,那就是行为型。
1.2 三分钟建立起分类记忆
我第一次背的时候就吃了硬背的亏,后来发现必须先记“地图”。23种模式分布如下:
- 创建型5种:工厂方法、抽象工厂、单例、建造者、原型
- 结构型7种:适配器、装饰器、代理、外观、桥接、组合、享元
- 行为型11种:策略、模板方法、观察者、迭代器、责任链、命令、备忘录、状态、访问者、中介者、解释器
给你一个顺口溜,我到现在面试写白板都靠它:
创建:工抽单建原(工厂方法、抽象工厂、单例、建造者、原型)
结构:适装代外桥组享(适配器、装饰器、代理、外观、桥接、组合、享元)
行为:策模观迭责命备状访中解(策略、模板方法、观察者、迭代器、责任链、命令、备忘录、状态、访问者、中介者、解释器)
还有个容易踩的坑:网上很多文章会把“简单工厂”也算进去,但GoF官方23种设计模式里根本没有简单工厂,它只是工厂方法的一种简化写法。期末考试如果出填空题,你写“简单工厂”很可能不给分。
2. 创建型模式速记:5种模式一套逻辑
2.1 工厂三兄弟:简单工厂、工厂方法、抽象工厂
这三个是创建型里头最容易混的,先说我的记忆锚点。
简单工厂:一个工厂类,根据参数switch创建不同产品。优点是写起来快,缺点是加一个产品就要改工厂,违反开闭原则。适合产品种类少、基本不变的场景。注意它不属于GoF 23。
工厂方法:把工厂类抽象成接口,每个具体产品对应一个具体工厂。加新产品时,只需要新增一个产品和对应工厂,不用改老代码。C++里最典型的样子长这样:
class Product { public: virtual void use() = 0; }; class ConcreteProductA : public Product { public: void use() override { /* ... */ } }; class Factory { public: virtual Product* createProduct() = 0; }; class ConcreteFactoryA : public Factory { public: Product* createProduct() override { return new ConcreteProductA(); } };抽象工厂:工厂方法负责生产“一种产品”,抽象工厂负责生产“一族产品”。比如一个餐厅既卖汉堡又卖可乐,那抽象工厂就是“连锁品牌”,它规定“你这一整套菜单必须包含汉堡、薯条、可乐”,至于怎么实现看具体分店。
怎么区分?简单工厂看参数返回不同对象;工厂方法是每个产品配一个工厂;抽象工厂是一个工厂返回一族有关联的对象。考试和面试最喜欢问这个问题,你把“一族产品”这四个字记住,抽象工厂就抓住了核心。
2.2 单例模式:懒汉、饿汉、双检锁
单例模式面试出现频率极高,尤其喜欢问Double-Checked Locking。先看标准答案。
饿汉式:类加载时就创建实例,线程安全,但可能造成不必要的资源浪费。代码很简单:
public class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton() {} public static Singleton getInstance() { return INSTANCE; } }懒汉式:第一次调用时才创建,但是线程不安全。给方法加synchronized能解决,但性能太差。所以有了双检锁:
public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }面试最爱问:为什么加volatile?因为instance = new Singleton()不是原子操作,它分三步:分配内存、初始化对象、把引用指向内存。如果不加volatile,编译器可能重排序成“先指向内存、再初始化对象”,另一个线程就会拿到一个没初始化完的半成品。简单理解就是volatile禁用了重排序,保证看到的是完整对象。
实际开发我一般推荐枚举单例,代码最简洁,还能防反射攻击和序列化破坏。
2.3 建造者模式:专治构造函数参数爆炸
建造者模式解决的是“一个对象参数太多、构造方法太乱”的问题。比如一个电脑类,有CPU、内存、硬盘、显卡、显示器,十几个可选参数,你写十几个构造函数?那维护起来就是灾难。
建造者的思路是:把对象的构建过程拆分成一步步的build方法,最后通过build()组装:
Computer computer = new Computer.Builder() .cpu("i7") .memory("16G") .disk("1T SSD") .build();Java里很常见的Lombok @Builder注解、OkHttp的Builder、Spring的BeanDefinitionBuilder都是这个套路。面试问它,你就说“解决多参数构造和对象属性过多的问题,同时保证对象创建过程清晰”。注意建造者模式和工厂模式的区别:工厂模式关心“造出哪个产品”,建造者模式关心“这个复杂产品怎么一步步拼出来”。
还有一种容易忽略的场景:当一个对象创建完之后不应该被修改时,建造者模式配合不可变类非常好用,构造过程灵活,最终对象还是不可变的。
2.4 原型模式:浅拷贝深拷贝的坑
原型模式的核心是“复制现有对象,而不是new”。Java里就是实现Cloneable接口,重写clone()方法。它省去了重新初始化的开销,适合创建成本高、对象状态差别不大的场景。
但原型模式最容易踩的坑就是浅拷贝和深拷贝的区别。默认clone()是浅拷贝,对象内部的引用类型还是指向同一个对象,你改了一个对象的子对象,另一个也跟着变。解决方法是重写clone()时,把内部的可变对象也手动clone一遍,或者用序列化的方式实现深拷贝。
面试问原型模式,一般会追问深浅拷贝区别,你把“浅拷贝只拷引用、深拷贝连内部对象一起复制”记住,再用“数组里的对象修改会互相影响”举例就够了。
3. 结构型模式速记:7种模式两种套路
3.1 适配器、装饰器、代理:三个最容易混的模式
这三个模式代码长得很像,面试时特别容易说串。我的记忆方法是“三个生活比喻”。
适配器模式就是转接头。Type-C转USB、插座转换器,作用是把一个接口转换成客户期望的另一个接口。比如老系统的接口是220V,新系统要110V,中间加一个适配器。代码上就是“包装一个对象,把A接口转成B接口”。
装饰器模式就是手机壳。它不改变接口,只是在原有功能上叠加增强。Java的IO流是经典例子:new BufferedReader(new FileReader(...)),一层套一层,每个装饰器加一点功能,但接口始终是Reader。核心特征是“接口不变,功能增强”。
代理模式就是经纪人。它也不改变接口,但目的是“控制访问”而不是增强功能。比如真正的对象不想让别人直接访问,或者访问前要做权限校验、日志记录,就通过代理。Spring AOP、MyBatis的Mapper动态代理都是代理模式。
代理和装饰器长得几乎一样,区别在意图:装饰器只是想给对象“加功能”,代理是想“控制对象怎么被访问”。面试官问你怎么区分,你就说“看目的,装饰器重功能增强,代理重访问控制”。
3.2 外观、桥接、组合、享元:四个整理关系的模式
外观模式是遥控器。你把一堆复杂子系统封装成一个简单门面,外部只跟门面打交道。比如一个家庭影院要开投影、开音响、拉窗帘,你封装成一个oneKeyPlay()方法,外面调一个方法就行。它最重要的价值是降低使用方的复杂度,不改变子系统本身的功能。
桥接模式是抽象与实现分离。典型例子是JDBC的DriverManager,Java定义了一套数据库接口,具体实现由各数据库厂商提供,两边通过桥接解耦。它解决的是“多个维度变化”的问题,比如形状和颜色,如果用继承就是N种形状乘以M种颜色,用桥接就是两个维度独立扩展。
组合模式是树形结构。文件系统、公司组织架构、菜单层级,都是组合模式。核心思想是“部分和整体有一致性”,单个文件和文件夹都能调用同一个方法。大话设计模式里的例子就很经典:总公司有子公司,子公司有部门,一层层往下,都算“组织节点”。这样客户端调用时不需要区分是叶子节点还是容器节点。
享元模式是池化思想。String常量池、线程池、数据库连接池都是享元模式。它把可共享的对象缓存起来,重复使用,避免重复创建造成内存浪费。核心是区分“内部状态”(可共享)和“外部状态”(不可共享)。面试只要说出“线程池、字符串常量池就是享元模式的典型应用”,基本就过关了。
3.3 结构型模式做题判断技巧
期末考或面试时给一段代码让你判断用了什么模式,我的判断顺序是:
- 如果代码里出现了“接口转换”,优先想适配器。
- 如果出现了“包了一层又一层,接口没变”,想装饰器。
- 如果出现了“代理对象,实际干活的是被代理对象”,想代理。
- 如果出现“一个类封装了多个子系统”,想外观。
- 如果出现“一个接口多个实现,客户端只面向接口”,先想桥接。
- 如果出现“树形结构递归”,想组合。
- 如果出现“对象池/缓存复用”,想享元。
4. 行为型模式速记:11种模式分清职责
4.1 三个最常用的:策略模式、模板方法、观察者模式
策略模式,本质是“把算法封装起来,客户端可以随时切换”。Java里的Comparator就是最典型的例子。用策略模式做打折计算,可以让满减、折扣、立减各自实现同一个策略接口,业务代码里传入不同的策略对象即可。它的好处是完全符合开闭原则:新增一种优惠方式,不用改老代码,只加一个类。
模板方法模式,本质是“父类定义流程,子类实现细节”。比如做奶茶,步骤固定是“煮茶、加料、封装”,但加什么料由子类决定。它的核心就是把不变的流程写在抽象类里,把变化的步骤抽象成方法交给子类实现。面试问它,你举“AbstractList的模板方法”或者“Spring的JdbcTemplate”都可以。
观察者模式,本质是“一对多通知”。一个主题状态变化,多个观察者自动收到通知。Java的Listener、Android的点击事件、消息队列的订阅发布,都是这个思路。面试必问题:观察者模式和发布订阅模式什么区别?简单说,观察者模式是主题直接通知观察者,耦合还在;发布订阅读者在中间加了一个消息代理,生产者和消费者完全解耦,属于观察者模式的升级版。
策略模式和状态模式特别容易混。策略模式是“客户主动换算法”,状态模式是“对象内部状态变化自动切换行为”。一句话:策略是外面换,状态是自己换。
4.2 责任链、命令、备忘录、状态、中介者:处理流程与状态的五虎将
责任链模式,本质是“请求沿着链传递,谁能处理谁处理”。Servlet的Filter链、SpringMVC的拦截器链、Netty的ChannelHandler链都是责任链。考试大作业如果做审批系统,领导审批、经理审批、总监审批层层往上,这就是责任链模式。
命令模式,本质是“把请求封装成对象”。好处是可以排队、可以撤销、可以记录日志。编辑器里的Ctrl+Z撤销就是命令模式,每个操作都是一个Command对象,撤销时调用undo()。Runnable接口其实也是命令模式的思想,把任务封装成对象丢给线程池执行。
备忘录模式,本质是“保存快照,支持恢复”。游戏存档、文档历史版本都是这个模式。Caretaker(管理者)持有快照,Originator(发起人)负责生成和恢复快照,核心是“不破坏封装的前提下保存和恢复状态”。
状态模式,本质是“状态决定行为”。电梯门开着能按关门,关着不能按;订单待支付能取消、已发货不能取消。它的核心是把每个状态的逻辑封装成一个类,状态切换由类内部完成,避免一堆if-else。我之前做订单系统,最深的体会是:订单状态一多,if-else就是灾难,状态模式虽然类多了点,但每个状态类的逻辑非常清晰,加新状态也好扩展。
中介者模式,本质是“多个对象不直接交互,都通过中介者”。比如聊天室,用户A发消息不需要直接推给B、C、D,而是发给聊天室,聊天室中转。它解决的是“同事对象”两两交互导致的网状依赖,改成星型依赖。
4.3 三个“偏门”的:迭代器、访问者、解释器
迭代器模式最简单,就是“提供一种统一方式遍历集合,不暴露内部结构”。Java的Iterator就是标准实现,考试考得不多,记住“遍历”两个字就行。
访问者模式是设计模式里比较难懂的一个,核心是“数据结构和操作分离”。比如医院里药房划价、医生诊断,同一个病人被不同角色“访问”,每种角色给出不同结果。但它带来的问题是增加新操作容易,增加新元素难,所以实际项目里用得很少,面试也很少深挖。你能说出“它把稳定的数据结构和对数据的操作解耦”就算过关。
解释器模式是“定义一种语言,解释它的句子”。正则表达式、SQL解析、编译器都是解释器模式的思想。但它类结构非常复杂,一般只在特定领域使用,我做了这么多年开发,几乎没有在业务代码里手写过解释器。面试问到的话,举正则表达式的例子就够了。
行为型11种如果觉得头大,再给你一条记忆主线:策模观是“最常用的三大天王”,迭责命备状是“处理流程和状态的五虎将”,访中解是“三个偏门选手”。
5. 高频考点、面试场景与避坑清单
5.1 高频面试题速查表
| 问题 | 一句话回答要点 |
|---|---|
| 单例模式有哪些写法? | 饿汉、懒汉、双检锁、静态内部类、枚举,推荐枚举和双检锁 |
| 工厂方法和抽象工厂的区别? | 工厂方法生产一种产品,抽象工厂生产一族产品 |
| 代理模式和装饰器模式的区别? | 代理控制访问,装饰器增强功能 |
| 策略模式和状态模式的区别? | 策略是外部主动换算法,状态是内部自动换行为 |
| 观察者模式哪里用到? | 事件监听、消息订阅、Spring事件机制 |
| 适配器和外观模式都能“包装”别人,怎么分? | 适配器为了接口兼容,外观为了简化调用 |
| 静态代理和动态代理区别? | 静态代理在编译期写死代理类,动态代理在运行期生成代理类,JDK动态代理基于接口,CGLIB基于继承 |
5.2 期末大作业怎么选模式
如果你们课程要求“设计模式大作业”,我的建议是别贪多。选一个完整场景,把3到4个模式用进去就行。比如做“文件管理系统”:组合模式表示目录树,迭代器模式遍历文件,观察者模式通知文件变化,命令模式实现撤销删除。再比如“网上订餐系统”:工厂方法创建不同类型的餐品,策略模式计算优惠价格,状态模式管理订单状态,责任链模式做订单审核。
最推荐的是组合模式做公司组织架构或文件目录,因为树形结构天然适合组合模式,画出来的类图也清晰,答辩时好讲,评委也容易理解。
5.3 我踩过的几个坑
第一个坑是死记类图。设计模式考的不是类图画得多标准,而是你能不能讲清楚“为什么这么设计”。对着类图背十遍,不如亲手写一遍策略模式,不信你试试。
第二个坑是误以为用设计模式就是好代码。我见过有人为了用模式而用模式,简单功能硬套一个抽象工厂,最后类爆炸了,维护更痛苦。设计模式的本质是解决特定问题,抽象是要付出代价的,类多了、调用链复杂了、读代码的负担也重了。没有那个问题,就别硬套。
第三个坑是面试时只背名字。面试官问“你用过哪些设计模式”,你别只答“单例、工厂、代理”。正确姿势是:先说项目中某个具体场景,再说用了什么模式,最后说解决了什么问题。比如“我在订单模块里,因为订单状态多、分支乱,用状态模式重构后if-else少了很多,加新状态只需要新增状态类”。
设计模式这东西,光看是看不熟的。我自己也是项目里踩过几次坑、重构过几轮代码之后,才真正明白“面向接口编程”“组合优于继承”这些原则到底在说什么。你把这23种模式背下来只是第一步,第二步是在自己的项目里找到能用的地方,哪怕只是把一个switch换成策略模式,你都会比昨天进步一点。祝期末顺利,面试顺利。