设计模式之中介者模式

发布于:2024-03-14 ⋅ 阅读:(127) ⋅ 点赞:(0)

设计模式专栏: http://t.csdnimg.cn/4Mt4u

目录

1.概述

2.结构

3.实现

4.使用

5.总结


1.概述

        我们编写的大部分代码都有不同的组件(类),它们通过直接引用或指针相互通信。但是在某些情况下,我们不希望对象知道彼此的存在。有时,也许我们确实希望它们相互了解,但不希望它们通过指针或引用进行通信,因为这些指针或引用有可能会失败,并且我们最不希望看到的就是对nullptr解引用。

        于是,中介者模式(Mediator Pattern)诞生了。中介者模式是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。

        中介者模式是一种使不同组件之间通信更加便利的机制。自然,参与其中的每个组件都可以访问中介者,这意味着它要么是一个全局静态变量,要么是一个注入没有组件中的引用。

2.结构

        中介者模式的UML类图如下所示:

角色定义:

抽象中介者(Mediator): 抽象中介者用于定义一个接口,接口用于各同事之间的通信。

具体中介者(ConcreteMediator): 具体中介者是抽象中介者的子类,通过协调各个同事对象来实现协作行为,了解并维护它对各个同事对象的引用。

抽象同事类(Colleague): 抽象同事类定义各同事的公有方法。

具体同事类(ConcreteColleague)具体同事类是抽象同事类的子类,每一个同事对象都引用一个中介者对象;每一个同事对象在需要和其他同事对象通信时,先与中介者通信,通过中介者来间接完成与其他同事类的通信。

3.实现

下面是一个简单的C++中介者模式示例:

// 中介者接口  
class Mediator {  
public:  
    virtual ~Mediator() {}  
    virtual void send(const std::string& message, Colleague* sender) = 0;  
};  
  
// 同事类接口  
class Colleague {  
protected:  
    Mediator* mediator;  
public:  
    Colleague(Mediator* mediator) : mediator(mediator) {}  
    virtual ~Colleague() {}  
    virtual void receive(const std::string& message) = 0;  
    void send(const std::string& message) {  
        mediator->send(message, this);  
    }  
};  
  
// 具体同事类A  
class ColleagueA : public Colleague {  
public:  
    ColleagueA(Mediator* mediator) : Colleague(mediator) {}  
    void receive(const std::string& message) override {  
        std::cout << "ColleagueA received: " << message << std::endl;  
    }  
};  
  
// 具体同事类B  
class ColleagueB : public Colleague {  
public:  
    ColleagueB(Mediator* mediator) : Colleague(mediator) {}  
    void receive(const std::string& message) override {  
        std::cout << "ColleagueB received: " << message << std::endl;  
    }  
};  
  
// 具体中介者类  
class ConcreteMediator : public Mediator {  
private:  
    ColleagueA* colleagueA;  
    ColleagueB* colleagueB;  
public:  
    ConcreteMediator() : colleagueA(nullptr), colleagueB(nullptr) {}  
    void setColleagueA(ColleagueA* a) { colleagueA = a; }  
    void setColleagueB(ColleagueB* b) { colleagueB = b; }  
    void send(const std::string& message, Colleague* sender) override {  
        if (sender == colleagueA) {  
            colleagueB->receive(message);  
        } else if (sender == colleagueB) {  
            colleagueA->receive(message);  
        }  
    }  
};  
  
int main() {  
    ConcreteMediator* mediator = new ConcreteMediator();  
    ColleagueA* colleagueA = new ColleagueA(mediator);  
    ColleagueB* colleagueB = new ColleagueB(mediator);  
  
    mediator->setColleagueA(colleagueA);  
    mediator->setColleagueB(colleagueB);  
  
    colleagueA->send("Hello from A!");  
    colleagueB->send("Hello from B!");  
  
    delete colleagueA;  
    delete colleagueB;  
    delete mediator;  
  
    return 0;  
}

        在这个示例中,我们定义了一个Mediator接口和一个Colleague接口,以及它们的具体实现类ConcreteMediatorColleagueAColleagueBColleagueAColleagueB通过send方法向中介者发送消息,而中介者则负责将消息传递给相应的同事类。这样,同事类之间不再直接相互通信,而是通过中介者进行间接通信。

4.使用

        以聊天室为例来讲解中介者模式的使用

        一般的网络聊天室就是中介者模式的经典示例,所以在讨论更复杂的示例之前,我们先实现一个聊天室。
        聊天室成员的最简单的实现可以定义为:

struct Person
{
    string name;
    ChatRoom* room(nullptr};
    vector<string> chat_log;

    Person(const string& name);

    void receive(const string& origin, const string& message);
    void say(const string& message)const;
    void pm(const string& who,const string& message)const;
};

        person对象拥有用户ID(name)、聊天日志(chat_log),以及指向其所在聊天室(ChatRoom)的指针。我们定义了1个构造函数和3个成员函数,3个成员函数分别是:
1.receive()函数用于接收消息。通常,这个函数会将接收的消息显示在用户屏幕上,并将消息添加到聊天日志中。请注意,不同的用户可以有不同的聊天日志。
2.say()函数允许用户向聊天室中的每个成员广播消息。
3.pm()函数是用于私聊的函数,使用时必须指定私聊对象的 name。
函数say()和pm()均是用于聊天室中的消息转发操作。说到这里。接下来我们来实现ChatRoom,它并不复杂:

