设计模式这东西,只要你接触过一段时间,肯定有过这种体验:看的时候感觉全懂,合上书第二天就忘了一大半;面试前一晚背得滚瓜烂熟,面试官换了个场景提问,瞬间不知道用哪个;期末复习的时候,二十多个模式排成一列,脑子里全是“工厂、抽象工厂、建造者……”长相都差不多的影子。这几年我带过不少实习生、也帮人做过面试突击辅导,发现大多数人不是不努力,而是把设计模式当成了“知识点”在死记硬背,没有真正把它变成一套可以随时调用的“思维工具”。这篇文章我就把自己一直用的这套“设计模式速记”方法整理出来,从一个实际项目里打磨过的角度,告诉你哪些模式必须熟练掌握、每个模式最核心的一句话本质是什么、复习的时候怎么在几分钟之内把整个框架串起来,以及考试和面试里最容易踩的坑。
这套方法是我在实际开发项目里反复用过的,也在给团队做技术分享的时候验证过,不是从哪本书上抄下来的理论框架。它解决的是三个最具体的问题:第一,怎么用最短的时间建立起设计模式的整体坐标系,不至于学了后面忘了前面;第二,每个模式到底在解决什么问题,用一句话说清楚,而不是背一堆“定义+类图+示例代码”;第三,遇到面试题或者大作业需求的时候,怎么快速判断该用哪个模式,以及为什么是它而不是别的。
1. 从“背模式”到“建坐标”:先搞懂设计模式到底在解决什么
很多人的第一个误区,是想把二十三个经典模式一个个背熟。这个思路从一开始就走偏了。设计模式不是一个一个孤立的知识点,它是一套关于“怎么让代码在变化面前更稳”的经验总结。如果你抓住了这个核心,你会发现所有模式背后其实就两件事在反复出现:一个是“找出变化,封装变化”,另一个是“面向接口编程,而不是面向实现编程”。
1.1 七大设计原则才是真正的“心法”
记住GOF那二十三个模式之前,我建议你先花一个小时把七大设计原则弄明白。为什么?因为原则是“为什么”,模式是“怎么做”。你只有先知道了设计要往哪个方向走,才能理解为什么某个模式要那样搭结构。
七大原则我用一个口诀记:“开里依单迪合”。对应的是开闭原则、里氏替换原则、依赖倒置原则、单一职责原则、迪米特法则、合成复用原则,加上一个接口隔离原则。这里面最核心的是开闭原则:对扩展开放,对修改关闭。说人话就是,以后要加新功能的时候,尽量不碰已经写好并且测试过的老代码,而是通过加新代码来实现。
举个例子你就明白了。假设你给一家餐厅写点餐系统,一开始只支持现金支付。你写了一个CashPay类,调用方直接new CashPay()然后pay(),很正常。后来老板说要支持微信支付,你要怎么改?如果原来的代码里到处都是new CashPay(),就得一个一个去改,这就是“修改关闭”没做好。如果你一开始定义了一个IPay接口,调用方只依赖接口,那加微信支付就只是新增一个WechatPay类实现IPay,原来的代码一行都不用动。
里氏替换原则也很关键,它是说子类必须能够替换掉它的父类,并且程序的行为不出问题。这个原则直接决定了你继承用不用得对。很多人写代码特别喜欢继承,看到两个类有点像就抽个父类出来,结果子类重写父类方法的时候把逻辑改得面目全非。等到哪天要替换子类的时候,程序直接崩了。我见过一个真实案例,有个项目里Square(正方形)继承了Rectangle(长方形),然后重写了setWidth方法保证长宽相等。看着挺好的,结果做面积计算的代码传了个Rectangle进来,运行的时候设了宽和长,面积算出来是错的——因为里氏替换被破坏了。这就是看起来“合理”的继承,实际是个坑。
依赖倒置原则简单说就是:高层模块不应该依赖低层模块,两者都应该依赖抽象。翻译成人话:业务层不要直接依赖工具类、具体实现类,而是依赖抽象接口。这个原则跟开闭原则是配套的,你只有依赖了抽象,才可能做到对扩展开放。这几个原则相互之间是有关联的,单一职责是让类有明确的“一个理由”,接口隔离是让接口尽量小而专,迪米特法则告诉你要“少跟陌生人说话”,合成复用原则是提醒你优先用组合而不是继承。
1.2 二十三个模式的“三维坐标”:创建型、结构型、行为型
原则有了,接下来就是模式主体的分类。二十三个模式分成三大类,很多人只是把这个分类背下来就没了。但仔细想想,这个分类本身就有极强的记忆价值,因为它其实对应着软件设计的三个维度:
- 创建型模式(5个):解决“对象怎么产生”的问题。你在什么场景下用什么方式new一个对象。
- 结构型模式(7个):解决“对象怎么组合”的问题。类与类、对象与对象之间怎么组成更大的结构。
- 行为型模式(11个):解决“对象之间怎么协作”的问题。责任怎么划分、算法怎么封装、状态怎么流转。
我建议你把这个分类当成坐标系来记。拿到一个需求,先问自己三个问题:这个场景的重点是在“怎么创建对象”?还是在“怎么组织对象关系”?还是在“怎么处理对象之间的交互”?定位到其中一个维度之后,再在维度内部去找具体的模式,命中率会高得多。
举一个很典型的例子:你需要一个全局唯一的配置管理器。第一步判断,重点在“对象怎么产生”,锁定创建型;第二步,需要一个类只允许一个实例——单例模式,搞定。整个过程不到十秒钟。但如果不懂分类,你可能看哪个模式都像,卡在那里半天选不出来。
还有一个容易被忽略的点:三大类不是完全割裂的,同一个复杂的项目里经常会多个模式配合使用。比如一个管理系统里,你可能用工厂方法创建不同类型的报表,用组合模式把报表和子报表组织成树形结构,再用观察者模式让数据变动时自动刷新报表,用策略模式切换不同的统计算法。这就是模式组合的典型场景。你在复习的时候,与其一个个孤立地背,不如想想怎么把三四个模式串成一个微型项目,这样记忆会牢固得多。
关于每个模式的数量和名字,我附一个速记清单,你可以把它作为自检表,看自己是不是能对着表把每个模式的核心思想在三句话以内讲清楚:
| 类型 | 模式名称 | 一句话本质 |
|---|---|---|
| 创建型 | 简单工厂 / 工厂方法 / 抽象工厂 | 把new对象的逻辑集中起来,让调用方和具体类解耦,差别在于工厂的抽象粒度 |
| 创建型 | 单例模式 | 保证一个类只有一个实例,并提供一个全局访问点 |
| 创建型 | 建造者模式 | 把一个复杂对象的构建过程和它的表示分离,同样的构建过程能造出不同的产品 |
| 创建型 | 原型模式 | 通过复制现有对象来创建新对象,而不是通过new |
| 结构型 | 适配器模式 | 让两个接口不兼容的类可以一起工作,加一个中间转换层 |
| 结构型 | 装饰器模式 | 动态地给一个对象添加额外的职责,比继承更灵活 |
| 结构型 | 代理模式 | 不直接访问真实对象,通过一个替身来控制访问,可以在访问前后加逻辑 |
| 结构型 | 组合模式 | 把部分和整体的关系用树形结构表示,让客户端可以一致地处理单个对象和组合对象 |
| 结构型 | 外观模式 | 给一堆复杂的子系统提供一个统一的简单入口 |
| 结构型 | 桥接模式 | 把抽象部分和实现部分分离,让它们可以独立变化 |
| 结构型 | 享元模式 | 通过共享细粒度对象来节省内存,大量相似对象只保留一份共享状态 |
| 行为型 | 策略模式 | 定义一族算法,把它们封装起来,让它们可以互相替换 |
| 行为型 | 观察者模式 | 对象之间一对多依赖,一个对象状态变了,所有依赖它的对象都收到通知 |
| 行为型 | 模板方法模式 | 父类定义算法的骨架,把一些步骤延迟到子类中实现 |
| 行为型 | 责任链模式 | 多个对象都有机会处理请求,把它连成一条链,沿着链传递直到有人处理 |
| 行为型 | 状态模式 | 对象的行为取决于它的内部状态,状态变了行为也跟着变 |
| 行为型 | 命令模式 | 把请求封装成对象,从而可以用不同的请求对客户端进行参数化 |
| 行为型 | 迭代器模式 | 提供一种顺序访问聚合对象内部元素的方法,又不暴露内部结构 |
| 行为型 | 中介者模式 | 用一个中介对象来封装一系列对象之间的交互,让它们不必显式互相引用 |
| 行为型 | 备忘录模式 | 在不破坏封装的前提下,捕获并保存对象的内部状态,以便之后恢复 |
| 行为型 | 解释器模式 | 给定一门语言,定义它的文法表示,并提供一个解释器来解释语言中的句子 |
| 行为型 | 访问者模式 | 在不改变元素类的前提下,定义作用于这些元素的新操作 |
这张表你先留个印象,下面我会挑最核心的十几个,把速记要点展开讲透。
2. 创建型模式速记:别让“new”毁了你的代码
创建型模式一共五个,算上常被提到的简单工厂其实是六个(简单工厂不属于GOF 23种,但考试和面试都常考),主要解决一个核心痛点:new这个动作太“硬”了。你在代码里写new A(),就把A这个具体类和调用方死死绑定了。创建型模式就是想办法把“怎么new”“new什么”从调用方的代码里抽出去。
2.1 工厂三兄弟:从简单工厂到抽象工厂,差在哪
工厂模式是创建型里的重点,也是考试和面试的高频区域。我给你一条清晰的升级路线:简单工厂 → 工厂方法 → 抽象工厂。
简单工厂是最直白的:把创建逻辑放在一个工厂类里,传个参数进去,工厂帮你判断该返回哪个产品。它本质上是“把一堆if-else收拢到一个地方”。优点是好用、好写,缺点是工厂类的职责太重了,每加一个新品就要改工厂的if-else,违背开闭原则。考试里如果问你简单工厂的缺点,答案就在这。
工厂方法模式的思路是把工厂抽象化:定义一个抽象的Factory接口,每个具体产品对应一个具体工厂。比如PizzaFactory是个抽象类,下面有CheesePizzaFactory、VeggiePizzaFactory。这样新加产品的时候只要新增一个工厂类,不用改老代码。代价是类多了很多,每个工厂基本就是“死磕”一个产品。
抽象工厂模式继续往前走一步:它不是为了创建某一个产品,而是为了创建“一族”相关的产品。比如你要做一个跨平台的界面库,Windows风格下有WinButton和WinText,Mac风格下有MacButton和MacText。这时候抽象工厂UIFactory有两个实现类WindowsFactory和MacFactory,每个实现类负责创建一整套配套产品。这样能保证产品族的一致性:从WindowsFactory出来的必然是一套Windows风格的东西,不会出现Windows按钮配Mac文本框的错乱。
速记的时候抓住一条主线:简单工厂“一夫当关”,工厂方法“各管各的”,抽象工厂“成套定制”。再配一个记忆锚点:简单工厂改代码,工厂方法加代码,抽象工厂换整套。
2.2 单例模式:饿汉、懒汉、双重检查锁
单例模式是我在面试里问得最多的一个,因为代码简单,但坑多。它的核心是:一个类全局只有一个实例。两种最常见的写法你要刻在脑子里。
饿汉式:类加载的时候就创建实例,天然线程安全,缺点是类一加载就占着内存。懒汉式:第一次用的时候才创建实例。单线程时代直接写个懒汉没问题,多线程时代就得加处理,不然两个线程同时进来,就可能创建出两个实例。经典写法是双重检查锁:先判断为null再进同步块,进了同步块再判一次null,然后用volatile修饰实例防止指令重排。
考试或者面试里经常问“单例模式有什么问题”,很多人只会说“线程安全”。其实更重要的是容易被滥用:一个类全局唯一,意味着它变成了隐式的全局变量,测试不好做,类与类之间的依赖也不直观。而且万物皆可单例的话,实际上你就把整个项目的生命周期耦合死了。我的建议是:配置管理、连接池、日志写入这种“天然就应该全局只有一份”的用单例;业务上觉得“好像用不着多个”就上单例的,要谨慎一点。
2.3 建造者模式和原型模式:应对“复杂对象”和“昂贵创建”
建造者模式很多初学者不太能理解,其实它就类似于你在餐厅点餐:你自己不用关心菜是怎么做出来的,你只向服务员(Builder)交代——我要什么样的主食、什么样的配菜、什么样的饮料,最后交给厨师(Director)一起做出来。它适合那些构造参数特别多、对象构建步骤比较固定的场景。比如一个富文本编辑器,一个Document对象可能有字体、颜色、行距、页边距、水印等等几十个设置项,如果全塞在构造函数里,调用方得疯掉。用建造者链式调用就非常爽:new DocumentBuilder().setFont("微软雅黑").setColor("red").setLineHeight(1.5).build()。
原型模式的记忆点特别简单:不重新new,而是通过clone()复制一份。它的核心价值是省去重复的创建和初始化过程。比如你有一个已经配置好的报表模板对象,想要十个相似但个别字段不同的,你用原型模式clone一下,改改差异字段就行。Java里实现原型模式记得实现Cloneable接口,不然运行时会抛CloneNotSupportedException,这是个经典坑。另外还要注意深拷贝和浅拷贝的问题,如果对象内部还有引用类型的成员变量,直接clone()复制的是引用,改动会互相影响,这种情况需要自己实现深拷贝逻辑。
3. 结构型模式速记:类和对象怎么“搭积木”
创建型解决的是“怎么造对象”,结构型解决的是“对象造出来之后怎么摆、怎么组”。我用一句大白话总结这一类的核心思想:在尽量不动原有代码的前提下,把类或对象组合成更大的结构。
3.1 适配器模式与装饰器模式:名字像,用处完全不一样
适配器和装饰器是很多学生容易混淆的一对。它们结构上有点类似——都是包一层,但意图截然不同。
适配器模式的出发点是“接口不兼容”。典型场景:你在接入第三方支付SDK的时候,对方的接口叫createPayment(),你的系统里统一用的是pay(),直接调肯定不行,你就写一个适配器类,内部调用第三方SDK,对外暴露pay()。客户端的代码不用改,第三方SDK的代码也不用改,中间加一层“翻译官”。记这个模式的锚点就是:充电器转换头——你的设备是Type-C口,墙上是USB-A口,加一个转换头两边就都对上了。
装饰器模式的核心是“增强功能,但不改原有类”。它跟适配器的最大区别是:适配器是把A接口“翻译”成B接口,装饰器是不改接口,只是在调用前后动态地加职责。最经典的就是Java IO流:new BufferedReader(new FileReader("a.txt")),BufferedReader就是个装饰器,给FileReader增加了缓冲功能。考试里常问它和继承的区别:继承是在编译期静态决定,装饰器是运行期动态组合,想加什么功能就用装饰器一层层包上去,灵活性高得多,也不会造成类爆炸。
3.2 代理模式:别小看这个“替身”
代理模式的关键词是“控制”。给它加一个速记锚点——明星和经纪人:你联系明星之前,经纪人会先过滤一下你的请求,谈好了排期,你再见到真人。这个中间过程就是代理做的事情。
代理和装饰器结构上很像,很多人又搞混了。区分要点在意图:装饰器是“增强”,它做完事情之后,对象本身的能力被加了buff;代理是“控制”,它决定要不要让客户端访问真实对象,以及在访问前后要插入什么权限校验、日志记录、延迟加载之类的横切逻辑。Spring AOP的底层就用到了动态代理,所以这东西不仅是理论考点,实际框架里处处都是。
3.3 组合模式:树形结构的天然表达
组合模式特别直观,所以也好记:它适合表示“部分-整体”的层次结构。文件系统就是最贴切的例子:一个文件夹里有文件,也有子文件夹,子文件夹里又有文件和子文件夹……对于客户端来说,操作一个文件和操作一个文件夹,应该尽量一致,这样代码写起来才统一。
大作业和考试里典型的题目就是“设计一个公司组织架构”:总公司下有大部门,大部门下有小组,小组下有员工。每个人都有一个display()方法,叶子节点直接显示自己,非叶子节点先显示自己再遍历调用子节点的display()。组合模式的精髓就是让叶子节点和容器节点实现同一个接口,代码对两者一视同仁。
实际工作里,菜单系统、权限目录树、XML/JSON解析树都会用组合模式的思想。注意一个坑:如果设计和业务过分追求“一致”,导致容器节点和叶子节点的职责差异太大,接口会变得很臃肿。这时候可以考虑安全组合模式,把管理子节点的方法只放在容器节点里,牺牲一部分一致性,换取安全性。
3.4 外观模式与桥接模式:一个“做减法”,一个“做分离”
外观模式特别容易上手,它的核心思想就是“给复杂系统一个简化入口”。比如你开了一家电影院,要看电影需要开投影仪、开音响、拉幕布、调灯光。你不可能每次手动去操作所有设备,于是你搞了一个“一键观影”按钮,点一下,后台把所有设备都按顺序打开。这个按钮就是外观。日常开发里,很多Service层的方法就是在做这件事:把一堆底层组件的调用封装起来,对外暴露一个干净利落的入口。这个模式不改变底层系统,只提供一个“门面”,所以也叫门面模式。
桥接模式是结构型里比较难理解的一个,核心是“抽象和实现分离”。我记它的一个锚点是“笔和颜料”:毛笔有大号、小号,颜料有红、蓝、黑,你组合出来大号红笔、小号蓝笔……如果把“笔的型号”和“颜料颜色”各自抽象成两个维度并独立变化,中间通过组合的方式搭起来,这就是桥接。
考试里有一个特别典型的桥接案例:跨平台消息发送。系统有普通消息和加急消息(抽象维度),可以用短信发送也可以用邮件发送(实现维度)。两个维度交叉就有4种组合。如果用继承,你会搞出普通短信、普通邮件、加急短信、加急邮件4个类,以后每加一种消息类型或者一种发送渠道,类都会爆炸。用桥接模式,你只需要两个抽象,分别是消息类型和消息渠道,客户端自由组合,扩展性天差地别。
享元模式在考试里出现的频率不算最高,但概念要知道:它通过共享来减少创建对象的数量。最经典的例子是围棋里的黑白棋子——棋盘上几千个落子,但本质上只有黑白两种棋子对象,位置信息单独存储。String常量池、Integer缓存、线程池都是享元思想的应用。
4. 行为型模式速记:对象之间的“智慧协作”
行为型模式数量最多,一共11个,也是最容易让人“背了后面忘了前面”的重灾区。我的经验是:不要按顺序背,而是按“高频场景”来分组记忆。
4.1 策略模式:把“算法”从业务代码里拎出来
策略模式是行为型里面试出现率最高的一个。它的核心词是“替换”:定义一族算法,把它们各自封装起来,让它们可以互相替换,算法的变化不影响使用算法的客户端。
你在实际项目里大概率写过这种代码:根据不同的用户等级计算折扣,满500减50、会员打8折、VIP打7折,写一堆if-else。if-else本身没错,错的是每加一种等级,就要改这段核心业务代码,容易改错,也违背了开闭原则。策略模式的做法是:定义一个DiscountStrategy接口,每种折扣写一个实现类,再让上下文(Context)持有策略接口,运行时动态传入具体的策略。
我给你的速记口诀是:“策略模式不是消灭了if-else,而是把if-else从业务代码搬到了工厂/配置里”。这句话非常有用,面试的时候你这么说,会显得你是真懂而不是在背概念。配一个生活化类比:手机地图的出行方式——你输入目的地,它根据你选的是驾车/公交/骑行,用不同的算法规划路线,核心的目的地查找逻辑不变,变的只是“出行算法策略”。
4.2 观察者模式:发布-订阅的那点事
观察者模式也是高频中的高频,核心词是“通知”。微博的“关注”机制就是最好的例子:你关注了一个博主,博主发微博,所有粉丝都能收到动态,你不关注了,就收不到。
这个模式有两个关键角色:主题(Subject)和观察者(Observer)。主题持有一个观察者列表,自己状态变化时遍历列表调用每个观察者的更新方法。最需要注意的坑有两个:一是主题和观察者之间的循环依赖,A观察B,B又观察A,一个状态变化可能引发无限循环;二是观察者数量太多或者更新逻辑太重,主题一旦通知就会阻塞很久。所以在实现的时候,要谨慎设计通知的粒度,必要的时候用异步通知,避免“一个对象变了,整个系统跟着抖三抖”。
GUI事件监听、消息队列的发布订阅模型、监听器模式,底层都是观察者的思想。你在简历上写到“使用消息队列解耦”,面试官很可能就会追问一句:消息队列的发布订阅和观察者模式有什么关系?这个问题你要是能答出“观察者是进程内的同步/异步通知,消息队列是跨进程的可靠消息投递,两者思想一脉相承,但消息队列多了持久化、回溯、削峰等能力”,面试官会觉得你Level完全不一样。
4.3 模板方法模式:父类定骨架,子类填细节
模板方法模式是最“像继承”的模式,它的核心词是“骨架”。父类定义一个算法流程,里面某些步骤是固定的,某些步骤是抽象的、留给子类实现的。
给你一个生活化类比:煮速冻饺子和煮手擀面,大流程都是“烧水→下锅→煮熟→捞出来”,但“下锅后要不要加凉水、煮多久”这些细节各自不同。这就叫父类锁定骨架,子类覆盖细节。
很多框架里都有模板方法的身影:Spring的JdbcTemplate执行SQL的流程是固定的(获取连接→执行语句→处理结果→关闭连接),里面“怎么处理每一行数据”这一步骤交给子类的回调去实现。考试里它还有一个经典对比题:模板方法和策略模式的区别。记住一条主线——模板方法是“继承+固定流程”,子类是继承关系,算法骨架被定了;策略模式是“组合+算法替换”,客户端完全替换整个算法,不涉及继承骨架。
4.4 责任链模式和状态模式:两种特别容易记乱的状态机思路
责任链模式的记忆锚点是“审批流程”。你提交一个报销单,金额小于1000组长批,小于5000经理批,超过5000总监批。这个申请沿着一条链传递,直到某个节点处理。好处是发起者不用知道谁最终能处理,也不需要改一堆if-else知道“我的单子该给谁”,职责天然解耦。
如果把责任链再扩展一下,它就变成了FilterChain(过滤器链),Java Web里的Filter就是标准实现,Spring MVC的拦截器也是。所以这个模式非常实用,不是那种只在考试里出现的概念。
状态模式的核心词是“状态决定行为”。一个对象在不同状态下,同一行为的表现完全不同。最经典的例子是订单状态:待付款状态下点“支付”能成功,已发货状态下再点“支付”就报错。用大量if-else可以写,但状态一多,逻辑就会缠成一团乱麻。状态模式把每个状态封装成一个类,状态之间的切换逻辑放在了状态类内部,代码结构清晰,而且新增状态时不用改动大段历史逻辑。
有一个很容易混淆的点:状态模式和策略模式的类图非常像。区分方式是看意图——策略模式是“算法族可以互相替换,客户端主动选择”,状态模式是“状态内部自动流转,客户端无感知”。策略再像、状态再像,本质上是一枚硬币的两面:策略是主动切换,状态是被动流转。
4.5 命令模式、迭代器模式、备忘录模式等:最低限度的记忆要求
对于不是主要考点的行为型模式,我建议不用背太深,但要做到“看到类图能认出来、给需求能说出意图对应哪几个模式”。
命令模式的核心是把请求封装成一个对象,这样你可以把请求排队、记录日志、支持撤销。类比就是餐厅里的“服务员记菜单”:顾客不用直接跟厨师喊,而是把“要什么菜”写在小票上交给厨师,小票就是命令对象,小票攒一摞还能排队,厨房还能根据小票追溯历史。Jenkins的构建任务、操作系统的操作记录,都有命令模式的影子。
迭代器模式的核心是“遍历与容器解耦”。为什么for-each能用?因为集合实现了Iterable接口,提供了统一的迭代方式。这样客户端不关心底层是数组还是链表,反正都能用同一种方式遍历。记它的锚点就是:医院体检的排队叫号系统——你不关心前面排的是谁、从哪个队列来的,你只关心叫到自己。
备忘录模式是“存档/回档”,适合做撤销功能的场景。最简单的类比就是游戏存档:打Boss之前存个档,挂了之后读档重来。实现的时候要注意大对象复制带来的内存开销,生产环境里通常不会保存全量状态,而是保存差异或压缩快照。
剩下的中介者、解释器、访问者三个模式,记一个最基本的场景就行:中介者解决“对象网状交互”的问题,可以用楼管大妈帮住户传话这个例子帮助记忆;解释器用来实现某个特定语言/规则的解释,正则表达式就是一个典型应用;访问者解决“不影响元素类的前提下,增加新的操作”,经典案例是编译器里AST上的语法检查、类型检查、代码生成,但日常业务开发里用得很少,能说出案例即可。
5. 考前冲刺与面试实战:用“一句话识别法”快速定位模式
速记的最终目的是在考场上、面试现场快速给出正确答案。我把自己一直用的实战方法分享出来,这套方法叫“一句话识别法”:拿到题目,不要一上来就背定义,先问自己三个连环问题:这个场景的重点是“创建对象”还是“组织结构”还是“对象协作”?在这个维度里,最核心的矛盾是什么——是想解耦创建逻辑?想不改变原有代码就增强功能?还是想让多个对象协同工作的时候不互相直接依赖?针对这个核心矛盾,模式的名字基本就浮出水面了。
我整理了一个面试/考试高频场景速查表,你可以把它当作“刷题前的思维导图”:
| 业务场景 | 核心矛盾 | 首选模式 |
|---|---|---|
| 日志管理器要全局唯一 | 保证全局只有一个实例 | 单例模式 |
| 多种数据库连接方式切换 | 运行时替换数据库访问算法 | 策略模式 |
| 新建一种支付方式,老代码尽量不改 | 对扩展开放、避免改老代码 | 工厂方法模式 |
| 第三方接口不兼容,但不想改它的代码 | 解决接口不一致 | 适配器模式 |
| 给类加权限校验/日志,但不想动原逻辑 | 在原有功能上叠加横切逻辑 | 代理模式 / 装饰器模式 |
| 无限套娃的树形菜单 | 统一处理部分与整体 | 组合模式 |
| 用户修改资料后多个模块需要同步更新 | 一对多通知解耦 | 观察者模式 |
| 一套完整报销流程,不同金额走不同审批人 | 请求按顺序传递、解耦发送者和接收者 | 责任链模式 |
| 复杂SQL执行流程固定,只需定制结果处理 | 固定骨架、定制变化步骤 | 模板方法模式 |
| 对象在不同状态下有完全不同的行为 | 状态转移决定行为,减少if-else | 状态模式 |
| 复杂子系统只想暴露一个简单入口 | 降低客户端使用成本 | 外观模式 |
再补一个你可能在笔试里遇到的点:给你一段代码,让你画类图判断是什么模式。这个技巧也很简单,看到一个接口、多个实现类,然后有一个Context类持有接口引用,并且Context的方法内部调用了接口的方法——十有八九是策略模式。看到父类定义了方法骨架,方法内部调用了若干个抽象方法——模板方法模式。一个类聚合了一堆同样接口的对象,递归调用它们的方法——组合模式。一个类持有自己同一类型实例的引用,并且方法里先处理自己再调用那个引用——责任链模式。看到两个接口各自有多个实现类,其中一个接口的实现里持有另一个接口的引用——桥接模式。
说白了,判断模式的本质就是看类图里的“关系”,继承、实现、聚合、组合、依赖,每一种关系对应了一种“意图形态”。复习的时候,我强烈建议你不要只盯着文字定义,而是把每一个模式的类图画一遍。画图不要求精美,关键是你能自己把接口、实现类、关系的箭头标对。为什么这个动作特别有效?因为考试和面试里,模式识别题最终考的都是类图关系,你平时画多了,看到陌生题就能条件反射地捕捉到关系特征。
6. 设计模式速记的常见陷阱与避坑指南
我见过了太多人在设计模式上投入了大量时间却收效甚微,问题往往不是出在“记不住”,而是出在几个非常典型的误区上。这些坑我自己也踩过,所以专门整理出来,希望能帮你绕开。
6.1 背了一堆模式,却不知道何时该用
这是最常见的问题。很多人的学习路径是:每个模式背定义、背类图、背代码示例,感觉自己都会了。但到了一个大作业或者真实需求里,就完全懵了,不知道怎么选。原因在于:定义和类图是“静态知识”,而选型是一种“动态决策能力”。
想培养这个能力,最有效的方法就是我前面说的“三维坐标+核心矛盾”法。拿到需求先分类,分类完再找矛盾。你还需要刻意练习:随便想一个需求,比如“点餐系统”“教务管理系统”“停车场计费系统”,然后试试用你学过的模式去重新设计一遍。这种练习做十次,比你抄十遍代码都管用。我在带人的时候,经常让他们把需求用文字描述,再用模式去重写设计思路,效果立竿见影。设计模式的学习最终要落到“从需求到设计”的转换能力上,而不是“记下这些模式”。
6.2 为了模式而模式,把简单问题复杂化
设计模式是来解决“变化”的,如果一段代码将来根本不需要变化,用设计模式反而画蛇添足。比如业务规则里明确“付费方式只有现金”,你非套一个策略模式,搞一堆接口和实现类,那不仅没有降低复杂度,反而增加了维护成本。
我见过最夸张的一个例子是,有人为了实现“计算一个数是不是偶数”这个小算法,硬生生套了一个策略模式,接口、两个实现类、一个Context,写了五行代码解决问题的事,搞出了十几个文件。过度设计这件事,资深工程师看到会直接摇头。它带来的问题是:整个项目的抽象层次越来越深,接手的人需要一层层去跳才能看懂这段代码,最终理解了发现只为了一个“if (n % 2 == 0)”。所以面试的时候你还要展现一种判断力——在“简单可扩展”和“过度设计”之间把握分寸感。一个合格的说法是:“这个场景目前没有变化点,所以先用最简单的方式实现,等出现第二个变体的时候再考虑抽象。”
6.3 只记住模式名,说不出来龙去脉
面试官问“你用过哪些设计模式”的时候,很多人会像报菜名一样报出一串,然后面试官随便挑一个接着问“这个模式解决的是什么问题,解决了什么问题,代价是什么”,就卡壳了。这是典型的“背名不背实”。
你应该怎么应对?不要只记住模式名,而是永远围绕“为什么用、怎么用、代价是什么”三件套来讲。比如你简历上写了“使用单例模式管理全局配置”,就要能接着回答:为什么用单例——配置要全局一致;单例怎么保证线程安全——用了双重检查锁加volatile;单例有什么代价——难以测试、隐式依赖、可能成为并发瓶颈;如果不用单例会怎样——每个类各读一份配置,必然出现数据不一致。这套追问链条能走通,才说明你真的理解了这个模式,而不只是在报菜名。我在面试里最常听到的差评回答就是背概念,好评回答就是讲真实项目里踩过的坑和权衡取舍。
6.4 常见问题速查表
最后再放一个速查表,把一些容易踩的问题集中列出。我标准的说法是:平时可以拿它来自测,看自己能不能不看资料就解释清楚。
| 问题 | 核心要点 | 避坑建议 |
|---|---|---|
| 简单工厂和工厂方法有什么区别 | 简单工厂把创建逻辑集中在一个类里,工厂方法把创建逻辑下放到多个子工厂 | 新增产品时,工厂方法不需要改老代码,简单工厂需要改 |
| 代理模式和装饰器模式的区别 | 都是加一层,代理偏“控制”,装饰器偏“增强” | 看意图,不要只看类图 |
| 抽象工厂和工厂方法的区别 | 工厂方法创建单个产品,抽象工厂创建一族相关产品 | 出现“配套一致性”需求(如Windows风格全家桶)优先抽象工厂 |
| 策略模式和状态模式的区别 | 策略是主动换算法,状态是被动随状态流转 | 客户端是否感知状态切换,是重要判断依据 |
| 单例模式一定是线程安全的吗 | 饿汉安全,懒汉不处理则不安全 | 懒汉用双重检查锁+volatile |
| 组合模式和继承的关系 | 组合模式不是用继承解决问题,而是用树形结构组织“部分-整体” | 优先用组合而非继承,是合成复用原则的核心 |
| 责任链模式和观察者模式的区别 | 责任链是“链式传递,一人处理”,观察者是“广播通知,全员响应” | 在“谁来处理”和“谁要感知”之间选型 |
| Java里深拷贝还是浅拷贝 | 原型模式默认是浅拷贝 | 有引用类型成员变量时要手动实现深拷贝 |
设计模式这个东西,你说它难,它其实就二十几个套路,翻来覆去就那些关系;你说它简单,它又要求你把二十几个套路灵活应用在千变万化的业务场景里,没有一个放之四海而皆准的答案。我自己这几年最大的体会是,设计模式最好的学习方式不是“背”,而是“用”——把它当成你工具箱里的工具,先掌握每个工具的一两个经典应用场景,然后在项目里遇到类似问题时自然地掏出来试试。等你用了几次,就会发现在某个需求场景里,你几乎是条件反射地想到了某个模式,而且每一步的原因都能说得明明白白——到这个时候,你才算是真的“速记”成功了。
最后分享一个小技巧:复习的时候,不要按“创建型-结构型-行为型”这种顺序从头看到尾,而是打乱顺序,随机抽一个模式,强迫自己在一分钟内说清楚它的“一句话本质、代码结构核心、典型应用场景、和它最容易混淆的模式”。如果每个模式你都能在一分钟之内走完这四个问题,考场上和面试间里基本上就不会慌了。这套速记法如果对你有帮助,你可以把它整理成自己的复习卡片,反复用,直到形成肌肉记忆。