把23种设计模式全部写完,是在上周的事。回头翻这二十几篇文章,最大的感受是:经历过一遍从概念到落地、从背诵到实战的过程,再回头看设计模式,会发现它没那么玄,也没那么难。这也是我写这篇文章的初衷——给这个系列画个句号,同时给你一张可以直接收藏的总目录。这篇文章会按创建型、结构型、行为型三大类,把23种模式全部过一遍,每个模式讲清楚它解决什么问题、在C#里最常见的写法是什么、哪些坑我替你先踩过了。不管你是在准备面试,还是在改一个历史遗留项目,或者单纯想看看自己的代码有没有“模式味”,这份目录都会对你有用。
1. 为什么程序员都该啃一遍这23种设计模式
1.1 设计模式解决的是“人”的问题
设计模式不是银弹,也不是代码规范,它是一套被反复验证过的、可复用的解决方案。很多人初学时喜欢背类图,觉得记住了UML就是学会了,实际根本不是那么回事。模式真正解决的是“沟通问题”:你说“这里用了装饰器”,别人马上明白你的意图,不用一行一行读代码;你说“这是个策略模式”,审查代码的人立刻知道算法有替换空间。C# 又是很注重“表达力”的语言,委托、事件、LINQ 这些特性让很多模式的实现变得非常简洁,但前提是你得先会认。这个系列写完 23 种模式之后,我更强烈的体会是:模式本身不难,难的是知道什么时候该用、什么时候不该用。
1.2 这份总目录适合谁看
这套目录适合三类人:刚学完 C# 基础、想进阶的人;准备面试、需要系统过一遍知识点的人;维护历史项目、看到别人的“奇奇怪怪”设计但看不懂的人。你可以按需查阅,也可以顺着创建型、结构型、行为型三个分类从头读。每个模式我都会给出核心思想、C# 写法和实际项目里的使用场景,尤其会点出那些“网上说法千篇一律,但实际一写就踩坑”的地方。如果你时间有限,至少把速查表记住,再结合自己项目的代码去对照,进步会比闷头刷书快得多。
2. 创建型模式:五种模式管好对象的“出生”
创建型模式解决的是“怎么把对象造出来”的问题。平时一句new很简单,可一旦对象构造逻辑变复杂、变多变散,直接 new 就会把调用方和具体类死死绑在一起。创建型模式的共同目标,就是让对象的创建过程与使用过程解耦,调用方不一定要关心对象到底是谁、怎么拼装出来的。C# 里因为有了泛型、委托、反射这些工具,创建型模式的写法往往比传统类图更灵活,但核心思路没有变。
2.1 单例模式:全局唯一,但别乱用
单例模式是最容易解释、也最容易背的:保证一个类只有一个实例,并提供一个全局访问点。很多教程上来就写双重锁检查,但如果你的项目是 .NET 6+,直接用Lazy<T>是最省心的做法,默认线程安全,代码也短。public static readonly Lazy<MyService> Instance = new(() => new MyService());这一行基本就够了。真正要小心的不是写法,而是“全局唯一”容易被当成“方便到处取数据”的借口。日志、配置、缓存这类东西适合单例,但如果你发现有五六个单例在互相调用,差不多该考虑是不是把依赖关系搞乱了。
2.2 工厂方法模式:把对象的“生产”交给子类
工厂方法的核心是“定义一个创建对象的接口,让子类决定实例化哪个类”。C# 里最常见的实现是基类提供一个protected abstract的创建方法,子类去重写它。比如报表导出功能,基类定义好“导出”的完整流程,但具体生成 Excel 还是 PDF,交给子类决定。这样做的好处是:新增一种导出格式,你只需要加一个子类,不用改调用方的代码。很多新手会把工厂方法和直接switch混淆,其实工厂方法强调的是“继承体系内由子类决定”,如果只是根据字符串switch出不同对象,那更接近简单工厂,不是同一个东西。
2.3 抽象工厂模式:一整套产品的“生产线”
抽象工厂是用来创建“一系列相关对象”的,它的重点不是单个对象,而是一整套产品之间要配套。比如一套 UI 框架,按钮、输入框、弹窗这三个控件必须风格统一;再来一套暗色主题,就得能同时产出暗色风格的三个控件。C# 里通常用一个接口定义三个方法,分别返回按钮、输入框、弹窗的抽象类型;每个主题类去实现这个接口。相比工厂方法,抽象工厂的扩展成本更高,因为每加一个产品族,所有具体工厂类都得跟着改。所以我在实际项目里,只有确定产品族会成套出现时才会用它,否则更愿意用简单的工厂方法。
2.4 建造者模式:拆解复杂对象的构造过程
当对象的构造函数参数越滚越多,七七八八的配置项加起来十几个,写了还容易传错的时候,建造者模式就该出场了。C# 里最常见的形态是链式写法:new HttpRequestBuilder().WithUrl(...).WithTimeout(...).WithHeader(...).Build()。它的核心不是让你少写几个字,而是把“构造过程”和“对象本身”分离,让调用方只看得到自己关心的参数。要注意的是,建造者模式需要额外维护一套 Builder 类,如果对象本身只有三五个参数,硬上 Builder 反而增加阅读负担。我自己常用的判断标准是:参数超过 6 个,或者存在必填/选填组合要求,再考虑建造者。
2.5 原型模式:克隆自己,而不是重头 new
原型模式是通过复制已有实例来创建新对象,适用于创建成本很高、或者想保留对象当前状态的场景。C# 自带ICloneable接口,但这个接口有个坑:它没有定义到底是浅拷贝还是深拷贝,实现全凭自觉。所以很多老手不建议直接依赖ICloneable,而是自己在类里定义一个Clone()方法,明确注释是浅拷贝还是深拷贝,必要时用序列化或手动逐字段复制。原型模式最常见的误用是拿它当“对象复制工具”,有些场景直接写构造函数来做拷贝反而更清晰。如果只是偶尔复制一次,就别为了“模式”硬造一个 Prototype 抽象。
3. 结构型模式:七种模式理清对象的“组合关系”
创建型管的是对象怎么来,结构型管的是对象怎么“组装”。这一组的核心思想是:通过类与对象之间的关系,把大系统拆成更灵活的小块。在 C# 里,接口、继承、组合、泛型这些东西天然为结构型模式提供了很好的土壤。很多人觉得结构型比创建型难记,那是因为没找到共同点:每个模式其实都在调整“接口”和“实现”的关系。把它理解成乐高积木的接口造型设计,就好记多了。
3.1 适配器模式:老接口也得配上新系统
适配器模式解决的是“接口不兼容”的问题:你手上有一个老组件,接口是旧的,但新系统只认新接口,又不能直接改老组件源码,于是写一层适配器把调用转换一下。C# 里最典型的场景是接入第三方 SDK:对方的回调格式和我内部定义的消息类不一样,我就包一个 Adapter,把第三方 SDK 的输出转成内部标准消息。别小看这个简单封装,它能把“别人家的接口”隔离在系统边界之外。常见坑是适配器越写越厚,最后把业务逻辑也塞进去了,这时候适配器就变成了上帝类,反而比不迁就还要难维护。
3.2 桥接模式:抽象和实现各自独立进化
桥接模式解决的是“维度多导致类爆炸”的问题。比如画图形,按形状分圆形、矩形,按颜色分红色、蓝色,如果继承硬搞,会有 2x2 个类;再加一个颜色或形状,类数量立刻翻倍。桥接的做法是把“形状”和“颜色”各拆成一个独立维度,通过组合而不是继承连接起来。C# 里实现时通常会让抽象层持有一个实现层的接口引用,这样形状和颜色都可以各自扩展而不影响对方。实际业务里,凡是发现有“两个维度同时变化”的迹象,就可以考虑桥接。它的代价是增加了一层间接调用,逻辑上没那么直观。
3.3 组合模式:让单个对象和组合对象用起来一样
组合模式的核心思想是“部分和整体要有一致的操作方式”。最典型的是文件系统:一个文件可以“删除”,一个文件夹也可以“删除”,删除文件夹时会递归删掉里面所有文件,但对用户来说都是同一个删除操作。C# 里通常定义一个抽象节点类,叶节点和容器节点都实现它,容器节点内部持有子节点列表,递归调用子节点方法。做权限系统时,单个权限项和权限组也可以这样设计。组合模式的坑在于容易把公共方法写得太宽,为了照顾所有子类型,导致叶节点里堆了一堆没意义的方法,得靠抛异常兜底。
3.4 装饰器模式:给对象动态加功能
装饰器模式是用来“在不修改原类的情况下动态添加职责”的。C# 里最直观的例子就是Stream:你可以给FileStream套一层BufferedStream,再套一层CryptoStream,每一层都增强一点能力,但外层用起来仍然是Stream。装饰器和继承的区别是:继承是编译期静态绑定,装饰器是运行时组合。实际写业务时,缓存、日志、重试这些横切逻辑都可以做成装饰器。但有个问题容易忽略:装饰器会改变对象的“身份”,如果用装饰器包装过的类型去和原类型做类型判断,结果可能不稳定,这点要在设计时想清楚。
3.5 外观模式:给复杂子系统一个统一入口
外观模式很简单:当一个子系统内部有十几个类、方法调用关系复杂时,对外只暴露一个简单的门面类,调用方只需要调用门面上的几个方法就行。比如一个上传功能,涉及文件校验、压缩、分片、上传、回调通知五个步骤,我不可能让每个业务方都自己拼一遍,而是提供一个UploadService.UploadFile(),把整条流程封装起来。外观模式的代价是,门面类容易逐渐膨胀,最后变成“上帝服务”。我的习惯是门面只做流程编排,不做具体逻辑,具体逻辑仍然放在各模块内部,这样门面永远只是薄薄的一层。
3.6 享元模式:共享内在状态,节省内存
享元模式的目标是“用共享减少对象数量”,适合存在大量相似对象、且每个对象可以拆出“不变的内在状态”和“变化的外在状态”的场景。最经典的例子是文字编辑器里的字符对象:每个字符的字体、字形是固有属性,可以共享;坐标、颜色是外部状态,由外部维护。C# 里可以配合工厂来管理对象池,客户端传入外部状态来获取共享实例。现在很多业务系统内存都够大,反而容易忽视这个模式,但如果你写的是游戏、图形渲染或报表这种大量重复对象的程序,享元模式依然很值得用。
3.7 代理模式:不是本尊,但能帮你挡事
代理模式是给目标对象提供一个代替者,由这个代替者控制访问。代理和装饰器结构很像,但意图不同:装饰器重点在“增强功能”,代理重点在“控制访问”。C# 里很常见的是懒加载代理:一个大型对象创建代价高,先用一个轻量代理占位,等到真正访问时才创建真实对象。还有人用代理做权限校验,比如只有管理员才能执行某个方法。日常开发中,谈到EF Core的延迟加载、Castle DynamicProxy这类库,本质都是在做代理。需要注意代理会增加调用链长度,如果代理里塞了太多逻辑,出了问题排查起来会特别痛苦。
4. 行为型模式:十一种模式掌握对象间的“协作方式”
行为型模式是三类里数量最多的,也是最贴近业务逻辑的一类。前面创建型、结构型主要管“对象长什么样、跟谁搭配”,行为型管的是“对象之间怎么通信、怎么分配职责”。这一组最容易让人记混,因为很多模式看起来都是“把一个流程抽出来”。我的学习方法是抓每个模式最核心的动词:模板方法定骨架,策略换算法,观察者发通知,责任链转手,命令做封装,状态管切换。把这个动词抓到,模式就不会认错。
4.1 模板方法模式:把算法骨架留给基类
模板方法模式是在基类里定义好算法步骤,把某些步骤延迟到子类实现。比如做订单校验:先查用户,再查库存,再冻结金额,这三个步骤骨架固定,但“查库存”在不同的场景下可能走不同仓库,那就让子类去重写这一步。C# 里用抽象方法或虚方法来实现留白即可。这种模式的优点是复用骨架、约束流程,缺点是继承关系让子类和基类耦合得很紧。我见过很多团队把模板方法做成三层基类,最后改一处流程要牵动一堆子类,所以建议基类层级尽量保持浅平。
4.2 策略模式:把算法换成一组可替换的策略
策略模式的核心是“定义一系列算法,把它们各自封装起来,并且可以互相替换”。C# 里最典型的就是排序函数传入IComparer<T>,比较规则不同,排序结果就不同。业务中我常用来处理运费计算:不同物流公司有不同计费策略,每个策略一个类,都实现同一个接口;前端传入物流公司编码,后端从容器里取出对应策略执行。策略模式能有效干掉大段的 if-else,但不要为了消 if-else 而不顾实际,如果只有两三种散落的变化,写多个策略类反而显得笨重。
4.3 观察者模式:发布/订阅的经典实现
观察者模式定义一种一对多的依赖关系,当一个对象状态变化时,所有依赖它的对象都收到通知。C# 里最自然的实现是event和delegate,声明一个public event EventHandler<OrderCreatedEventArgs> OrderCreated;,业务模块里订阅它,事件发生时就自动触发。这个模式在 C# 里用起来太顺手了,但顺手带来的问题是容易过度使用,到处都是事件,调用链变成“散弹式”,非常难调试。我的经验是:事件跨类、跨层时一定要命名语义清晰,参数对象不要裸传一堆零散字段,最好封装成事件参数类。
4.4 迭代器模式:用统一方式遍历集合
迭代器模式让客户端可以用统一的方式顺序访问集合内部元素,而不暴露集合的底层结构。C# 开发者的幸福之处在于语言直接把迭代器做到了底层,foreach就是迭代器模式的语法糖,yield return更是让你可以方便地写自定义迭代器。用迭代器模式最大的价值是“延迟执行”:配合yield return,集合元素可以按需生成,处理大型数据流时不会一次性加载到内存。比如读取超大文件,逐行yield return返回内容,性能会好很多。很少有人会刻意写一个IEnumerator<T>类,但理解迭代器原理对排查foreach中的迭代器异常很有帮助。
4.5 责任链模式:一个请求,一路传递
责任链模式把多个处理对象串成一条链,请求沿着链传递,直到某个对象处理为止。C# 里最常见的是 ASP.NET Core 的中间件机制:请求先经过认证、日志、静态文件等中间件,一层层传入,每一层都可以决定继续传递或短路返回。业务中做审批流也很适合,不同级别审批人组成链,上一级不通过就拦住。责任链的好处是解耦了“谁处理”和“怎么组织处理链”,坏处是链条一长,调试时容易看不清请求到底停在哪一环。所以实现时最好给每个环节加清晰的名字和日志,方便定位。
4.6 命令模式:把请求封装成对象
命令模式把“执行某个操作”封装成一个对象,这样请求发送者和执行者之间就不再直接耦合。C# 里用Action和Func<T>其实是命令模式的简化版,但传统写法通常会定义一个命令接口,包含Execute()和Undo()。命令模式适合做操作队列、撤销重做、宏命令等功能。我写过一个小型编辑器,每个编辑操作都是一个 Command 对象,用栈保存执行记录,撤销时弹出栈顶调用Undo(),效果非常直观。坑在于撤销逻辑本身就很难写,一个命令的Undo()如果不能完全恢复现场,这个模式会变成事故源头。
4.7 状态模式:对象状态变,行为跟着变
状态模式让对象在内部状态变化时改变自身行为,看起来就像换了类一样。C# 里可以定义状态接口,把不同状态下的行为拆到不同状态类中;上下文对象持有当前状态,调用时委托给状态类处理。最常见的例子是订单状态机:待支付、已支付、已发货、已完成,不同状态下同一操作(比如“取消”)的服务逻辑完全不同。用状态模式能把一堆if (state == ...)的代码拆干净。不过状态模式使用门槛不低,状态之间的流转关系如果复杂,很容易出现状态类互相跳转的“网状依赖”,建议画好状态转换图再动手。
4.8 解释器模式:定义一套小语言的语法规则
解释器模式是用来定义一套语法规则,并解释执行这条语法对应的语言。平时业务代码里很少会手写解释器,但你用过的正则表达式、模板引擎、表达式求值库,底层都有解释器模式的身影。C# 里如果想做一个简单的表达式解析,比如“SKU 编号规则解析”,可以用解释器把输入拆成语法树节点,定义每个节点的解释方法。这种模式最大的特点就是容易扩展,也容易掉进性能坑:如果语法规则本身很复杂,解析器可能非常慢,必须配合缓存或优化。我的建议是,除非你真的在写语言、引擎、规则解析器,否则尽量找现成库,别自己实现解释器。
4.9 中介者模式:让对象之间不直接对话
中介者模式用一个中介对象来封装一组对象之间的交互,让对象之间不直接互相引用。典型类比是聊天室:每个人不直接给所有人发消息,而是把消息发给“聊天室服务器”,由它转发。C# 里做 UI 程序时,多个窗体控件之间互相联动,如果每个控件的值变化都直接去修改其他控件,依赖会非常乱;引入一个中介者统一处理联动逻辑,窗体本身会清爽很多。现在很多团队用 MediatR 这类“中介者模式”的库来解耦业务请求,虽然名称一样,但场景更偏向命令查询分离,本质思想还是“别让对象互相直接说话”。
4.10 备忘录模式:把状态存个快照
备忘录模式用于保存对象的某个时刻状态,以便后面可以恢复。C# 里要保存状态,通常有两种做法:一种是定义只读的 Memento 类,私有字段不接受外部修改;另一种是直接把状态序列化成 JSON/二进制存起来。做撤销功能、草稿箱、游戏存档这类功能时非常合适。坑在“快照能不能做深拷贝”:如果对象里嵌套了集合或其他引用类型,保存的 Memento 必须彻底复制,否则你存了一个引用,后面原对象改了,快照内容也跟着变。很多人学这个模式时觉得简单,用的时候才发现浅拷贝会造出各种灵异 bug。
4.11 访问者模式:在不改类的前提下增加新操作
访问者模式让你可以在不修改已有类的情况下,给一组类增加新操作。做法是定义一个访问者接口,里面为每种元素类准备一个重载方法;元素类提供一个Accept(visitor),访问者再根据具体元素类型执行对应逻辑。C# 里常见的应用场景是对 AST 语法树做不同处理:分析、格式化、生成代码,每个处理逻辑写成一个访问者。之所以不常被推荐,是因为引入访问者会让类结构变得绕,而且新增一种元素时,所有访问者接口都得跟着改。除非你明确要针对一组稳定对象不断扩展操作,否则建议慎用。
5. C#里那些“长得不像模式”的模式
GoF 的 23 种模式是通用面向对象设计,但到了 C# 里,语言本身的特性让一部分模式有了“更地道”的表达方式。有时候你已经在用某个模式,只是没有意识到。理解这些“隐形模式”,能帮你把语言特性和设计思想串起来。
5.1 委托和事件:观察者的原生形态
event和delegate不但在语法上支持观察者模式,而且比手写订阅者列表更安全。普通观察者模式需要在主题对象里维护一个订阅者集合,而 C# 的事件关键字帮你处理好了加减订阅、空判断这些细节。使用事件时有个容易忽略的点:事件是在发布者线程上同步触发的,如果订阅者方法耗时长,发布者会被卡住。所以大型系统里经常把事件触发放进消息队列或后台任务,这和观察者模式本身无关,但现实工作里经常要处理同样的线程问题。
5.2 LINQ 和 yield:迭代器模式无处不在
写了多年 C# 的人,几乎每天都用foreach和 LINQ,但未必意识到这背后就是迭代器模式。IEnumerable<T>就是迭代器接口,yield return实现了惰性求值的枚举器。正是因为有这层设计,Where(...).Select(...)才能做到“等你要结果的时候才真正执行”。理解这一点,你就明白为什么 LINQ 查询不能随意多次枚举:一个IEnumerable<T>可能是一次性的数据流,第二次遍历可能得到空集合或重新执行副作用。处理大数据时,这种认知能避免不少隐蔽的性能问题。
5.3 using 与 IDisposable:把清理动作做成模板方法
using语句本质上是try-finally的语法糖,而如果把Dispose()看成算法流程中留白的那一步,它就很像模板方法模式。很多库的设计也印证了这一点:从Stream到DbContext,都要求使用者通过using来保证资源的释放。在自建类时,实现IDisposable不只是为了“销毁资源”,更是为了给调用方一个约定:你用完了我的对象,就必须触发这个清理流程。设计时还要记得“可空释放”与“幂等释放”的问题:Dispose()被调用两次也不应该抛异常。
5.4 扩展方法与依赖注入:模式与语言的碰撞
扩展方法可以让一个类在不被修改的情况下增加新功能,行为上很像“装饰器”的简化版,但它本质是静态方法,无法做到运行时的动态状态管理。依赖注入则经常用来实现工厂模式和策略模式:容器根据接口配置自动装配出对应实例,这就省去了手写工厂类的繁琐。C# 生态里这种“模式被语言重构”的例子越来越常见,写代码时没必要强行套 GoF 类图,只要抓住了模式想解决的核心矛盾,用最地道的语言特性实现它,往往更加优雅。
6. 实战中的模式运用:怎么用才不会用力过猛
模式学完很容易出现一个极端:看什么都像模式,恨不得每个类都套上“身世”。说实话,这种代码比不用模式的还难维护。这里分享一些我踩过坑之后总结的经验。
6.1 从坏味道反推模式
正确姿势是“闻到坏味道,再找模式”,而不是“先找模式,再安排类”。比如看到大量 if-else 在判断类型,可以考虑策略模式或工厂模式;看到某个类改了行为导致一堆调用方受影响,可以考虑观察者模式或中介者模式;看到构造函数参数长到头疼,可以考虑建造者模式。模式是被需求逼出来的,不是被设计“设计”出来的。我在代码评审里最常给新人的建议就是:先写符合直觉的代码,等确实出现重复、耦合、扩展困难时,再用模式去重构,而不是第一版就把所有可能的变化全部抽象化。
6.2 一个真实的组合案例
很多项目不是只用一种模式,而是几种模式组合在一起。我做过一个多渠道支付对接:支付渠道有微信、支付宝、银联,每个渠道的请求参数、验签、回调处理都不同。这里我用工厂模式根据渠道编码创建支付服务;用策略模式封装每个渠道的“下单”和“回调”差异;再用模板方法把“记录日志、落库、通知业务方”的公共流程放到基类,让子类只实现渠道特有的部分。最终调用方看起来非常干净,新增一个渠道也只需要新增一个类,再注册到工厂里。这个案例想说明:模式组合时,每个模式负责一个维度的变化,不要一个类里同时承载多种模式职责。
6.3 常见问题与排查技巧
这里整理几个我实际遇到过的“模式翻车现场”,做成速查表:
| 症状 | 可能原因 | 建议 |
|---|---|---|
| 单例对象在并发下数据错乱 | 单例里存了可变的实例状态 | 单例尽量保持无状态,或把状态放到方法参数里 |
| 工厂方法越来越多分支 | 每个分支只是参数不同,没有真正行为差异 | 考虑改用普通方法 + 参数,别硬套模式 |
| 装饰器套了太多层,堆栈很深 | 每一层都拷贝了公共逻辑 | 检查装饰器是不是只做横切关注点,公共逻辑应下沉 |
| 事件订阅后对象无法被垃圾回收 | 事件源持有订阅者的强引用 | 使用弱事件模式,或在适当时机显式取消订阅 |
| 责任链请求没被处理 | 链尾没有默认处理节点 | 增加“兜底处理器”,至少记录未处理事件 |
| 状态模式到处跨状态跳转 | 状态流转规则散落在各个状态类 | 抽出一个状态流转表统一集中管理 |
7. 23种模式速查表
7.1 一张表记住 23 种模式
为了方便查阅,我把 23 种模式按分类整理成表,每个模式配一句最核心的适用场景。这张表用于面试前快速回忆,也适合贴在公司项目文档首页。
| 分类 | 模式 | 一句话场景 |
|---|---|---|
| 创建型 | 单例 | 全局只需要一个实例 |
| 创建型 | 工厂方法 | 子类决定创建哪种对象 |
| 创建型 | 抽象工厂 | 需要创建一整套配套产品 |
| 创建型 | 建造者 | 对象构造参数多、配置复杂 |
| 创建型 | 原型 | 通过复制现有对象创建新对象 |
| 结构型 | 适配器 | 接口不兼容,需要转接 |
| 结构型 | 桥接 | 多个维度独立变化,避免类爆炸 |
| 结构型 | 组合 | 单个对象和组合对象要统一对待 |
| 结构型 | 装饰器 | 动态给对象增加额外功能 |
| 结构型 | 外观 | 给复杂子系统提供简化入口 |
| 结构型 | 享元 | 大量相似对象需要共享节省内存 |
| 结构型 | 代理 | 控制访问、延迟加载、权限拦截 |
| 行为型 | 模板方法 | 流程骨架固定,部分步骤可变 |
| 行为型 | 策略 | 同一行为有多种可替换算法 |
| 行为型 | 观察者 | 对象状态变化通知其他对象 |
| 行为型 | 迭代器 | 统一方式遍历集合 |
| 行为型 | 责任链 | 多个处理者依次尝试处理请求 |
| 行为型 | 命令 | 把操作封装成对象,便于记录撤销 |
| 行为型 | 状态 | 状态不同,行为不同 |
| 行为型 | 解释器 | 解析并执行自定义语法规则 |
| 行为型 | 中介者 | 对象之间减少直接耦合 |
| 行为型 | 备忘录 | 保存对象状态,支持恢复 |
| 行为型 | 访问者 | 不修改类的前提下增加新操作 |
7.2 怎么用这张表
这张表不是给你背的,而是给你定位的。遇到具体问题时,先从“场景”列找到候选模式,再回头翻对应的详细文章,看它的类图、C# 实现和注意事项。更好的用法是:每次你写完一段代码,尝试在表里找一找,这段代码是否无意中实现了某种模式;如果有,再判断是有意设计还是“模式味”冒出来了。记住,表里 23 个词只是索引,真正有价值的是它背后解决的那类问题。
8. 后续还能怎么学
8.1 学习顺序建议
如果让你从零开始补模式,不建议从抽象工厂或访问者这种高门槛的开始。我推荐按“单例 -> 工厂方法 -> 策略 -> 观察者 -> 装饰器 -> 模板方法”的顺序先上手,这六个模式最常用,也最容易在真实代码里找到切入点。等这些熟了,再碰抽象工厂、建造者、责任链、状态这类偏重流程的模式。命令、备忘录、中介者这类可以按需学,面试遇到时突击一下也能应付。最后再用组合模式、桥接模式、解释器模式、享元模式这种“更偏架构设计”的模式来扩展视野。顺序不是标准答案,但能让你更快获得正反馈。
8.2 三个刻意练习方法
第一,重构练习:从旧项目里挑一个被 if-else 搞乱的类,尝试用策略或工厂模式重写,然后用测试保证行为不变。第二,画图比照:把每个模式画成自己的简化 UML,但不要照抄书,而是画你项目里能对应的例子。第三,讲给别人听:能用自己的话把模式讲明白,才说明真的理解。我每写完一篇设计模式文章就会做一次“给同事讲一遍”的自我测试,讲的时候卡住的地方,通常就是还没吃透的地方。这套目录只是一个起点,真正的收获还是在你自己那一行行代码里。