ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

中介者模式:解耦复杂对象交互的软件设计利器

中介者模式:解耦复杂对象交互的软件设计利器 1. 项目概述为什么我们需要一个“中间人”在软件开发的日常里我们经常会构建一些复杂的系统其中包含大量相互关联的对象。想象一下你正在设计一个航空公司的航班调度系统里面有几十架飞机、几十个登机口、几十个地勤班组、几十个航班计划。如果让每架飞机都直接去和登机口、地勤、塔台沟通代码会变成什么样飞机对象需要持有登机口对象的引用登机口对象需要知道地勤班组的状态地勤班组又得监听塔台的指令……这会导致对象之间形成一张密密麻麻的、高度耦合的网状结构。任何一个对象的改动都可能像多米诺骨牌一样引发一连串的修改。这种系统不仅难以理解维护起来更是噩梦我们称之为“过度耦合”。中介者模式Mediator Pattern就是为了解决这个问题而生的。它的核心思想非常简单引入一个“中间人”来协调所有对象之间的交互。原本需要直接“对话”的对象现在都只和这个“中间人”打交道。飞机不再直接找登机口而是告诉“调度中心”“我准备降落了请安排。”调度中心根据全局状态去协调登机口、地勤和塔台。这样一来对象之间的直接依赖关系被解除了它们都只依赖于中介者。网状结构变成了星型结构系统的复杂度和耦合度都大大降低。这个模式特别适合那些对象间交互关系复杂多变的场景。它不是什么银弹用不好反而会增加中介者本身的复杂度但它提供了一种清晰的结构化思路将“多对多”的混乱通信转变为“一对多”的集中管理。接下来我们就深入拆解这个模式的里里外外看看它到底是怎么工作的什么时候该用以及怎么用好它。2. 核心原理与结构拆解2.1 模式的定义与核心思想中介者模式属于行为型设计模式。在《设计模式可复用面向对象软件的基础》一书中它的意图被定义为用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用从而使其耦合松散而且可以独立地改变它们之间的交互。这句话里有几个关键词“封装一系列的对象交互”、“耦合松散”、“独立地改变交互”。这精准地概括了中介者的价值。它不是简单地传递消息而是接管了对象间交互的协调逻辑。这个协调逻辑原本是分散在各个对象内部的现在被集中到了中介者这一个地方。这样做的好处是当交互规则发生变化时比如飞机降落流程增加了新的安全检查步骤我们只需要修改中介者这一个类而不是去修改所有相关的飞机、登机口、地勤类。2.2 UML类图与角色解析要理解一个模式看它的标准UML类图是最直观的。中介者模式的结构非常清晰主要包含四个角色中介者Mediator 定义与各个同事对象进行通信的接口。它通常是一个接口或抽象类声明了诸如notify(sender: Colleague, event: string)这样的方法用于接收来自同事对象的通知。具体中介者ConcreteMediator 实现中介者接口。它知道所有具体的同事对象并负责协调它们之间的交互。它持有所有同事对象的引用或通过某种方式能定位到它们并根据具体的业务逻辑在收到某个同事的通知后去调用其他同事的方法。这是整个模式的大脑和调度中心所有的协调逻辑都写在这里。同事类Colleague 定义所有同事类的通用接口或抽象类。每个同事对象都知道它的中介者对象但不知道其他任何同事对象。当它需要与其他同事通信时不会直接调用对方而是通过中介者来转发请求或通知。具体同事类ConcreteColleague 实现同事类接口。每个具体同事对象在需要与其他同事通信时都会与它的中介者进行通信。它通常会在构造函数中接收一个中介者引用并在自身状态改变时调用中介者的通知方法。它们之间的关系是具体中介者聚合了多个具体同事对象通常通过列表或映射持有引用。而每个具体同事对象都关联着一个中介者对象。交互的流程是同事A - 中介者 - 同事B/C/D。注意这里有一个关键的设计决策是让同事对象持有中介者的引用还是让中介者主动发现同事绝大多数情况下我们采用前者即在创建同事对象时将中介者实例注入进去。这样同事对象在需要时就能直接调用中介者。这种方式更符合依赖注入的原则也使得对象间关系更清晰。2.3 交互流程与通信机制让我们用一个更具体的例子来走一遍流程。假设我们有一个简单的聊天室程序。同事类User用户中介者ChatRoom聊天室没有中介者时每个User对象都需要维护一个所有其他User的列表。当张三想发消息时他需要遍历这个列表调用李四、王五等每个对象的receiveMessage方法。增加一个新用户赵六就需要修改所有现有用户的列表。这是典型的网状耦合。使用中介者后每个User对象在创建时都会加入ChatRoom中介者。User对象内部只持有ChatRoom的引用。当张三发送消息时他调用chatRoom.sendMessage(“大家好”, this)这里的this代表发送者自己。ChatRoom的sendMessage方法被触发。它知道当前聊天室里所有在线的User除了发送者张三。ChatRoom遍历在线用户列表对每一个用户李四、王五调用其receiveMessage(“张三说大家好”)方法。消息成功广播而张三完全不需要知道李四和王五的存在。这个流程的核心在于通信的发起者同事只与中介者对话由中介者决定如何将消息或事件分发给其他相关的同事。中介者内部封装了分发的逻辑比如是广播给所有人还是只发给特定分组或者需要进行消息过滤。3. 模式优缺点深度剖析中介者模式是一把双刃剑用对了地方能化繁为简用错了地方则会画蛇添足。我们必须清晰地认识到它的两面性。3.1 核心优势为何它能成为“解耦利器”极大降低类间耦合提升复用性这是中介者模式最根本、最显著的优势。它将对象间错综复杂的交互关系从每个对象内部剥离出来集中到中介者中。这样一来同事类就变得非常“干净”和独立。它们只负责自己的核心状态和行为以及通过一个统一的接口与中介者通信。这使得每个同事类都可以被更容易地复用到其他系统中只要新系统提供兼容的中介者即可。例如一个设计良好的Button同事类既可以用在对话框中介者里也可以用在工具栏中介者里。简化对象间的交互将多对多关系简化为一对多这是从结构上带来的简化。原本是网状结构N个对象之间可能有 N*(N-1) 条潜在的通信链路。引入中介者后变成了星型结构每个对象只与中介者这1个中心点通信通信链路减少到N条。这使得系统的结构图变得异常清晰易于理解和绘制。集中控制交互逻辑使变更局部化由于所有的协调逻辑都集中在中介者这一个类中因此当业务规则、交互流程发生变化时我们只需要修改这一个类。比如在聊天室例子中如果新增一个规则“夜间23点后禁止发言”我们只需要在ChatRoom的sendMessage方法开头加一个时间判断即可完全不需要改动任何一个User类。这符合“单一职责原则”将交互职责分离了出来。便于统一管理和监控因为所有交互都经过中介者中介者自然成为了一个绝佳的监控点和控制点。我们可以轻松地在中介者里加入日志记录、权限检查、流量控制、事务管理等功能。例如可以在消息分发前记录日志或者对某些用户的发言进行过滤。3.2 潜在缺陷与使用陷阱中介者可能演变为“上帝对象”God Object这是中介者模式最容易掉入的陷阱。随着系统演进越来越多的交互逻辑被塞进中介者它可能变得极其庞大和复杂承担了过多的职责。一个几千行代码的中介者类会难以理解、难以测试、难以维护最终成为新的瓶颈和单点故障。这违背了设计模式的初衷从“对象耦合”恶化成了“中介者耦合”。增加了系统的复杂度与理解成本对于简单、交互关系固定的少数几个对象引入中介者无疑是过度设计。原本两个对象A和B直接调用A.doSomethingWith(B)一目了然。现在变成了A.notify(mediator)-Mediator.handleEventFromA()-Mediator.callB()。多了一层间接性对于阅读代码的人来说需要多跳转一次才能理解完整的交互流程增加了心智负担。性能上的细微损耗多了一层调用转发理论上会引入微小的性能开销。虽然在绝大多数应用场景下可以忽略不计但在对性能极其敏感的底层系统或高频调用路径上需要谨慎评估。实操心得在实际项目中判断是否使用中介者模式我有一个简单的“5-3原则”作为参考当有超过5个以上的对象之间存在复杂的、动态的交互并且这种交互关系可能频繁变化超过3种主要变化维度时才值得考虑引入中介者。对于静态的、固定的、对象数量少的交互直接引用往往是更简单清晰的选择。另外要时刻警惕中介者类的膨胀如果发现它超过了300行代码这只是一个经验值就要考虑是否应该按功能将其拆分成多个更细粒度的中介者或者引入“中介者层级”的概念。4. 典型应用场景与案例甄别中介者模式不是凭空想象出来的它在众多软件系统中都有经典体现。理解这些场景能帮助我们在自己的设计中快速识别出使用中介者的时机。4.1 GUI开发与消息传递系统这是中介者模式最经典的应用领域。几乎所有的现代GUI框架如Java Swing, .NET WinForms, Qt 乃至前端框架如ReactRedux的Flux架构思想都深度使用了中介者模式的思想。场景描述一个对话框上有多个控件文本框、下拉框、复选框、多个按钮。这些控件之间需要联动比如选中某个复选框需要禁用某个文本框改变下拉框的选项需要更新另一个列表框的内容点击“确定”按钮需要收集所有控件的值进行验证。中介者应用对话框本身或一个专门的DialogMediator类就扮演了中介者的角色。所有控件同事都将事件发送给对话框中介者。中介者知道所有控件的引用并根据事件类型和业务逻辑来更新其他控件的状态。这样按钮不需要知道文本框的存在复选框也不需要直接操作下拉框它们都只和对话框“说话”。示例在Swing中虽然控件间可以通过直接注册监听器来通信但复杂的业务逻辑通常会引入一个控制器Controller或使用Action对象来集中处理这本质上就是中介者模式的变体。4.2 聊天室、游戏大厅与多用户协作系统如前所述聊天室是诠释中介者模式的完美例子。类似的还有在线游戏大厅协调玩家匹配、房间创建、协同编辑工具如Google Docs 协调多个用户的编辑操作等。核心需求需要将一个用户/实体的动作高效、可控地广播或分发到一组其他用户/实体同时发送者不应依赖于具体的接收者。中介者价值中介者聊天服务器、游戏大厅服务器、协同服务器负责管理所有连接、维护用户列表、处理消息路由私聊、群聊、广播、执行规则禁言、敏感词过滤。客户端同事只需要连接服务器并收发消息。4.3 航空调度、交通控制与物联网枢纽这些是更贴近“中介者”本意的领域系统。航空管制系统塔台就是中介者。所有飞机同事向塔台报告位置和意图塔台根据全局空域情况向每架飞机下达起飞、降落、高度调整等指令避免冲突。飞机之间不直接通信。智能交通信号系统区域交通控制中心是中介者。它接收来自各个路口摄像头、地磁传感器同事的车流数据经过分析后统一协调该区域内所有红绿灯同事的配时方案以实现区域整体通行效率最优。物联网平台物联网云平台是典型的中介者。成千上万的设备传感器、执行器将数据上报到平台平台进行数据处理、分析和存储然后根据规则或应用指令向下发送控制命令给特定的设备。设备之间不直接交互。4.4 企业应用中的工作流与业务流程引擎在企业级软件中复杂的业务流程往往涉及多个部门、多个角色、多个系统。场景描述一个采购审批流程涉及申请人、部门经理、财务、采购员、供应商等多个角色。流程的走向下一个处理人是谁取决于当前节点的审批结果和业务规则如金额大小。中介者应用工作流引擎如Activiti, Flowable就是中介者。它定义了流程模型BPMN并在运行时负责驱动流程实例。当“部门经理审批”节点完成时工作流引擎根据结果同意/驳回和流程定义自动计算出下一个节点可能是“财务审核”或“流程结束”并创建相应的任务。各个参与角色同事只与工作流引擎交互领取任务、完成任务而无需知道流程的其他参与者是谁。引擎集中管理了所有流程状态和流转逻辑。如何甄别场景当你发现代码中出现了大量的对象间直接引用并且修改一个对象的交互逻辑需要牵连修改多个其他对象时或者当你发现对象之间的调用关系图看起来像一团乱麻时就应该停下来思考“这些对象是不是在讨论一件共同的事情是否需要引入一个主持人来管理这场讨论”这个“主持人”就是潜在的中介者候选。5. 实战示例从零实现一个聊天室系统理论讲得再多不如动手写一遍。我们用一个完整的、可运行的TypeScript示例来实现一个简单的聊天室并逐步迭代展示中介者模式如何优雅地处理扩展。5.1 基础版本实现核心中介者与同事类首先我们定义中介者接口和同事基类。接口的使用让系统更灵活未来可以替换不同的中介者实现。// 中介者接口定义通信契约 interface ChatMediator { sendMessage(message: string, user: User): void; addUser(user: User): void; } // 同事类抽象所有用户的基类 abstract class User { protected mediator: ChatMediator; protected name: string; constructor(mediator: ChatMediator, name: string) { this.mediator mediator; this.name name; // 用户创建后自动向中介者注册自己 this.mediator.addUser(this); } // 发送消息委托给中介者 public send(message: string): void { console.log(${this.name} 发送消息: ${message}); this.mediator.sendMessage(message, this); } // 接收消息由中介者调用 public abstract receive(message: string): void; }接下来实现具体的中介者——聊天室。它维护一个用户列表并实现消息分发逻辑。// 具体中介者聊天室 class ChatRoom implements ChatMediator { private users: User[] []; public addUser(user: User): void { this.users.push(user); console.log(用户 ${user[name]} 加入聊天室。); } public sendMessage(message: string, sender: User): void { console.log(聊天室转发消息...); // 关键逻辑遍历所有用户除了发送者让他们接收消息 for (const user of this.users) { // 不将消息发送给自己 if (user ! sender) { user.receive(${sender[name]} 说: ${message}); } } } }最后实现具体的用户类。这里我们创建两种用户普通用户和VIP用户仅用于演示扩展性VIP用户接收消息时有特殊提示。// 具体同事类普通用户 class CommonUser extends User { public receive(message: string): void { console.log([普通用户 ${this.name} 收到] ${message}); } } // 具体同事类VIP用户 class VIPUser extends User { public receive(message: string): void { console.log(✨ [VIP用户 ${this.name} 收到] ${message} ✨); } }现在让我们组装系统并运行// 客户端代码 function main() { // 1. 创建中介者 const chatRoom: ChatMediator new ChatRoom(); // 2. 创建用户同事并注入中介者 const alice: User new CommonUser(chatRoom, Alice); const bob: User new VIPUser(chatRoom, Bob); const charlie: User new CommonUser(chatRoom, Charlie); // 3. 用户开始聊天他们之间没有直接引用 console.log(\n--- 聊天开始 ---); alice.send(大家好我是Alice); bob.send(Hi all, Bob here.); charlie.send(欢迎Bob); } main();运行这段代码你会看到如下输出用户 Alice 加入聊天室。 用户 Bob 加入聊天室。 用户 Charlie 加入聊天室。 --- 聊天开始 --- Alice 发送消息: 大家好我是Alice 聊天室转发消息... [普通用户 Bob 收到] Alice 说: 大家好我是Alice ✨ [VIP用户 Charlie 收到] Alice 说: 大家好我是Alice ✨ Bob 发送消息: Hi all, Bob here. 聊天室转发消息... [普通用户 Alice 收到] Bob 说: Hi all, Bob here. ✨ [VIP用户 Charlie 收到] Bob 说: Hi all, Bob here. ✨ Charlie 发送消息: 欢迎Bob 聊天室转发消息... [普通用户 Alice 收到] Charlie 说: 欢迎Bob [普通用户 Bob 收到] Charlie 说: 欢迎Bob可以看到Alice、Bob、Charlie之间没有任何直接依赖。Alice发送消息时她只调用了chatRoom.sendMessage()。聊天室中介者负责将消息准确地分发给其他所有用户。Bob作为VIP用户其接收消息的行为与普通用户不同这个差异被封装在各自的receive方法中中介者无需关心它一视同仁地调用user.receive()。这充分体现了“交互集中化”和“个体差异化”的分离。5.2 功能演进为聊天室增加私聊与消息过滤现在产品经理提出了新需求1. 支持私聊功能2. 需要过滤敏感词。如果没有中介者我们需要修改每个User类让它们知道如何寻找特定用户并发送私信还要在每个发送消息的地方添加过滤逻辑。这将是灾难性的。而有了中介者我们只需要修改一个地方——ChatRoom类。class EnhancedChatRoom implements ChatMediator { private users: Mapstring, User new Map(); // 改用Map方便按名称查找 public addUser(user: User): void { this.users.set(user[name], user); console.log(用户 ${user[name]} 加入聊天室。); } // 公共消息发送 public sendMessage(message: string, sender: User): void { const filteredMsg this.filterSensitiveWords(message); // 过滤敏感词 if (!filteredMsg) { console.log(消息包含敏感词已被拦截。); return; } console.log(聊天室广播消息...); for (const [name, user] of this.users) { if (user ! sender) { user.receive([广播] ${sender[name]}: ${filteredMsg}); } } } // 新增私聊功能 public sendPrivateMessage(message: string, sender: User, recipientName: string): void { const filteredMsg this.filterSensitiveWords(message); if (!filteredMsg) { console.log(私聊消息包含敏感词已被拦截。); return; } const recipient this.users.get(recipientName); if (recipient) { console.log(聊天室转发私聊消息...); recipient.receive([私聊] ${sender[name]} 对你说: ${filteredMsg}); } else { console.log(用户 ${recipientName} 不存在。); } } // 新增简单的敏感词过滤 private filterSensitiveWords(message: string): string { const sensitiveWords [暴力, 攻击]; // 示例敏感词库 let filtered message; for (const word of sensitiveWords) { if (filtered.includes(word)) { filtered filtered.replace(new RegExp(word, g), ***); } } return filtered; } } // 扩展User基类增加私聊方法 abstract class EnhancedUser extends User { constructor(mediator: ChatMediator, name: string) { super(mediator, name); } // 注意这里需要将mediator断言为EnhancedChatRoom以调用新方法 // 更好的做法是定义更丰富的中介者接口这里为演示简化 public sendPrivate(message: string, to: string): void { console.log(${this.name} 发送私信给 ${to}: ${message}); (this.mediator as EnhancedChatRoom).sendPrivateMessage(message, this, to); } }使用方式const enhancedRoom: EnhancedChatRoom new EnhancedChatRoom(); const alice2 new CommonUser(enhancedRoom, “Alice”); const bob2 new VIPUser(enhancedRoom, “Bob”); alice2.send(“这个游戏真不错”); // 正常广播 alice2.send(“含有暴力的内容”); // 触发过滤被拦截或替换 alice2.sendPrivate(“嘿Bob有个秘密告诉你”, “Bob”); // 发送私信在这个演进版本中我们轻松地增加了两个核心功能而User类几乎不需要改动仅需继承新基类并调用新方法。所有的复杂逻辑——寻找收信人、过滤敏感词——都被封装在EnhancedChatRoom中。这就是中介者模式“集中控制变更”威力的体现。当未来需要增加更多功能比如群组管理、消息撤回、某人功能时我们依然只需要修改或扩展中介者类。实操心得在设计中介者接口时要有一定的前瞻性。虽然不提倡过度设计但可以预判常见的交互方向。例如最初的ChatMediator接口可以设计为包含broadcast,sendPrivate,sendToGroup等方法。这样具体中介者的实现可以按需提供而同事类依赖的是稳定的接口不会因为中介者内部逻辑的增强而频繁修改。6. 中介者模式与其他模式的对比与关联在设计中模式很少孤立使用。理解中介者与相似模式的区别以及如何结合其他模式能让你在架构选择上更加游刃有余。6.1 中介者 vs. 观察者模式调度员与广播站这是最容易混淆的一对。两者都用于处理对象间的通信但目的和结构截然不同。观察者模式Observer 定义了一种一对多的依赖关系。当一个主题Subject对象的状态发生改变时所有依赖于它的观察者Observer对象都会得到通知并自动更新。主题不知道也不关心观察者具体是谁、有多少个。它就像一个广播站只管发出信号谁接收、接收后做什么它不负责协调。核心是状态的同步和通知。中介者模式Mediator 定义了一个封装一组对象如何交互的对象。中介者知道所有同事对象并且负责协调它们之间的复杂交互。同事对象之间不直接通信。它就像一个调度员或交通警察不仅传递消息还根据复杂的规则决定消息传给谁、怎么传、何时传。核心是交互的协调和控制。关键区别耦合方向观察者模式中主题对观察者是单向的、松散的依赖主题持有观察者列表。中介者模式中中介者和同事是双向的、紧密的依赖互相持有引用或通过接口知晓。职责观察者的主题只负责“通知变化”不负责处理观察者之间的逻辑。中介者则深度介入包含了同事间交互的业务逻辑。关系复杂度观察者处理的是简单的广播/订阅关系。中介者处理的是复杂的、定制化的多对象协作关系。如何选择如果你只是需要在一个对象状态改变时通知其他多个对象用观察者。如果你需要管理多个对象之间复杂的、有条件的交互流程用中介者。有趣的是它们经常结合使用中介者内部可以使用观察者模式来管理同事对象的注册与通知机制。6.2 中介者 vs. 门面模式协调者 vs. 简化接口门面模式Facade也为子系统提供了一个统一的接口从而简化了客户端与子系统的交互。这听起来和中介者有点像但它们的关注点不同。门面模式 关注于简化接口为子系统的一组接口提供一个一致的高层接口。它隐藏了子系统的复杂性让客户端更容易使用。门面通常不包含新的业务逻辑它只是将客户端的请求委派给子系统中的相应对象。它的目的是“简化调用”。中介者模式 关注于控制协作它封装了多个对象之间的交互逻辑。中介者本身通常包含核心的业务协调逻辑。它的目的是“解耦交互”。类比门面模式就像酒店的前台你客户端只需要告诉前台“我要入住”前台会帮你协调客房部、财务部等你不需要知道内部有哪些部门、怎么运作。中介者模式就像公司的项目经理他协调程序员、测试员、UI设计师之间的日常工作A做完这个模块交给B测试B发现问题反馈给A和C确保项目流程顺畅。6.3 中介者 vs. 命令模式协调与执行命令模式Command将请求封装为对象从而支持请求的排队、记录、撤销等。它和中者者也可以协作。结合场景在中介者模式中同事对象发给中介者的请求本身就可以被封装成一个命令对象。例如在聊天室中User发送的“发送消息”请求可以是一个SendMessageCommand对象。中介者接收到这个命令对象后可以将其放入队列、记录日志然后再执行它即分发给其他用户。这样中介者就获得了对交互过程更强的控制能力比如实现异步消息处理、消息持久化、撤销发送等功能。6.4 中介者模式的常见变体与演进在实际项目中纯粹的标准中介者模式可能会演化出一些变体以适应更复杂的需求多中介者层级对于超大型系统一个中介者可能不堪重负。可以引入层级化的中介者。例如在一个分布式游戏系统中可以有全局的“世界中介者”管理多个“区域中介者”每个区域中介者管理该区域内的所有游戏实体玩家、怪物等。这避免了单一中介者过于庞大。事件驱动中介者中介者本身可以基于事件总线Event Bus来实现。同事对象发布事件到总线中介者作为特定事件的订阅者接收到事件后根据事件类型和内容触发相应的协调逻辑再发布新的事件或直接调用其他同事。这种方式进一步降低了中介者与同事的编译时依赖使系统更加灵活。许多前端框架的状态管理库如Vuex, Redux就融合了这种思想。中介者与依赖注入容器在Spring这类IoC容器中容器本身可以看作一个强大的中介者。它管理着所有Bean的生命周期和依赖关系。Bean之间通常不直接相互注入而是通过容器来获取依赖。容器负责解决复杂的依赖图这本质上是中介者模式在对象创建和组装层面的应用。理解这些关联与变体能帮助你在实际设计中不囿于教条灵活运用模式的思想来解决实际问题。模式是地图而不是轨道它指引方向但不规定每一步怎么走。7. 常见问题、陷阱与最佳实践即使理解了原理在实际应用中介者模式时依然会遇到各种坑。下面是我从多年项目中总结出的一些典型问题和应对策略。7.1 如何避免中介者退化为“上帝类”这是中介者模式最大的反模式。症状是Mediator类的代码行数爆炸式增长包含了无数if-else或switch语句来处理各种交互组合难以阅读和维护。解决方案按职责拆分中介者不要试图用一个类管理所有交互。如果系统模块边界清晰可以为每个模块或每个功能领域创建独立的中介者。例如在UI系统中可以为“数据表单”和“图表联动”分别设立FormMediator和ChartMediator。使用策略模式或状态模式将中介者内部的复杂协调逻辑抽取出来封装成一个个独立的“策略”类或“状态”类。中介者只负责持有当前策略或状态并委托给它执行。这样交互逻辑的变化就变成了策略类的替换符合开闭原则。引入事件机制将同事对象发出的通知标准化为不同类型的事件对象。中介者内部可以使用“责任链模式”或“观察者模式”来让一系列的事件处理器Handler来处理这些事件。每个处理器只负责一类特定的交互逻辑中介者变得像一个轻量级的事件路由器。设定代码行数警戒线为中介者类设定一个硬性指标例如不超过500行。一旦接近这个指标就必须强制进行重构和拆分。7.2 中介者与同事类的双向依赖导致循环引用怎么办在标准实现中同事类持有中介者引用用于发送请求中介者也持有所有同事类的引用用于协调。这在某些语言或框架中可能导致序列化、垃圾回收或测试上的问题。解决方案使用接口隔离依赖同事类只依赖中介者接口Mediator中介者只依赖同事类接口Colleague。这降低了耦合度便于测试和替换。采用弱引用或事件订阅在某些场景下中介者可以不长期强引用同事对象。同事对象在创建时向中介者“注册”自己提供一个回调函数或事件监听器中介者只保存这些注册信息。当同事对象销毁时主动从中介者注销。这样可以避免不必要的对象保持存活。依赖注入框架管理生命周期在使用Spring等框架时利用其生命周期管理能力。将中介者和同事都声明为Bean通过Autowired注入。框架会处理好循环依赖通常通过三级缓存和setter注入等方式开发者无需手动处理。7.3 中介者模式是否会导致性能瓶颈由于所有交互都经过中介者理论上中介者可能成为性能热点。特别是在高并发场景下。优化策略异步非阻塞处理不要让同事对象同步等待中介者处理完成。中介者接收到请求后可以将其放入一个任务队列立即返回。由后台线程池异步处理这些任务并通知结果。这能极大提高系统的吞吐量。这就是前面提到的“命令模式中介者”的结合。减少中介者内的同步锁粒度如果中介者内部状态需要共享仔细设计锁策略。例如使用并发集合如ConcurrentHashMap来存储同事引用或者为不同的同事分组使用不同的锁而不是用一个粗粒度的锁锁住整个中介者。评估必要性对于性能极其敏感的底层模块如网络协议栈、游戏引擎核心循环可能需要权衡。有时为了极致的性能可以允许一定程度的直接耦合或者采用更轻量级的通信机制如直接函数调用、内存共享。7.4 在分布式系统中如何使用中介者模式在微服务架构下服务之间需要通信。我们很容易想到用一个中心化的“服务中介者”如API网关、消息总线来协调服务间的调用。实践与挑战服务注册与发现中心如Eureka, Nacos可以看作是一种中介者它管理了所有服务实例的信息。服务消费者通过中介者注册中心找到提供者而不是硬编码地址。API网关如Spring Cloud Gateway, Kong是一个更强大的中介者。它负责路由、认证、限流、监控等所有跨服务的横切关注点让后端服务专注于业务逻辑。消息中间件如RabbitMQ, Kafka是典型的事件驱动式中介者。服务将事件发布到Topic或Queue其他服务订阅感兴趣的事件。消息中间件负责消息的路由、持久化和传递完全解耦了服务之间的直接依赖。分布式下的注意事项此时的中介者本身成为了一个关键的基础设施组件其高可用性和可扩展性变得至关重要。需要采用集群化部署并考虑数据一致性如注册中心的数据和网络分区问题CAP定理。7.5 测试策略如何对中介者模式进行单元测试测试中介者模式的重点是验证交互逻辑是否正确。测试同事类使用Mock对象模拟中介者。测试同事类在特定状态下是否会调用中介者的正确方法并传递正确的参数。// 伪代码示例测试User发送消息 test(‘User should call mediator.sendMessage when sending a message’, () { const mockMediator { sendMessage: jest.fn() }; const user new User(mockMediator, ‘TestUser’); user.send(‘Hello’); expect(mockMediator.sendMessage).toHaveBeenCalledWith(‘Hello’, user); });测试中介者类使用Mock对象模拟同事类。测试中介者在收到来自某个同事的特定通知后是否会按预期调用其他相关同事的方法。// 伪代码示例测试ChatRoom广播逻辑 test(‘ChatRoom should broadcast message to all other users’, () { const mockUser1 { name: ‘A’, receive: jest.fn() }; const mockUser2 { name: ‘B’, receive: jest.fn() }; const sender { name: ‘Sender’ }; const chatRoom new ChatRoom(); chatRoom.addUser(mockUser1); chatRoom.addUser(mockUser2); chatRoom.addUser(sender); chatRoom.sendMessage(‘Hi’, sender); expect(mockUser1.receive).toHaveBeenCalledWith(‘Sender 说: Hi’); expect(mockUser2.receive).toHaveBeenCalledWith(‘Sender 说: Hi’); // 确保没有发送给自己 // (需要根据具体实现来验证这里假设sender没有receive方法或被特殊处理) });集成测试创建真实的中介者和同事对象模拟完整的业务场景验证端到端的交互流程是否符合预期。中介者模式是一个强大的工具但它要求设计者对系统的交互边界有清晰的认识。用对了它能将一团乱麻理得清清楚楚用错了则会增加不必要的抽象层。记住它的本质封装对象间的交互。当你发现对象们正在为了完成一件共同任务而频繁“交谈”时就是考虑请出这位“会议主持人”的时候了。
返回列表