设计模式
设计模式简介
看懂UML类图和时序图
UML统一建模语言
UML类图及类图之间的关系
类关系记忆技巧
如何正确使用设计模式
优秀设计的特征
面向对象设计原则
创建型设计模式
工厂模式
抽象工厂模式
简单工厂模式
静态工厂模式(Static Factory)
单例模式
建造者模式
原型模式
结构型设计模式
适配器模式
桥接模式
组合模式
装饰器模式
外观模式
享元模式
代理模式
过滤器模式
注册模式(Registry)
行为型设计模式
责任链模式
命令模式
解释器模式
中介者模式
备忘录模式
迭代器模式
观察者模式
状态模式
策略模式
模板模式
访问者模式
规格模式(Specification)
J2EE 设计模式
MVC 模式
业务代表模式
组合实体模式
数据访问对象模式(DAO模式)
前端控制器模式
拦截过滤器模式
空对象模式
服务定位器模式
传输对象模式
数据映射模式(Data Mapper)
依赖注入模式(Dependency Injection)
流接口模式(Fluent Interface)
其他模式
对象池模式(Pool)
委托模式
资源库模式(Repository)
实体属性值模式(EAV 模式)
反面模式
归纳设计模式
本文档使用 MrDoc 发布
-
+
首页
中介者模式
> 中介者模式是一种行为设计模式, 能让你减少对象之间混乱无序的依赖关系。 该模式会**限制对象之间的直接交互, 迫使它们通过一个中介者对象进行合作**。 ![中介者设计模式](/media/202203/2022-03-12_1247040.11256732045307127.png) 中介者模式(Mediator Pattern,调解人、控制器、Intermediary、Controller、Mediator)是用来**降低多个对象和类之间的通信复杂性**。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。 ## 问题 假如你有一个创建和修改客户资料的对话框, 它由各种控件组成, 例如文本框 (TextField)、 复选框 (Checkbox) 和按钮 (Button) 等。 某些表单元素可能会直接进行互动。 例如, 选中 “我有一只狗” 复选框后可能会显示一个隐藏文本框用于输入狗狗的名字。 另一个例子是提交按钮必须在保存数据前校验所有输入内容。 如果直接在表单元素代码中实现业务逻辑, 你将很难在程序其他表单中复用这些元素类。 例如, 由于复选框类与狗狗的文本框相耦合, 所以将无法在其他表单中使用它。 你要么使用渲染资料表单时用到的所有类, 要么一个都不用。 ## 解决方案 中介者模式建议你**停止组件之间的直接交流并使其相互独立**。 这些组件必须调用特殊的中介者对象, 通过中介者对象重定向调用行为,以间接的方式进行合作。 最终, 组件仅依赖于一个中介者类, 无需与多个其他组件相耦合。 在资料编辑表单的例子中, 对话框 (Dialog) 类本身将作为中介者, 其很可能已知自己所有的子元素, 因此你甚至无需在该类中引入新的依赖关系。 绝大部分重要的修改都在实际表单元素中进行。 让我们想想提交按钮。 之前, 当用户点击按钮后, 它必须对所有表单元素数值进行校验。 而现在它的唯一工作是将点击事件通知给对话框。 收到通知后, 对话框可以自行校验数值或将任务委派给各元素。 这样一来, 按钮不再与多个表单元素相关联, 而仅依赖于对话框类。 你还可以为所有类型的对话框抽取通用接口, 进一步削弱其依赖性。 接口中将声明一个所有表单元素都能使用的通知方法, 可用于将元素中发生的事件通知给对话框。 这样一来, 所有实现了该接口的对话框都能使用这个提交按钮了。 采用这种方式, 中介者模式让你能在单个中介者对象中封装多个对象间的复杂关系网。 类所拥有的依赖关系越少, 就越易于修改、 扩展或复用。 ## 目的 本模式提供了一种轻松的多组件之间弱耦合的协同方式。如果你有个 “情报中心”,观察者模式也是个好选择,类似于控制器(并非 MVC 意义上的控制器)。 所有关联协同的组件(称作 Colleague)仅与 MediatorInterface 接口建立耦合,面向对象编程中这是好事,一个良友胜于有多个朋友。这是该模式的重要特性。 ## 介绍 **意图:** 用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。 **主要解决:** 对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。 **何时使用:** 多个类相互耦合,形成了网状结构。 **如何解决:** 将上述网状结构分离为星型结构。 **关键代码:** 对象 Colleague 之间的通信封装到一个类中单独处理。 **应用实例:** 1. 中国加入 WTO 之前是各个国家相互贸易,结构复杂,现在是各个国家通过 WTO 来互相贸易。 2. 机场调度系统。 3. MVC 框架,其中 C(控制器)就是 M(模型)和 V(视图)的中介者。 **优点:** 1. 降低了类的复杂度,将一对多转化成了一对一。 2. 各个类之间的解耦。 3. 符合迪米特原则。 **缺点:** 中介者会庞大,变得复杂难以维护。 **使用场景:** 1. 系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。 2. 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。 **注意事项:** 不应当在职责混乱的时候使用。 ## 结构 ![中介者设计模式的结构](/media/202203/2022-03-12_2044590.02727518166903553.png) - 组件 (Component) 是各种包含业务逻辑的类。 每个组件都有一个指向中介者的引用, 该引用被声明为中介者接口类型。 组件不知道中介者实际所属的类, 因此你可通过将其连接到不同的中介者以使其能在其他程序中复用。 - 中介者 (Mediator) 接口声明了与组件交流的方法, 但通常仅包括一个通知方法。 组件可将任意上下文 (包括自己的对象) 作为该方法的参数, 只有这样接收组件和发送者类之间才不会耦合。 - 具体中介者 (Concrete Mediator) 封装了多种组件间的关系。 具体中介者通常会保存所有组件的引用并对其进行管理, 甚至有时会对其生命周期进行管理。 - 组件并不知道其他组件的情况。 如果组件内发生了重要事件, 它只能通知中介者。 中介者收到通知后能轻易地确定发送者, 这或许已足以判断接下来需要触发的组件了。 对于组件来说, 中介者看上去完全就是一个黑箱。 发送者不知道最终会由谁来处理自己的请求, 接收者也不知道最初是谁发出了请求。 ### 实例 我们通过聊天室实例来演示中介者模式。实例中,多个用户可以向聊天室发送消息,聊天室向所有的用户显示消息。我们将创建两个类 ChatRoom 和 User。User 对象使用 ChatRoom 方法来分享他们的消息。 MediatorPatternDemo,我们的演示类使用 User 对象来显示他们之间的通信。 ![中介者模式的 UML 图](/media/202203/2022-03-12_2237180.17882215194799367.png) ## 伪代码 在本例中, 中介者模式可帮助你减少各种 UI 类 (按钮、 复选框和文本标签) 之间的相互依赖关系。 ![中介者模式示例的结构](/media/202203/2022-03-12_2238000.47142986094970807.png) 用户触发的元素不会直接与其他元素交流, 即使看上去它们应该这样做。 相反, 元素只需让中介者知晓事件即可, 并能在发出通知时同时传递任何上下文信息。 本例中的中介者是整个认证对话框。 对话框知道具体元素应如何进行合作并促进它们的间接交流。 当接收到事件通知后, 对话框会确定负责处理事件的元素并据此重定向请求。 ``` // 中介者接口声明了一个能让组件将各种事件通知给中介者的方法。中介者可对这 // 些事件做出响应并将执行工作传递给其他组件。 interface Mediator is method notify(sender: Component, event: string) // 具体中介者类可解开各组件之间相互交叉的连接关系并将其转移到中介者中。 class AuthenticationDialog implements Mediator is private field title: string private field loginOrRegisterChkBx: Checkbox private field loginUsername, loginPassword: Textbox private field registrationUsername, registrationPassword, registrationEmail: Textbox private field okBtn, cancelBtn: Button constructor AuthenticationDialog() is // 创建所有组件对象并将当前中介者传递给其构造函数以建立连接。 // 当组件中有事件发生时,它会通知中介者。中介者接收到通知后可自行处理, // 也可将请求传递给另一个组件。 method notify(sender, event) is if (sender == loginOrRegisterChkBx and event == "check") if (loginOrRegisterChkBx.checked) title = "登录" // 1. 显示登录表单组件。 // 2. 隐藏注册表单组件。 else title = "注册" // 1. 显示注册表单组件。 // 2. 隐藏登录表单组件。 if (sender == okBtn && event == "click") if (loginOrRegister.checked) // 尝试找到使用登录信息的用户。 if (!found) // 在登录字段上方显示错误信息。 else // 1. 使用注册字段中的数据创建用户账号。 // 2. 完成用户登录工作。 … // 组件会使用中介者接口与中介者进行交互。因此只需将它们与不同的中介者连接 // 起来,你就能在其他情境中使用这些组件了。 class Component is field dialog: Mediator constructor Component(dialog) is this.dialog = dialog method click() is dialog.notify(this, "click") method keypress() is dialog.notify(this, "keypress") // 具体组件之间无法进行交流。它们只有一个交流渠道,那就是向中介者发送通知。 class Button extends Component is // ... class Textbox extends Component is // ... class Checkbox extends Component is method check() is dialog.notify(this, "check") // ... ``` ## 应用场景 - 当一些对象和其他对象紧密耦合以致难以对其进行修改时, 可使用中介者模式。 该模式让你将对象间的所有关系抽取成为一个单独的类, 以使对于特定组件的修改工作独立于其他组件。 - 当组件因过于依赖其他组件而无法在不同应用中复用时, 可使用中介者模式。 应用中介者模式后, 每个组件不再知晓其他组件的情况。 尽管这些组件无法直接交流, 但它们仍可通过中介者对象进行间接交流。 如果你希望在不同应用中复用一个组件, 则需要为其提供一个新的中介者类。 - 如果为了能在不同情景下复用一些基本行为, 导致你需要被迫创建大量组件子类时, 可使用中介者模式。 由于所有组件间关系都被包含在中介者中, 因此你无需修改组件就能方便地新建中介者类以定义新的组件合作方式。 ## 实现方式 1. 找到一组当前紧密耦合, 且提供其独立性能带来更大好处的类 (例如更易于维护或更方便复用)。 2. 声明中介者接口并描述中介者和各种组件之间所需的交流接口。 在绝大多数情况下, 一个接收组件通知的方法就足够了。 如果你希望在不同情景下复用组件类, 那么该接口将非常重要。 只要组件使用通用接口与其中介者合作, 你就能将该组件与不同实现中的中介者进行连接。 3. 实现具体中介者类。 该类可从自行保存其下所有组件的引用中受益。 4. 你可以更进一步, 让中介者负责组件对象的创建和销毁。 此后, 中介者可能会与工厂或外观类似。 5. 组件必须保存对于中介者对象的引用。 该连接通常在组件的构造函数中建立, 该函数会将中介者对象作为参数传递。 6. 修改组件代码, 使其可调用中介者的通知方法, 而非其他组件的方法。 然后将调用其他组件的代码抽取到中介者类中, 并在中介者接收到该组件通知时执行这些代码。 ## 优点 - 单一职责原则。 你可以将多个组件间的交流抽取到同一位置, 使其更易于理解和维护。 - 开闭原则。 你无需修改实际组件就能增加新的中介者。 - 你可以减轻应用中多个组件间的耦合情况。 - 你可以更方便地复用各个组件。 ## 缺点 - 一段时间后, 中介者可能会演化成为上帝对象。 ## 与其他模式的关系 - 责任链模式、 命令模式、 中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式: - 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理 - 命令在发送者和请求者之间建立单向连接。 - 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。 - 观察者允许接收者动态地订阅或取消接收请求。 - 外观模式和中介者的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。 - 外观为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到外观的存在。 子系统中的对象可以直接进行交流。 - 中介者将系统中组件的沟通行为中心化。 各组件只知道中介者对象, 无法直接相互交流。 - 中介者和观察者之间的区别往往很难记住。 在大部分情况下, 你可以使用其中一种模式, 而有时可以同时使用。 让我们来看看如何做到这一点。 中介者的主要目标是消除一系列系统组件之间的相互依赖。 这些组件将依赖于同一个中介者对象。 观察者的目标是在对象之间建立动态的单向连接, 使得部分对象可作为其他对象的附属发挥作用。 有一种流行的中介者模式实现方式依赖于观察者。 中介者对象担当发布者的角色, 其他组件则作为订阅者, 可以订阅中介者的事件或取消订阅。 当中介者以这种方式实现时, 它可能看上去与观察者非常相似。 当你感到疑惑时, 记住可以采用其他方式来实现中介者。 例如, 你可永久性地将所有组件链接到同一个中介者对象。 这种实现方式和观察者并不相同, 但这仍是一种中介者模式。 假设有一个程序, 其所有的组件都变成了发布者, 它们之间可以相互建立动态连接。 这样程序中就没有中心化的中介者对象, 而只有一些分布式的观察者。 ## 示例代码 ### TS ```ts /** * The Mediator interface declares a method used by components to notify the * mediator about various events. The Mediator may react to these events and * pass the execution to other components. */ interface Mediator { notify(sender: object, event: string): void; } /** * Concrete Mediators implement cooperative behavior by coordinating several * components. */ class ConcreteMediator implements Mediator { private component1: Component1; private component2: Component2; constructor(c1: Component1, c2: Component2) { this.component1 = c1; this.component1.setMediator(this); this.component2 = c2; this.component2.setMediator(this); } public notify(sender: object, event: string): void { if (event === 'A') { console.log('Mediator reacts on A and triggers following operations:'); this.component2.doC(); } if (event === 'D') { console.log('Mediator reacts on D and triggers following operations:'); this.component1.doB(); this.component2.doC(); } } } /** * The Base Component provides the basic functionality of storing a mediator's * instance inside component objects. */ class BaseComponent { protected mediator: Mediator; constructor(mediator: Mediator = null) { this.mediator = mediator; } public setMediator(mediator: Mediator): void { this.mediator = mediator; } } /** * Concrete Components implement various functionality. They don't depend on * other components. They also don't depend on any concrete mediator classes. */ class Component1 extends BaseComponent { public doA(): void { console.log('Component 1 does A.'); this.mediator.notify(this, 'A'); } public doB(): void { console.log('Component 1 does B.'); this.mediator.notify(this, 'B'); } } class Component2 extends BaseComponent { public doC(): void { console.log('Component 2 does C.'); this.mediator.notify(this, 'C'); } public doD(): void { console.log('Component 2 does D.'); this.mediator.notify(this, 'D'); } } /** * The client code. */ const c1 = new Component1(); const c2 = new Component2(); const mediator = new ConcreteMediator(c1, c2); console.log('Client triggers operation A.'); c1.doA(); console.log(''); console.log('Client triggers operation D.'); c2.doD(); ``` 输出: ```txt Client triggers operation A. Component 1 does A. Mediator reacts on A and triggers following operations: Component 2 does C. Client triggers operation D. Component 2 does D. Mediator reacts on D and triggers following operations: Component 1 does B. Component 2 does C. ```
追风者
2022年3月29日 20:02
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
关于 MrDoc
觅思文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅思文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅思文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
Markdown文件
分享
链接
类型
密码
更新密码