C++ 设计模式——中介者模式
C++ 设计模式——中介者模式
中介者模式(Mediator Pattern)是一种行为型设计模式,它允许对象间的通信被封装到一个中介者对象中。该模式的主要目的是减少多个对象和类之间的通信复杂性,通过创建一个集中的控制点来管理相互间的依赖关系。这种模式特别适用于大型系统中,可以帮助减少组件之间的直接交互,从而使其更易于维护和扩展。
1. 主要组成成分
- 中介者(Mediator):定义一个接口用于与各同事(Colleague)对象通信。
- 具体中介者(Concrete Mediator):实现中介者的接口,并协调各同事对象之间的交互。
- 同事类(Colleague):与其他对象的通信是通过中介者进行的,每个同事类都知道它的中介者对象。
- 具体同事类(Concrete Colleague):每个具体同事类都继承自同事类,它们实现自己的业务,在需要与其他同事通信时,利用中介者对象。
2. 逐步构建中介者模式
这个示例展示了如何使用中介者模式来管理一个游戏登录系统,其中包含“游客登录”和“账号登录”两种方式。中介者模式在这里发挥了重要作用,通过集中控制来协调各种 UI 控件的交互逻辑,从而简化了组件间的通信和依赖关系。下面是对每一步实现的概述:
步骤1: 创建中介者接口
首先,按照中介者设计模式的编写惯例,需要定义一个中介者的抽象基类 Mediator
。这个类包含了关键的成员函数 ctlChanged
,它允许 UI 控件在状态变化时与中介者进行通信。此外,为了使中介者能够有效地管理和与所有 UI 控件互动,中介者类还将包括一个 createCtrl
方法,用于创建和持有所有 UI 控件的引用。代码如下:
//类前向声明
class CtlParent;
//中介者父类
class Mediator
{
public:
virtual ~Mediator() {} //做父类时析构函数应该为虚函数
public:
virtual void createCtrl() = 0; //创建所有需要用到的UI控件
virtual void ctlChanged(CtlParent*) = 0; //当某个UI控件发生变化时调用中介者对象的该成员函数来通知中介者
};
步骤2: 创建同事类
接下来,定义 UI 控件的父类,称为 CtlParent
。这个类扮演的是中介者模式中的“同事”角色,其核心功能是与中介者对象进行通信。为此,每个 CtlParent
对象都会持有一个指向中介者的指针 m_pmediator
。此外,当控件的状态发生变化时,它会调用 Changed
方法,该方法负责通知中介者进行相应的处理。这是通过中介者模式实现不同组件间解耦的关键机制。代码如下:
//UI控件类的父类
class CtlParent
{
public:
CtlParent(Mediator* ptmpm, string caption) :m_pmediator(ptmpm), m_caption(caption) {} //构造函数
virtual ~CtlParent() {} //做父类时析构函数应该为虚函数
public:
//当UI控件发生变化时该成员函数会被调用
virtual void Changed()
{
m_pmediator->ctlChanged(this); //通知中介者对象,所有事情让中介者对象去做
}
//设置UI控件启用或禁用
virtual void Enable(bool sign) = 0;
protected:
Mediator* m_pmediator; //指向中介者对象的指针
string m_caption; //控件上面显示的文字内容,可能并不是所有控件都需要但这里为显示方便依旧引入
};
步骤3: 实现具体同事类
建立了 CtlParent
基类后,可以实现具体的同事类,如按钮、单选按钮和编辑框等。这些类继承自 CtlParent
并实现具体的行为。例如,按钮类可能需要响应点击事件,而编辑框可能需要处理文本输入。代码如下:
//普通按钮相关类
class Button : public CtlParent
{
public:
Button(Mediator* ptmpm, string caption) :CtlParent(ptmpm, caption) {} //构造函数
//设置按钮的启用或禁用
virtual void Enable(bool sign)
{
if (sign == true)
{
cout << "按钮“" << m_caption << "”被设置为了\"启用\"状态" << endl;
}
else
{
cout << "按钮“" << m_caption << "”被设置为了\"禁用\"状态" << endl;
}
//具体实现按钮启用或者禁用的代码略......
}
};
//单选按钮相关类
class RadioBtn : public CtlParent
{
public:
RadioBtn(Mediator* ptmpm, string caption) :CtlParent(ptmpm, caption) {} //构造函数
//设置单选按钮的启用或禁用
virtual void Enable(bool sign)
{
//用不到该功能,实现代码略......
}
//设置单选按钮为被选中或者被取消选中,被选中的单选按钮中间有个黑色实心圆点
void Selected(bool sign)
{
if (sign == true)
{
cout << "单选按钮“" << m_caption << "”被选中" << endl;
}
else
{
cout << "单选按钮“" << m_caption << "”被取消选中" << endl;
}
//具体实现单选按钮被选中或者被取消选中的代码略......
}
};
//编辑框相关类
class EditCtl : public CtlParent
{
public:
EditCtl(Mediator* ptmpm, string caption) :CtlParent(ptmpm, caption) {} //构造函数
//设置编辑框的启用或禁用
void Enable(bool sign)
{
if (sign == true)
{
cout << "编辑框“" << m_caption << "”被设置为了\"启用\"状态" << endl;
}
else
{
cout << "编辑框“" << m_caption << "”被设置为了\"禁用\"状态" << endl;
}
//具体实现编辑框启用或者禁用的代码略......
}
//是否编辑框中的内容为空
bool isContentEmpty()
{
return m_content.empty();
}
//其他成员函数略......
private:
string m_content = ""; //编辑框中的内容
};
步骤4: 实现具体中介者
具体中介者 concrMediator
继承自 Mediator
抽象类,并实现了其中的方法来管理和协调同事对象之间的交互。具体中介者持有所有同事类的引用,并根据某个同事类的状态变化来更新其他同事类的状态。代码如下:
//具体中介者类
class concrMediator : public Mediator
{
public:
~concrMediator() //析构函数
{
//释放资源
if (mp_login) { delete mp_login; mp_login = nullptr; }
if (mp_logout) { delete mp_logout; mp_logout = nullptr; }
if (mp_rbtn1) { delete mp_rbtn1; mp_rbtn1 = nullptr; }
if (mp_rbtn2) { delete mp_rbtn2; mp_rbtn2 = nullptr; }
if (mp_edtctl1) { delete mp_edtctl1; mp_edtctl1 = nullptr; }
if (mp_edtctl2) { delete mp_edtctl2; mp_edtctl2 = nullptr; }
}
//创建各种UI控件
virtual void createCtrl()
{
//当然,各种UI控件对象在外面创建,然后把地址传递进来也可以
mp_login = new Button(this, "登录");
mp_logout = new Button(this, "退出");
mp_rbtn1 = new RadioBtn(this, "游客登录");
mp_rbtn2 = new RadioBtn(this, "账号登录");
mp_edtctl1 = new EditCtl(this, "账号编辑框");
mp_edtctl2 = new EditCtl(this, "密码编辑框");
//设置一下缺省的UI控件状态
mp_rbtn1->Selected(true); //“游客登录”单选按钮设置为选中
mp_rbtn2->Selected(false); //“账号登录”单选按钮设置为取消选中
mp_edtctl1->Enable(false); //“账号”编辑框设置为禁用
mp_edtctl2->Enable(false); //“密码”编辑框设置为禁用
mp_login->Enable(true); //“登录”按钮设置为启用
mp_logout->Enable(true); //“退出”按钮设置为启用
//UI控件的位置设置等代码略......
}
//当某个UI控件发生变化时调用中介者对象的该成员函数来通知中介者
virtual void ctlChanged(CtlParent* p_ctrl)
{
if (p_ctrl == mp_login) //登录按钮被单击
{
cout << "开始游戏登录验证,根据验证结果决定是进入游戏之中还是验证失败给出提示!" << endl;
}
else if (p_ctrl == mp_logout) //退出按钮被单击
{
//退出整个游戏
cout << "游戏退出,再见!" << endl;
}
if (p_ctrl == mp_rbtn1) //游客登录单选按钮被单击
{
mp_rbtn1->Selected(true); //“游客登录”单选按钮设置为选中
mp_rbtn2->Selected(false); //“账号登录”单选按钮设置为取消选中
mp_edtctl1->Enable(false); //“账号”编辑框设置为禁用
mp_edtctl2->Enable(false); //“密码”编辑框设置为禁用
mp_login->Enable(true); //“登录”按钮设置为启用
}
else if (p_ctrl == mp_rbtn2) //账号登录单选按钮被单击
{
mp_rbtn1->Selected(false); //“游客登录”单选按钮设置为取消选中
mp_rbtn2->Selected(true); //“账号登录”单选按钮设置为选中
mp_edtctl1->Enable(true); //“账号”编辑框设置为启用
mp_edtctl2->Enable(true); //“密码”编辑框设置为启用
if (mp_edtctl1->isContentEmpty() || mp_edtctl2->isContentEmpty())
{
//如果“账号”编辑框或者“密码”编辑框有一个为空,则不允许登录
mp_login->Enable(false); //“登录”按钮设置为禁用
}
else
{
mp_login->Enable(true); //“登录”按钮设置为启用
}
}
if (p_ctrl == mp_edtctl1 || p_ctrl == mp_edtctl2) //账号或密码编辑框内容发生改变
{
if (mp_edtctl1->isContentEmpty() || mp_edtctl2->isContentEmpty())
{
//如果“账号”编辑框或者“密码”编辑框有一个为空,则不允许登录
mp_login->Enable(false); //“登录”按钮设置为禁用
}
else
{
mp_login->Enable(true); //“登录”按钮设置为启用
}
}
}
public: //为方便外界使用,这里以public修饰,实际项目中可以写一个成员函数来return这些指针
Button* mp_login = nullptr; //登录按钮
Button* mp_logout = nullptr; //退出按钮
RadioBtn* mp_rbtn1 = nullptr; //游客登录单选按钮
RadioBtn* mp_rbtn2 = nullptr; //账号登录单选按钮
EditCtl* mp_edtctl1 = nullptr; //账号编辑框
EditCtl* mp_edtctl2 = nullptr; //密码编辑框
};
步骤5: 客户端使用
在主函数中,创建中介者和同事对象,并通过模拟用户操作(如点击按钮、选择单选按钮等)来展示整个系统的交互流程。这样,所有的控件交互都通过中介者来协调,大大简化了控件之间的直接通信和依赖。
int main()
{
concrMediator mymedia;
mymedia.createCtrl();
cout << "-------------当\"账号登录\"单选按钮被按下时:-------------" << endl;
mymedia.mp_rbtn2->Changed(); //模拟“账号”按钮被按下,则去通知中介者,由中介者实现具体的逻辑处理
return 0;
}
3. 中介者模式 UML 图
UML 图解析
中介者模式的UML图包含以下四种角色:
- Mediator (抽象中介者类):
- 这个类定义了一系列抽象方法或接口,这些方法允许不同的同事对象与中介者进行通信。
Mediator
类提供了createCtrl()
和ctlChanged(CtlParent*)
方法,它们分别用于初始化控件和响应控件状态变化的需要。
- 这个类定义了一系列抽象方法或接口,这些方法允许不同的同事对象与中介者进行通信。
- ConcreteMediator (具体中介者类):
- 继承自
Mediator
类,并实现了其定义的抽象方法。ConcreteMediator
(即concrMediator
类)持有所有同事类对象的引用,并根据某个同事类的状态变化来协调其他同事类的行为。这样,具体中介者成为了控制各种UI控件交互的中心点。
- 继承自
- Colleague (抽象同事类):
- 定义了同事类共有的方法,例如
Changed()
,用于通知中介者对象状态的改变,以及抽象方法Enable(bool)
,需要在具体的同事类中实现。每个同事类对象都维持了一个指向中介者对象的指针 (m_pmediator
),使得它们可以将内部状态的变化通知给中介者。这个角色由CtlParent
类实现。
- 定义了同事类共有的方法,例如
- ConcreteColleague (具体同事类):
- 继承自
Colleague
类,并实现了父类中定义的抽象方法。具体的同事类,如Button
,RadioBtn
,EditCtl
等,处理具体的用户交互和状态管理。这些类通过调用Changed()
方法与中介者交互,从而避免了直接与其他同事类通信,降低了系统的耦合度。
- 继承自
4. 中介者模式的优点
- 降低了类的复杂度:通过将网络的连接从一个类转移到一个中介者,减少了类之间的直接通信。
- 将各类解耦:通过使各个类不必显式地相互引用来促进松散耦合。
- 简化了对象协议:由于中心控制交流,使得各对象间的交流变得更加简单。
- 提供了集中控制交换:通过中介者控制交流,可以更容易地更改交流逻辑。
5. 中介者模式的缺点
- 中介者可能变得过于复杂:中介者自身可能会变得复杂且难以维护,因为所有的通信都通过中介者处理。
- 过度使用可能导致设计变得复杂:如果过度使用中介者模式,可能导致设计比原来更加复杂。
6. 中介者模式适用场景
- 当一组对象以定义良好但复杂的方式进行通信时:中介者模式限制了直接的引用,这有助于防止组件间的依赖。
- 当重用一个对象是困难的,因为它与其他对象紧密耦合时:中介者模式帮助减少类之间的耦合。
- 当一个行为被多个类共享,但又不适合放在一个父类中时:中介者可以将这些行为放在一个单独的中介者类中。
完整代码
#include <iostream>
#include <map>
using namespace std;
//类前向声明
class CtlParent;
//中介者父类
class Mediator
{
public:
virtual ~Mediator() {} //做父类时析构函数应该为虚函数
public:
virtual void createCtrl() = 0; //创建所有需要用到的UI控件
virtual void ctlChanged(CtlParent*) = 0; //当某个UI控件发生变化时调用中介者对象的该成员函数来通知中介者
};
//UI控件类的父类
class CtlParent
{
public:
CtlParent(Mediator* ptmpm, string caption) :m_pmediator(ptmpm), m_caption(caption) {} //构造函数
virtual ~CtlParent() {} //做父类时析构函数应该为虚函数
public:
//当UI控件发生变化时该成员函数会被调用
virtual void Changed()
{
m_pmediator->ctlChanged(this); //通知中介者对象,所有事情让中介者对象去做
}
//设置UI控件启用或禁用
virtual void Enable(bool sign) = 0;
protected:
Mediator* m_pmediator; //指向中介者对象的指针
string m_caption; //控件上面显示的文字内容,可能并不是所有控件都需要但这里为显示方便依旧引入
};
//------------------------
//普通按钮相关类
class Button : public CtlParent
{
public:
Button(Mediator* ptmpm, string caption) :CtlParent(ptmpm, caption) {} //构造函数
//设置按钮的启用或禁用
virtual void Enable(bool sign)
{
if (sign == true)
{
cout << "按钮“" << m_caption << "”被设置为了\"启用\"状态" << endl;
}
else
{
cout << "按钮“" << m_caption << "”被设置为了\"禁用\"状态" << endl;
}
//具体实现按钮启用或者禁用的代码略......
}
};
//单选按钮相关类
class RadioBtn : public CtlParent
{
public:
RadioBtn(Mediator* ptmpm, string caption) :CtlParent(ptmpm, caption) {} //构造函数
//设置单选按钮的启用或禁用
virtual void Enable(bool sign)
{
//用不到该功能,实现代码略......
}
//设置单选按钮为被选中或者被取消选中,被选中的单选按钮中间有个黑色实心圆点
void Selected(bool sign)
{
if (sign == true)
{
cout << "单选按钮“" << m_caption << "”被选中" << endl;
}
else
{
cout << "单选按钮“" << m_caption << "”被取消选中" << endl;
}
//具体实现单选按钮被选中或者被取消选中的代码略......
}
};
//编辑框相关类
class EditCtl : public CtlParent
{
public:
EditCtl(Mediator* ptmpm, string caption) :CtlParent(ptmpm, caption) {} //构造函数
//设置编辑框的启用或禁用
void Enable(bool sign)
{
if (sign == true)
{
cout << "编辑框“" << m_caption << "”被设置为了\"启用\"状态" << endl;
}
else
{
cout << "编辑框“" << m_caption << "”被设置为了\"禁用\"状态" << endl;
}
//具体实现编辑框启用或者禁用的代码略......
}
//是否编辑框中的内容为空
bool isContentEmpty()
{
return m_content.empty();
}
//其他成员函数略......
private:
string m_content = ""; //编辑框中的内容
};
//--------------------------
//具体中介者类
class concrMediator : public Mediator
{
public:
~concrMediator() //析构函数
{
//释放资源
if (mp_login) { delete mp_login; mp_login = nullptr; }
if (mp_logout) { delete mp_logout; mp_logout = nullptr; }
if (mp_rbtn1) { delete mp_rbtn1; mp_rbtn1 = nullptr; }
if (mp_rbtn2) { delete mp_rbtn2; mp_rbtn2 = nullptr; }
if (mp_edtctl1) { delete mp_edtctl1; mp_edtctl1 = nullptr; }
if (mp_edtctl2) { delete mp_edtctl2; mp_edtctl2 = nullptr; }
}
//创建各种UI控件
virtual void createCtrl()
{
//当然,各种UI控件对象在外面创建,然后把地址传递进来也可以
mp_login = new Button(this, "登录");
mp_logout = new Button(this, "退出");
mp_rbtn1 = new RadioBtn(this, "游客登录");
mp_rbtn2 = new RadioBtn(this, "账号登录");
mp_edtctl1 = new EditCtl(this, "账号编辑框");
mp_edtctl2 = new EditCtl(this, "密码编辑框");
//设置一下缺省的UI控件状态
mp_rbtn1->Selected(true); //“游客登录”单选按钮设置为选中
mp_rbtn2->Selected(false); //“账号登录”单选按钮设置为取消选中
mp_edtctl1->Enable(false); //“账号”编辑框设置为禁用
mp_edtctl2->Enable(false); //“密码”编辑框设置为禁用
mp_login->Enable(true); //“登录”按钮设置为启用
mp_logout->Enable(true); //“退出”按钮设置为启用
//UI控件的位置设置等代码略......
}
//当某个UI控件发生变化时调用中介者对象的该成员函数来通知中介者
virtual void ctlChanged(CtlParent* p_ctrl)
{
if (p_ctrl == mp_login) //登录按钮被单击
{
cout << "开始游戏登录验证,根据验证结果决定是进入游戏之中还是验证失败给出提示!" << endl;
}
else if (p_ctrl == mp_logout) //退出按钮被单击
{
//退出整个游戏
cout << "游戏退出,再见!" << endl;
}
if (p_ctrl == mp_rbtn1) //游客登录单选按钮被单击
{
mp_rbtn1->Selected(true); //“游客登录”单选按钮设置为选中
mp_rbtn2->Selected(false); //“账号登录”单选按钮设置为取消选中
mp_edtctl1->Enable(false); //“账号”编辑框设置为禁用
mp_edtctl2->Enable(false); //“密码”编辑框设置为禁用
mp_login->Enable(true); //“登录”按钮设置为启用
}
else if (p_ctrl == mp_rbtn2) //账号登录单选按钮被单击
{
mp_rbtn1->Selected(false); //“游客登录”单选按钮设置为取消选中
mp_rbtn2->Selected(true); //“账号登录”单选按钮设置为选中
mp_edtctl1->Enable(true); //“账号”编辑框设置为启用
mp_edtctl2->Enable(true); //“密码”编辑框设置为启用
if (mp_edtctl1->isContentEmpty() || mp_edtctl2->isContentEmpty())
{
//如果“账号”编辑框或者“密码”编辑框有一个为空,则不允许登录
mp_login->Enable(false); //“登录”按钮设置为禁用
}
else
{
mp_login->Enable(true); //“登录”按钮设置为启用
}
}
if (p_ctrl == mp_edtctl1 || p_ctrl == mp_edtctl2) //账号或密码编辑框内容发生改变
{
if (mp_edtctl1->isContentEmpty() || mp_edtctl2->isContentEmpty())
{
//如果“账号”编辑框或者“密码”编辑框有一个为空,则不允许登录
mp_login->Enable(false); //“登录”按钮设置为禁用
}
else
{
mp_login->Enable(true); //“登录”按钮设置为启用
}
}
}
public: //为方便外界使用,这里以public修饰,实际项目中可以写一个成员函数来return这些指针
Button* mp_login = nullptr; //登录按钮
Button* mp_logout = nullptr; //退出按钮
RadioBtn* mp_rbtn1 = nullptr; //游客登录单选按钮
RadioBtn* mp_rbtn2 = nullptr; //账号登录单选按钮
EditCtl* mp_edtctl1 = nullptr; //账号编辑框
EditCtl* mp_edtctl2 = nullptr; //密码编辑框
};
int main()
{
concrMediator mymedia;
mymedia.createCtrl();
cout << "-------------当\"账号登录\"单选按钮被按下时:-------------" << endl;
mymedia.mp_rbtn2->Changed(); //模拟“账号”按钮被按下,则去通知中介者,由中介者实现具体的逻辑处理
return 0;
}