struct ChatRoom
{
    vector<person*> people; // assume append-only
    void join(Person* p);
    void broadcast(const string& origin, const string& message);
    void message(const string& origin, const string& who, const string& message);
};

        具体是使用指针、引用,还是 shared_ptr 来存储聊天室中的成员,这最终由你自己决定:唯一的限制是 vector<>不能存储引用。在这里,我决定使用指针。ChatRoom 的 API十分简单:
1.join()函数用于将用户加人聊天室。我们暂不实现leave()函数。
2.broadcast()函数将消息发送给除本人以外的所有聊天成员。
3.message()函数用于在私聊时发送消息。
        函数join()的实现如下:

void ChatRoom::join(Person* p)
{
    string join_msg= p->name + " joins the chat";
    broadcast("room",join_msg);
    p->room = this;
    people.push_back(p);
}

        就像经典的IRC聊天室一样,我们会向聊天室里的每个成员广播有新成员加人的消息。本例中的 origin 指定为“房间”,而不是加入其中的人。然后,我们设置该成员的 room,并将其添加到房间中的 people 列表中。现在,我们来看 broadcast()成员函数。该函数负责向聊天室中的每个成员发送消息。请记住每个成员都通过自己的 person::receive()函数来处理消息,因此实现起来有些琐碎:

void ChatRoom::broadcast(const string& origin, const string& message)
{
    for (auto p : people){
        if (p->name != origin){
            p->receive(origin, message);
        )
    }
}

        是否要阻止广播信息被转发给我们自己、这是一个争论点,但是这里将主动避开它。不过、聊天室里的其他成员都能够收到消息。
        最后是私聊接口message()的实现:

void ChatRoom::message(const string& originconst string& who, const string& message)
{
    auto target = find_if(begin(people), end(people),[&](const person* p){ 
        return p->name == who; });
    if(target != end(people))
    {
        (*target)->receive(origin, message);
    }
}

        该函数在列表 people 中搜索收件人,如果找到收件人(收件人可能已经离开了房间),则将消息发送给此人。
        回到Person类的设计,Person类的接口say()和pm()的实现:

void Person::say(const string& message) const
{
    room->broadcast(name, message);
}

void Person::pm(const string& who, const string& message) const
{
    room->message(name, who, message);
}

        receive()函数负责将接收到的消息显示在屏幕上,并将其添加到聊天日志中。

void Person::receive(const string& origin, const string& message)
{
    string s{ origin + ": \"" + message + "\"" };
    cout << "[" << name << "'s chat session] " << s << "\n";
    chat_log.emplace_back(s);
}

        这个函数的功能更丰富,不仅可以显示消息来自哪个用户,还可以显示当前所在的聊天会话-这将有助于判断谁说了什么、什么时候说的。

        下面是我们将要经历的场景:

ChatRoom room;
Person john{ "john" };
person jane{ "jane" };
room.join(&john);
room.join(&jane);
john.say("hi room");
jane.say("oh, hey john");

person simon("simon");
room.jion(&simon);
simon.say("hi everyone!");

jane.pm("simon", "glad you could join us, simon”);

下面是这段程序的输出:
ijohn's chat session] room: "jane joins the chat"
[jane's chat session] john: "hi room"
[iohn's chat session] jane: "oh, hey john"
[ijohn's chat session] room: "simon joins the chat"
[iane's chat session] room: "simon joins the chat"
[john's chat session] simon: "hi everyone!"
[jane's chat session] simon: "hi everyone!"
[simon's chat session] jane: "glad you could join us, simon"

5.总结

优点

        1.简化对象交互:中介者模式通过引入中介者对象,将原本多个对象之间的复杂交互关系转化为中介者与对象之间的一对多交互,从而简化了对象之间的交互关系。这使得原本网状结构的交互关系变为星型结构,使得代码更易于理解和维护。

        2.降低耦合度:通过中介者模式,对象之间的直接依赖关系被转化为对中介者的依赖,从而降低了对象之间的耦合度。这使得对象更易于独立地改变和复用,符合面向对象设计的“开闭原则”。

        3.集中控制:中介者模式将多个对象之间的交互行为集中到一个中介者对象中,使得这些行为的控制和管理更加集中和方便。当需要改变交互行为时,只需修改中介者对象即可,无需修改多个对象。

缺点

        1.中介者膨胀:随着系统中对象数量的增加,中介者对象可能需要处理越来越多的交互请求,导致中介者对象变得庞大而复杂。这可能会使得系统的维护和扩展变得困难。

        2.维护复杂性:由于中介者对象集中了多个对象的交互行为,因此当交互逻辑发生变化时,可能需要修改中介者对象中的大量代码。这增加了系统的维护复杂性,特别是在大型系统中,可能导致维护成本的显著增加。

        3.性能开销:由于所有交互都需要通过中介者对象进行,因此在某些情况下,可能会引入额外的性能开销。特别是在高并发或实时性要求较高的系统中,这种开销可能会成为问题。

        综上所述,中介者模式在简化对象交互和降低耦合度方面具有显著优势,但在处理大量交互或需要高性能的场景中可能存在挑战。因此,在选择是否使用中介者模式时,需要根据具体的应用场景和需求进行权衡和考虑。


网站公告

今日签到

点亮在社区的每一天
去签到