聊设计模式,最容易掉进去的坑,就是把它当成一张“分类表”来背。我见过太多同学,翻开《设计模式》教材,先盯住目录:创建型5个、结构型7个、行为型11个,一共23个,然后开始逐条记忆模式名字、结构图、优缺点,结果背到后面全部混在一起——工厂和抽象工厂分不清,策略和状态像双胞胎,代理和装饰器就差一层皮。然后他们问我:是不是我记性不好?
真不是记性的问题。分类表的价值从来不是让你“背下来”,而是让你在写代码遇到特定场景时,能想起“这类问题其实有成熟解法”。如果你把分类当成终点,那23个模式就只是23个名词;如果你把分类当成地图,那你在面对复杂系统时,走每一步都能找到参照系。
这篇文章不打算给你重新抄一遍教材目录。我想从“为什么分类”讲起,把创建型、结构型、行为型这三类模式的本质讲透,再结合C++和Java两种语言的落地差异,最后聊聊这两年AI应用架构里设计模式怎么“借尸还魂”的。无论你是在准备期末、写大作业,还是已经工作多年回头补基础,这篇文章都能给你一个不一样的观察角度。
1. 为什么“背分类表”的思路从一开始就是错的
1.1 三个分类维度:目的、范围、触发方式
GoF那本《设计模式》里,其实给了两套分类维度,只是大多数人只记住了第一套。第一套就是按目的分:创建型、结构型、行为型,这套分类解决的是“这个模式是干什么用的”。第二套是按范围分:类模式和对象模式,类模式靠继承实现,对象模式靠组合实现,这套分类解决的是“这个模式主要绑在哪个层面”。
但真正容易被忽略的,是第三套隐含维度——模式发生作用的时机。有的模式在类加载或对象创建那一刻就定型了,比如单例、工厂;有的模式在程序运行期不断动态调整,比如策略、观察者、状态。理解这个维度特别重要,因为它决定了你的代码是“编译期就锁死结构”还是“运行期可以自由变形”。
这三套维度合在一起,分类的价值才真正显现:你看到一个问题时,先问“这是创建对象的问题,还是组合对象的问题,还是协调对象行为的问题”,再问“这个问题在编译期能确定吗,还是必须留到运行期”,两步下来,候选模式基本可以缩小到两三个。
1.2 分类表只是索引,触发条件才是地图
很多人把分类表当成终极答案,这是本末倒置。分类表本质上只是一个索引,就像图书馆的索书号,它告诉你哪本书在哪一排架子上,但书里写的是什么内容,你得打开看。
我自己的经验是:记住分类表,不如记住“触发条件”。比如,遇到“需要保证全局只有一个实例”这个语境,你该想到单例;遇到“创建对象时不希望客户端依赖具体类”这个语境,你该想到工厂;遇到“算法有多套实现,想自由切换”这个语境,你该想到策略;遇到“一对多依赖,一个对象状态变化要通知一堆对象”这个语境,你该想到观察者。触发条件是和问题强关联的,而分类表只和模式名字强关联。从问题出发检索模式,远比从模式出发匹配问题要顺。
所以我建议所有初学者做一件事:把分类表扔掉,换成一张“问题-模式”对照表。每个模式后面写清楚“解决什么问题”“什么场景触发”“用什么手段解决”,你会发现原来背不下来的东西,突然变得好记了。
1.3 重新理解“模式是经验的复用”
设计模式之所以被归纳出来,是因为前辈们在大量项目里发现:有些问题会反复出现,有些解法虽然细节不同,但骨架高度相似。GoF把23个模式写成一本书,本质上是在做“经验的标准化打包”。
这也就解释了为什么不同语言里实现设计模式,长得完全不一样。C++里的单例要考虑析构顺序和线程安全,Java里的单例可以用枚举一行写完,Python里甚至直接用模块导入就是天然单例。但如果你只背模式结构图,你会误以为所有语言的单例都必须长成那个样子。这就是“背分类表”带来的最大危害:把模式的“意图”和“形态”混为一谈。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 创建型模式:核心是把“创建权”交给谁,而不是怎么new
2.1 单例:最熟悉也最容易写错
单例是新手入门第一个接触的模式,也是被骂得最多的模式。骂它的人说它是“全局变量的伪装”,但实际上骂的是那种毫无节制地乱用单例的人。单例真正的适用场景有两个特征:一是全局确实只需要一个实例,二是这个实例被多处共享。比如配置管理器、日志对象、数据库连接池,这些场景用单例是很合理的。
最容易写错的地方,不在单例本身,而在多线程下的安全。Java里经典的双重检查锁(DCL)写法,如果不加volatile,会因为指令重排导致拿到一个未初始化完成的对象,这是非常隐蔽的bug。C++里老版本讲懒汉单例要加锁,但C++11之后有了局部静态变量的线程安全初始化,一个Meyers Singleton两行代码就写完,比折腾锁干净得多。
我在实际项目里见过太多“为了单例而单例”的代码:一个工具类,方法全是纯静态的,根本没状态,也用单例包装一层,说是“面向对象”。其实这种类直接用静态方法就够了,强行套单例反而让代码绕。判断一个类要不要单例,就看它有没有需要被共享的内部状态,而不是看它“像不像一个管理器”。
2.2 工厂三兄弟的区别:从“一个产品”到“一个产品族”
简单工厂、工厂方法、抽象工厂这三个词,是期末大作业里最容易丢分的点。很多人靠背定义来区分,但一到案例分析就崩。我推荐用一个电商支付的例子来理解。
一开始,你的系统只支持支付宝,客户端直接new一个AlipayService出来用,
