聊设计模式,最容易掉进去的坑,就是把它当成一张“分类表”来背。我见过太多同学,翻开《设计模式》教材,先盯住目录:创建型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出来用,没问题。后来加了微信支付,客户端里的if判断开始变多。这时候你在某个独立类里写一个静态方法,根据支付方式类型返回对应的支付实现,这就是简单工厂。简单工厂的问题是,每新增一个支付方式,那个工厂类就要改一次,不符合开闭原则。
然后你把工厂也抽象出来:定义PaymentFactory接口,支付宝工厂、微信工厂、银行卡工厂各自实现。客户端拿着一个工厂引用,调用factory.create()就能得到对应的支付对象。新增支付方式时不需要改既有工厂代码,只要新写一个工厂类,这就是工厂方法模式。到了这一步,工厂和产品是一对一的关系。
某天业务扩张,你的系统要出“国际版”和“国内版”:国内版朋友圈,支付、对账、退款一套流程;海外版配套的是另外一套支付、对账、退款实现。这时候你的工厂不再只生产“一个产品”,而是生产“一族产品”。创建一个国内支付工厂,它吐出来的是国内的支付、对账、退款三件套;创建一个海外支付工厂,它吐出来的是另一套三件套。这个就是抽象工厂模式。
三者最本质的区别,一句话就能概括:简单工厂是一个函数根据参数给你不同的东西;工厂方法是每个产品配一个自己的工厂;抽象工厂是一个工厂给你一整套配套产品。把这句话记住,比背十遍定义都管用。
2.3 建造者与原型:解决的是参数与克隆成本
建造者模式在教科书里经常被忽略,但在真实工程里出现频率极高。它要解决的核心问题是:一个对象有太多可选参数,构造器参数列表变得丑陋不堪,客户端每次创建都要传一堆null或者默认值。
Java里很多项目直接用Lombok的@Builder注解,你写起来毫无感知,实际上就是建造者模式的注解实现:用链式调用的方式设置每个参数,最后调build()生成最终对象。你去看Lombok生成的字节码,会发现它给你生成一个静态Builder类,每个字段对应一个方法,这就是教科书里的建造者结构。所以别再说设计模式“不实战”了,你天天都在用,只是用得太顺手没意识到。
原型模式则更适合那些创建成本高、但对象结构相似的场景。比如文档模板、游戏里的怪物预设、Excel表格样式,从原型克隆一份再改局部字段,比从零new一个再逐个赋值要方便。Java里实现Cloneable接口要注意深拷贝和浅拷贝的问题:如果对象内部还有引用类型字段,默认的clone是浅拷贝,两个对象可能共享同一个内部对象,改一处全变,这是坑。
2.4 创建型模式的边界感
创建型模式最大的纪律是“别滥用”。工厂模式是好东西,但如果你一个类只有两种固定实现,三五年都可能不新增,那直接new也不是罪。我见过有人为了“规范”,一个只有俩分支的if判断也硬套一个抽象工厂,结果类数量翻了三倍,可读性反而下降。
选创建型模式时,我会先问自己三个问题:创建逻辑是否复杂到客户端不该知道?产品种类未来是否可能持续扩展?当前维护者能否一眼看懂这个抽象?三个问题里只要有两个是“否”,就可以再想想是不是过度设计。设计模式是工具,不是KPI,类图画得再漂亮,跑不起来也是零。
3. 结构型模式:接口适配与层次控制的两条主线
3.1 适配器:改接口比改系统便宜
结构型模式里,适配器是最好理解的:两个接口对不上,中间加一层翻译。生活里的例子就是电源转换头——你从国外带回来一个两脚插头的充电器,国内的插座是三孔的,直接插不上,那你就需要一个转换头,这转换头就是适配器。
代码里的典型场景是:你接手一个老系统,老系统的支付接口叫pay(String orderId, double amount),新系统统一的支付接口叫execute(PaymentRequest req),两边接口对不上。你有两个选择:改老系统的接口,让所有老代码跟着改一遍,风险巨大;或者写一个适配器类,实现新接口,内部转调老接口,把参数做一个映射。聪明的工程决策几乎都是选后者。
适配器有两种实现方式:类适配器和对象适配器。类适配器靠继承,适配器类同时继承目标接口和已有类;对象适配器靠组合,适配器类持有已有类的引用。Java是单继承,所以类适配器在很多场景下写不出来,对象适配器是主流;C++虽然支持多继承,但多继承带来的菱形问题在项目里很容易惹祸,所以我个人在C++里也更倾向于对象适配器。
3.2 门面:给复杂系统一个接待前台
门面模式经常和适配器一起出现在考卷上,很多人搞混。区别其实很清晰:适配器是为了“对接接口”,门面是为了“简化访问”。你去医院看病,不需要自己跑到化验室、药房、收费处分别操作,只需要去导诊台,导诊台帮你安排所有流程,这就是门面。
在代码里,一个视频处理子系统包含解码、滤镜、编码、字幕四个模块,客户端如果直接跟这四个模块打交道,要了解一堆内部细节。你提供一个VideoProcessor类,内部依次调用四个模块,客户端只需要调processor.process(file),这就是门面。门面不改变各模块的接口,也不给它们加功能,它只是提供一个更简单的入口。
为什么门面模式重要?因为现实中的系统复杂度从来不会因为我们写了接口就消失。每个模块拆得越细,客户端要知道的协作细节就越多,门面正好在中间充当“前台”,把复杂的内部协作封装起来,让调用方只在最外层打交道。这个模式在微服务架构里的BFF(Backend for Frontend)层、在老项目的ServiceFacade类里,都能看到影子。
3.3 装饰器与代理:一个“加料”,一个“把门”
这两个模式是考卷上的惯犯。它们长得像——都是包在一个真实对象外层,结构图几乎一样。但意图完全不同:
装饰器的核心是“增强功能”,它给对象动态加责任,像给咖啡加奶、加糖、加焦糖,层层包裹,每一层都改变行为。经典例子是Java的IO流:new BufferedReader(new FileReader(filename)),BufferedReader就是装饰器,它在FileReader外面加了缓冲功能。
代理的核心是“控制访问”,它拦截调用并决定要不要放行,或者要不要先做事。比如你在访问一个远程服务之前,代理先做权限校验;你访问一个大图对象之前,代理先加载缩略图,等到真需要时才加载原图。代理本身不增强原对象的功能,它只是控制“你能不能碰到原对象”。
一句话记住:装饰器往里加东西,代理在外面把门。做技术选型时,如果目的是增强功能,用装饰器;目的是限制或延迟访问,用代理。这两个模棱两可的选择题,看意图就别选错。
3.4 组合模式:树形递归的心智
组合模式的适用场景,是对象呈明显的树形结构:文件系统有文件夹和文件,文件夹里还可以有文件夹;组织架构有部门和员工,部门下还可以有部门;UI界面有容器和控件,容器里还可以有容器。
组合模式的核心思想,是让“单个对象”和“组合对象”实现同一个接口。这样客户端调用时不用区分“我面对的是一个文件还是一个文件夹”,统一调display()就行。文件就显示自身,文件夹就遍历子节点逐个display。递归是这种结构的天然实现方式。
有人说组合模式是“用透明性换安全性”:统一接口带来的是客户端代码简单,但也意味着那种只有文件夹才有的方法(比如add)会出现在文件类里。解决方法是add方法抛异常或返回不支持,反正设计上没有完美的取舍。在C++和Java里,组合模式最大的坑是递归深度:如果树特别深,递归会让栈溢出,这时候考虑改成显式栈迭代遍历。
4. 行为型模式:把“会变的东西”从代码流程里抽出来
4.1 策略模式:把if-else变成可替换的算法对象
行为型模式的核心思想,和李嘉诚那句“决定成败的是行为”一样:对象创建好了,结构组合完了,剩下的问题是对象之间怎么协作、行为怎么变化。策略模式是行为型里最实用的模式之一。
它的触发条件很典型:你的方法里写了一长串if-else,每个分支是不同算法,而且这些算法未来可能增加或替换。比如结算系统里,普通会员、黄金会员、铂金会员的折扣算法不一样;再比如一个排序服务,数组小时用快排,数组大时用归并。用策略模式,就是把这些算法分别封装成类,实现同一个Strategy接口,然后在运行时把具体策略对象传给上下文。
Java里写策略模式特别舒服,因为Comparator接口本身就是策略模式的经典例子:Collections.sort(list, new XXComparator()),你传入不同的比较器,排序行为就变化。Java 8之后出了lambda,一个策略从“写一个类”变成“写一行lambda”,代价是如果你过度使用lambda,代码可读性可能下降——很多新人一上来就lambda,完全看不出在用什么策略。我会建议:策略逻辑短且直白,lambda没问题;策略逻辑超过十行,还是老实建一个策略类。
4.2 观察者模式:事件驱动架构的基石
观察者模式是GUI和事件驱动系统的基础。它的场景是:一个对象状态发生变化,一堆依赖它的对象需要跟着更新,但你不想让被观察者硬编码这些依赖者。
最经典的例子是天气预报系统:气象站数据一变,手机APP、网站、电视部件都要刷新。如果用硬编码,气象站类里要写死三个更新方法,下次新增一个订阅方,又要改气象站。用观察者模式,气象站只维护一个订阅者列表,数据变化时就通知列表里每个人,具体谁在列表里,气象站不关心。
观察者模式在Java里非常常见,Swing的ActionListener、Spring的事件机制、消息队列里的发布订阅模型,都是它的变体。C++里没有内建的Listener机制,早期写观察者要自己维护观察者列表,而且生命周期问题很头疼——如果观察者已经被销毁,但还挂在被观察者的列表里,通知时就出现悬垂指针。C++20的jthread、stop_token之类的新工具还没法完全替代传统的观察者生命周期管理,所以C++项目里用观察者模式,要特别小心注册和反注册的配对。
4.3 状态模式:把状态转换从if-else搬到对象图里
状态模式解决的问题,和策略模式表面上有相似之处——都是行为随条件变化,但本质不同。策略模式里,算法的选择是外部决定的,上下文不关心算法细节;状态模式里,状态的流转是对象自己管理,而且当前状态决定了下一步能不能执行某个操作。
一个经典的例子是订单状态:待支付、已支付、已发货、已收货、已取消。如果你用if-else写,每个操作里都要判断当前状态能不能执行,状态一多,这段逻辑就爆炸。状态模式的做法是:把每个状态定义成一个类,状态类自己决定“收到什么事件、迁到哪个状态”。Order对象持有一个当前状态的引用,调用pay()时直接委托给当前状态对象处理,状态对象内部决定要不要改状态。
状态模式的最大优点,是把“转移规则”从一堆if里解放出来,变成一张对象图。缺点是类数量会膨胀——每个状态一个类,状态多的时候类就多。但和if-else的复杂度爆炸相比,类数量的增加是可控且清晰的。游戏开发里,角色状态切换(待机、移动、攻击、受击)用状态模式特别顺手,不少游戏框架里甚至直接内置了状态机模块。
4.4 命令、模板方法与责任链:行为模式的其他重头戏
命令模式把“一个请求”封装成一个对象,这样就能把这个对象排队、记日志、做撤销。编辑器里的Ctrl+Z撤销,就是命令模式的标准应用:每个操作都是一个命令对象,撤销列表里存的是逆操作命令。Java里Runnable接口其实也是一种命令模式的变体——你把一段要执行的逻辑封装成对象,交给线程池去执行。
模板方法模式则是最“继承友好”的行为模式:父类定义算法的骨架,把某些步骤留给子类实现。比如所有在线支付的流程都是先下单、再校验、再付款、最后回调,但具体支付方式的校验逻辑不一样。父类把流程框架写死,子类实现校验那一步。这在Java框架里太常见了,Spring的JdbcTemplate、RedisTemplate都大量用模板方法。它的反面问题也很明显:如果父类骨架里步骤太多,子类要覆写的方法太多,模板就变成了一种约束,反而拖累复用。
责任链模式适合处理“多个处理器轮流尝试处理一个请求”的场景:日志系统里,debug消息只写文件,error消息既要写文件又要发邮件,每个处理器把自己的级别和职责定义好,请求沿着链条传递。这个模式在Spring MVC的拦截器链、Servlet的Filter链里都有体现。还有中介者模式,适合对象之间网状通信太多时,引入一个“中介者”集中调度,让多对多变成多对一。典型的例子是聊天室:每个人不需要知道别人在哪,都在聊天室里发消息,聊天室负责转发。
5. C++与Java实现同一模式的差异:考场之外的真实体验
5.1 生命周期:C++要“记得销毁”,Java“随缘回收”
很多人写设计模式作业的时候,用的是“伪代码”思维——画个类图,标一下箭头,直接交差。但到了实际工程里,语言的差异会让同一个模式走样。最明显的是生命周期。
C++里new出来的对象要自己delete,这导致观察者模式变得非常烫手:观察者注册到主题以后,如果观察者先死了,主题还留着它的指针,回调时就崩了。正确做法是主题析构时清空观察者列表,观察者析构时主动反注册。Java有GC,观察者被回收时不会因为忘记反注册而立刻崩溃,但如果观察者列表里还持有强引用,对象根本不会被回收,就会发生内存泄漏。所以Java里做观察者,有人改用WeakReference来存观察者,但随之而来的问题是回调时可能拿到null。
单例在两种语言里的处境差异也很大。C++的Meyers单例依赖局部静态变量,在C++11之后线程安全,而且要额外考虑销毁顺序:如果两个单例互相引用,析构时可能先析构一个,另一个再析构时访问它,就出问题。Java的枚举单例则省心到极致,JVM保证枚举实例只实例化一次,天然线程安全,还能防反射攻击。
5.2 接口形态:Java的interface与C++的纯虚类
策略模式、状态模式这类“面向接口编程”的写法,在Java里非常自然,因为interface是语言一等公民,一个类可以implements多个接口,实现类数量可以随便加。C++没有interface关键字,接口是用含有纯虚函数的抽象类表达的,一个类想实现多个“接口”,就要多继承多个抽象类,这又绕回菱形继承问题。
所以在C++里,我见过很多老练的开发者会刻意减少“抽象基类”的使用,转而用std::function直接传策略逻辑,避免为每个策略单独建一个类。比如一个排序函数,你不需要定义一个Comparator抽象类,直接传一个std::function<bool(int,int)>进去,调用方给个lambda就完了。Java 8之后的lambda也具备这种简化能力,但因为Java的lambda本质是函数式接口的实例,语言层面还是保留了接口这个“壳”。
这给我们的启示是:语言的表达能力会影响模式的实现形态,但模式要解决的问题没变。你用C++还是Java去实现策略模式,最终的意图都是“算法可替换”,只是实现路径一个走多态,一个走函数对象。
5.3 函数对象与lambda:让策略、命令模式的写法“轻”下来
C++11引入std::function和lambda之后,曾经需要写一个类才能实现的策略模式、命令模式,现在可以直接传lambda。Java 8引入lambda之后,同样的事情也发生了。这是设计模式“现代形态”的一个缩影:函数式编程不是取代设计模式,而是为设计模式提供了更轻的表达方式。
但轻并不总是好事。我记得有个项目,某位同事把策略模式全部用lambda写在调用处,调用链一长,谁都不知道这个策略逻辑属于哪个业务模块。后来我们不得不用注释把lambda按业务域标记出来。我的建议是:策略逻辑和业务紧密绑定、但可能会被多处复用时,还是用类封装,命名本身就是文档;策略逻辑真的就是“某个函数内部的一个可选算法”,那lambda就够了,不必为了模式而模式。
5.4 同一种模式,两种“语言性格”
我把这两门语言的“模式性格”总结成一张表,方便你写大作业或者做技术方案时对照参考:
| 维度 | C++ | Java |
|---|---|---|
| 生命周期 | 手动管理,单例要考虑销毁,观察者要考虑反注册 | GC回收,注意强引用引起的泄漏,枚举单例很省心 |
| 接口形态 | 抽象类+多继承,菱形继承要小心 | interface天然多实现,接口是类型约定 |
| 函数式写法 | std::function/lambda,模板可做编译期策略 | lambda+函数式接口,Comparator是典型 |
| 泛型机制 | 模板编译期生成,可特化,有运行时开销 | 泛型擦除,运行期都被当Object处理 |
| 典型应用场景 | 高性能中间件、游戏、引擎 | 企业应用、Web后端、大数据生态 |
这张表不是说你必须按“语言性格”去照搬模式,它只是想提醒你:教材里的类图是简化世界的模型,落到真实工程里,你要用自己手里的语言把模式的意图翻译出来。
6. 多Agent编排时代:设计模式正在以新名字回归
6.1 工具调用本质上是一个门面/适配层
这两年AI应用开发是热点,很多同学学设计模式时会怀疑:这种“老古董”知识在新领域还有用吗?其实非常有用,只是换了一层皮。
一个Agent要完成复杂任务,往往需要调用各种外部工具:搜索引擎、代码解释器、数据库查询接口、内部业务API。这些工具的入参格式、返回结构、认证方式各不相同,如果让Agent的prompt直接对接每个工具的原始协议,那一个工具一个样,模型根本记不过来。于是所有成熟的Agent框架都会做一层“工具封装层”:把每个工具包装成统一的“工具描述+输入参数+输出结果”格式,让大模型通过统一的协议去调用。
这套设计,本质就是门面模式加适配器模式的组合:门面把复杂的子系统隔离开,适配器把异构的外部接口翻译成统一协议。Agent本身甚至不知道它调用的东西是本地函数、HTTP接口还是另一个子系统。
6.2 主从模式:把subagent当“工具”调用的架构含义
这次特别值得聊的是多Agent协作里的主从模式,也就是当前端讨论得很热的一个思路:直接把subagent当作另类的tool进行调用。
过去大家设想多Agent协作,脑海里浮现的是“一群AI坐在一起开会讨论”,每个人有不同的角色,平等地交流。但真正落地的方案里,主Agent才是唯一的决策入口,它不直接处理所有问题,而是把子任务拆解后,逐个交给subagent去处理。对主Agent而言,它面对的subagent接口和面对一个工具接口几乎没有区别:给它一段任务描述,它会返回一个结果。这就是“subagent-as-tool”的本质。
这个设计的好处非常明显:对主Agent来说,工具和subagent被放进同一个资源池,调度逻辑完全统一。路由的时候不需要区分“这次是调用搜索工具,还是调用论文分析subagent”,统一走同一套“选型、调用、结果回收”机制。超时怎么办、重试几次、结果格式怎么校验,这些横切逻辑可以在包在统一接口里,不用为每个工具或subagent单独写一套。这就是门面模式在Agent编排层的体现——主Agent只需要跟一个“前厅”打交道,由前厅决定背后是工具还是subagent在干活。
6.3 多Agent路由与策略模式、责任链
真实的多Agent系统里,还会出现更多设计模式的影子。比如任务路由:同一个用户问题,该用轻量快速问答、深度推理、多轮检索还是调用外部工具,路由模块要根据问题类型选择不同的处理策略。这跟策略模式完全同构:定义统一的策略接口,每个处理链路是一个策略实现,路由模块在运行时选择合适的策略。
再比如安全审核或内容过滤:一条指令在执行前,可能要经过多个检查器逐级过滤,每个检查器只处理自己负责的维度,不满足条件就终止,满足就放给下一个。这跟责任链模式几乎一模一样。事件驱动的Agent里,任务状态变化需要通知UI、日志、下游服务,观察者模式的原则也照样适用。
所以你会发现,设计模式的分类不是封闭的清单,而是一套“架构词汇表”。新领域出现时,老问题会披着新外衣再次出现,而理解模式的意图和触发条件,能让你在新架构里更快地认出“老朋友”。
6.4 模式迁移带来的新学习路径
如果你现在刚开始学设计模式,我的建议是不要被“23个”吓到。先掌握每个模式背后对应的“问题场景”,然后带着这个问题去读代码、去画图、去写小demo,最后再回到分类表,你会发现这张表只是你的第一站,不是终点。
多Agent也好,传统后端也好,内核都是管理复杂性。创建型模式管理对象的产生,结构型模式管理对象的组合,行为型模式管理对象之间的协作,这套分类逻辑放到任何领域的软件架构里都成立。学的时候按这个思路去归类,写代码的时候按这个思路去思考,设计模式就能从“考试背诵题”变成“架构设计语言”。
最后说点个人体会。我自己学设计模式的过程,是从“背名字”到“画结构图”再到“写小demo验证”,最后到“在维护烂代码时突然顿悟‘原来这个坑早就有人用模式填过了’”。后来带团队,我从来不要求成员背出23个模式的名字,只要求他们遇到问题的时候先问一句:这个问题是创建问题、组合问题,还是协作问题?把分类的三大方向想明白,模式就会自己浮现出来。希望这篇文章,也能给你这样的感觉——分类不是终点,而是一把钥匙。
