中介者模式
(Mediator)
中介者 是一种行为设计模式,能让你减少对象之间混乱无序的依赖关系。该模式会限 制对象之间的直接交互,迫使它们通过一个中介者对象进行合作。
1. 问题
假如你有一个创建和修改客户资料的对话框,它由各种控件组成,例如文本框(TextField)、复选框(Checkbox)和按钮(Button)等。
用户界面中各元素间的关系会随程序发展而变得混乱。
某些表单元素可能会直接进行互动。例如,选中“我有一只狗”复选框后可能会显示一个隐藏文本框用于输入狗狗的名字。另一个例子是提交按钮必须在保存数据前校验所有输入内容。
元素间存在许多关联。因此,对某些元素进行修改可能会影响其他元素。
如果直接在表单元素代码中实现业务逻辑,你将很难在程序其他表单中复用这些元素类。例如,由于复选框类与狗狗的文本框相耦合,所以将无法在其他表单中使用它。你要么使用渲染资料表单时用到的所有类,要么一个都不用。
2. 解决方案
中介者模式建议你停止组件之间的直接交流并使其相互独立。这些组件必须调用特殊的中介者对象,通过中介者对象重定向调用行为,以间接的方式进行合作。最终,组件仅依赖于一个中介者类,无需与多个其他组件相耦合。
在资料编辑表单的例子中,对话框(Dialog)类本身将作为中介者,其很可能已知自己所有的子元素,因此你甚至无需在该类中引入新的依赖关系。
UI 元素必须通过中介者对象进行间接沟通。
绝大部分重要的修改都在实际表单元素中进行。让我们想想提交按钮。之前,当用户点击按钮后,它必须对所有表单元素数值进行校验。而现在它的唯一工作是将点击事件通知给对话框。收到通知后,对话框可以自行校验数值或将任务委派给各元素。这样一来,按钮不再与多个表单元素相关联,而仅依赖于对话框类。
你还可以为所有类型的对话框抽取通用接口,进一步削弱其依赖性。接口中将声明一个所有表单元素都能使用的通知方法,可用于将元素中发生的事件通知给对话框。这样一来,所有实现了该接口的对话框都能使用这个提交按钮了。
采用这种方式,中介者模式让你能在单个中介者对象中封装多个对象间的复杂关系网。类所拥有的依赖关系越少,就越易于修改、扩展或复用。
3. 结构
中介者模式的结构
其中:
*Mediator(中介者) 定义一个接口用于各同事(Colleague) 对象通信。
*ConcreteMediator(具体中介者) 通过协调各同事对象实现协作行为;了解并维护它的各个同事。
*Colleague class(同事类) 知道它的中介者对象;每一个同事类对象在需要与其他同事通信的时候与它的中介者通信。
4. 实现方式
1. 找到一组当前紧密耦合,且提供其独立性能带来更大好处的类。
例如更易于维护或更方便复用。
2. 声明中介者接口并描述中介者和各种组件之间所需的交流接口。
在绝大多数情况下,一个接收组件通知的方法就足够了。如果你希望在不同情景下复用组件类,那么该接口将非常重要。只要组件使用通用接口与其中介者合作,你就能将该组件与不同实现中的中介者进行连接。
3. 实现具体中介者类。
该类可从自行保存其下所有组件的引用中受益。
4. 你可以更进一步,让中介者负责组件对象的创建和销毁。
此后,中介者可能会与工厂或外观类似。
5. 组件必须保存对于中介者对象的引用。
该连接通常在组件的构造函数中建立,该函数会将中介者对象作为参数传递。
6. 修改组件代码,使其可调用中介者的通知方法,而非其他组件的方法。
然后将调用其他组件的代码抽取到中介者类中,并在中介者接收到该组件通知时执行这些代码。
5. 代码示例
mediator.h
#ifndef DESIGN_PATTERNS_MEDIATOR_H
#define DESIGN_PATTERNS_MEDIATOR_H
#include <iostream>
#include <string>
using namespace std;
//------------------------------//
class Country;//"国家"类
class UnitedNations //"联合国"类
{
public:
virtual void Declare(string, Country*) = 0;//声明
};
class UnitedNationsSecurityCouncil : public UnitedNations //"联合国安全理事会"
{
public:
UnitedNationsSecurityCouncil() {}
void SetUsa(Country*);//美国
void SetIraq(Country*);//伊拉克
void Declare(string, Country*);
private:
Country *usa_;
Country *iraq_;
};
//------------------------------//
class Country
{
public:
Country() {}
Country(UnitedNations*);
virtual void Declare(string) = 0;
virtual void GetMessage(string) = 0;
protected:
UnitedNations *mediator_;//调解员
};
class Usa : public Country
{
public:
Usa(UnitedNations*);
void Declare(string);
void GetMessage(string);
};
class Iraq : public Country
{
public:
Iraq(UnitedNations*);
void Declare(string);
void GetMessage(string);
};
//------------------------------//
#endif //DESIGN_PATTERNS_MEDIATOR_H
mediator.c
#include "mediator.h"
//------------------------------//
void UnitedNationsSecurityCouncil::SetUsa(Country *usa)
{
usa_ = usa;
}
void UnitedNationsSecurityCouncil::SetIraq(Country *iraq)
{
iraq_ = iraq;
}
void UnitedNationsSecurityCouncil::Declare(string message, Country *country)
{
if (country == iraq_)
{
iraq_->GetMessage(message);
}
else if (country == usa_)
{
usa_->GetMessage(message);
}
}
//------------------------------//
Country::Country(UnitedNations *mediator) : mediator_(mediator) {}
//------------------------------//
Usa::Usa(UnitedNations *mediator) : Country(mediator) {}
void Usa::Declare(string message)
{
mediator_->Declare(message, this);
}
void Usa::GetMessage(string message)
{
cout << "USA gets: \"" << message << "\"" << endl;
}
//------------------------------//
Iraq::Iraq(UnitedNations *mediator) : Country(mediator) {}
void Iraq::Declare(string message)
{
mediator_->Declare(message, this);
}
void Iraq::GetMessage(string message)
{
cout << "Iraq gets: \"" << message << "\"" << endl;
}
//------------------------------//
Main.c
//------------------------------//
#include <iostream>
#include "mediator.h"
using namespace std;
//------------------------------//
// Created by Cls on 2024/04/01.
//------------------------------//
int main(int argc, char *argv[])
{
UnitedNationsSecurityCouncil *unsc_;
Country *usa_;
Country *iraq_;
//-----------------//
unsc_ = new UnitedNationsSecurityCouncil();
usa_ = new Usa(unsc_);
iraq_ = new Iraq(unsc_);
unsc_->SetUsa(usa_);
unsc_->SetIraq(iraq_);
usa_->Declare("停止核武器研究");
iraq_->Declare("我们没有核工业");
//-----------------//
cout << "------------------" << endl;
//-----------------//
delete unsc_;
delete usa_;
delete iraq_;
//-----------------//
return 0;
}
//------------------------------//
打印输出
6. 应用场景
当一些对象和其他对象紧密耦合以致难以对其进行修改时,可使用中介者模式。
该模式让你将对象间的所有关系抽取成为一个单独的类,以使对于特定组件的修改工作独立于其他组件。
当组件因过于依赖其他组件而无法在不同应用中复用时,可使用中介者模式。
应用中介者模式后,每个组件不再知晓其他组件的情况。尽管这些组件无法直接交流,但它们仍可通过中介者对象进行间接交流。如果你希望在不同应用中复用一个组件,则需要为其提供一个新的中介者类。
如果为了能在不同情景下复用一些基本行为,导致你需要被迫创建大量组件子类时,可使用中介者模式。
由于所有组件间关系都被包含在中介者中,因此你无需修改组件就能方便地新建中介者类以定义新的组件合作方式。
7. 优缺点
√ 单一职责原则。你可以将多个组件间的交流抽取到同一位置,使其更易于理解和维护。
√ 开闭原则。你无需修改实际组件就能增加新的中介者。
√ 你可以减轻应用中多个组件间的耦合情况。
√ 你可以更方便地复用各个组件。
× 一段时间后,中介者可能会演化成为上帝对象。
8. 与其他模式的关系
• 责任链、命令、中介者和观察者用于处理请求发送者和接收者之间的不同连接方式:
◦ 责任链按照顺序将请求动态传递给一系列的潜在接收者,直至其中一名接收者对请求进行处理。
◦ 命令在发送者和请求者之间建立单向连接。
◦ 中介者清除了发送者和请求者之间的直接连接,强制它们通过一个中介对象进行间接沟通。
◦ 观察者允许接收者动态地订阅或取消接收请求。
• 外观和中介者的职责类似:它们都尝试在大量紧密耦合的类中组织起合作。
◦ 外观为子系统中的所有对象定义了一个简单接口,但是它不提供任何新功能。子系统本身不会意识到外观的存在。子系统中的对象可以直接进行交流。
◦ 中介者将系统中组件的沟通行为中心化。各组件只知道中介者对象,无法直接相互交流。
• 介者和观察者之间的区别往往很难记住。在大部分情况下,你可以使用其中一种模式,而有时可以同时使用。让我们来看看如何做到这一点。
中介者的主要目标是消除一系列系统组件之间的相互依赖。这些组件将依赖于同一个中介者对象。观察者的目标是在对象之间建立动态的单向连接,使得部分对象可作为其他对象的附属发挥作用。
有一种流行的中介者模式实现方式依赖于观察者。中介者对象担当发布者的角色,其他组件则作为订阅者,可以订阅中介者的事件或取消订阅。当中介者以这种方式实现时,它可能看上去与观察者非常相似。
当你感到疑惑时,记住可以采用其他方式来实现中介者。例如,你可永久性地将所有组件链接到同一个中介者对象。这种实现方式和观察者并不相同,但这仍是一种中介者模式。
假设有一个程序,其所有的组件都变成了发布者,它们之间可以相互建立动态连接。这样程序中就没有中心化的中介者对象,而只有一些分布式的观察者。