C++ 设计模式——中介者模式

C++ 设计模式——中介者模式

中介者模式(Mediator Pattern)是一种行为型设计模式,它允许对象间的通信被封装到一个中介者对象中。该模式的主要目的是减少多个对象和类之间的通信复杂性,通过创建一个集中的控制点来管理相互间的依赖关系。这种模式特别适用于大型系统中,可以帮助减少组件之间的直接交互,从而使其更易于维护和扩展。

1. 主要组成成分

  1. 中介者(Mediator):定义一个接口用于与各同事(Colleague)对象通信。
  2. 具体中介者(Concrete Mediator):实现中介者的接口,并协调各同事对象之间的交互。
  3. 同事类(Colleague):与其他对象的通信是通过中介者进行的,每个同事类都知道它的中介者对象。
  4. 具体同事类(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 图解析

中介者模式的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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值