In software engineering, Behavioural Design Patterns deal with the assignment of responsibilities between objects & encapsulating behaviour in an object to delegate requests. In this article of the Behavioural Design Patterns, we’re going to take a look at Mediator Design Pattern in Modern C++. And the motivation behind the Mediator Design Pattern is to provide proper communication between components by letting the components be aware(or unaware also, depending upon use case) of each other’s presence or absence in the system.

By the way, If you haven’t check out my other articles on Behavioural Design Patterns, then here is the list:

  1. Chain of responsibility
  2. Command
  3. Interpreter
  4. Iterator
  5. Mediator
  6. Memento
  7. Observer
  8. State
  9. Strategy
  10. Template Method
  11. Visitor

The code snippets you see throughout this series of articles are simplified not sophisticated. So you often see me not using keywords like override, final, public(while inheritance) just to make code compact & consumable(most of the time) in single standard screen size. I also prefer struct instead of class just to save line by not writing “public:” sometimes and also miss virtual destructor, constructor, copy constructor, prefix std::, deleting dynamic memory, intentionally. I also consider myself a pragmatic person who wants to convey an idea in the simplest way possible rather than the standard way or using Jargons.

Note:

  • If you stumbled here directly, then I would suggest you go through What is design pattern? first, even if it is trivial. I believe it will encourage you to explore more on this topic.
  • All of this code you encounter in this series of articles are compiled using C++20(though I have used Modern C++ features up to C++17 in most cases). So if you don’t have access to the latest compiler you can use https://wandbox.org/ which has preinstalled boost library as well.

Intent

To facilitates communication between objects.

  • Mediator implements functionality that dictates `how a set of objects interact with each other`. It also promotes loose coupling by keeping objects from referring to each other explicitly. And lets you vary their interaction independently.

Mediator Design Pattern Example in C++

  • The classic & most suitable example of Mediator Design Pattern would be a chat room where your components(most likely people) may go in and out of the system at any time.
  • Therefore, it makes no sense for the different participants to have direct references to one another because those references can go dead at any time.
  • So the solution here is to have all of the components refer to some sort of central component which facilitates the communication and that component happens to be the mediator.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
struct ChatRoom {
    virtual void broadcast(string from, string msg) = 0;
    virtual void message(string from, string to, string msg) = 0;
};

struct Person {
    string              m_name;
    ChatRoom*           m_room{nullptr};
    vector<string>      m_chat_log;

    Person(string n) : m_name(n) {}

    void say(string msg) const { m_room->broadcast(m_name, msg); }
    void pm(string to, string msg) const { m_room->message(m_name, to, msg); }
    void receive(string from, string msg) {
        string s{from + ": \"" + msg + "\""};
        cout << "[" << m_name << "'s chat session]" << s << "\n";
        m_chat_log.emplace_back(s);
    }
};

struct GoogleChat : ChatRoom
{
    vector<Person*>     m_people;

    void broadcast(string from, string msg) {
        for (auto p : m_people)
            if (p->m_name != from)
                p->receive(from, msg);
    }

    void join(Person *p) {
        string join_msg = p->m_name + " joins the chat";
        broadcast("room", join_msg);
        p->m_room = this;
        m_people.push_back(p);
    }

    void message(string from, string to, string msg) {
        auto target = find_if(begin(m_people), end(m_people),
        [&](const Person *p) {
            return p->m_name == to;
        });

        if (target != end(m_people)) (*target)->receive(from, msg);
    }
};

int main() {
    GoogleChat 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.join(&simon);
    simon.say("hi everyone!");

    jane.pm("Simon", "glad you found us, simon!");

    return EXIT_SUCCESS;
}
/*  
[John's chat session]room: "Jane joins the chat"
[Jane's chat session]John: "hi room"
[John's chat session]Jane: "oh, hey john"
[John's chat session]room: "Simon joins the chat"
[Jane'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 found us, simon!"
*/
  • So the takeaway from the above example is that you have a central component. In this case, it’s the GoogleChat and every person of the chatroom has a reference or pointer to that GoogleChat. Thus, they all communicate exclusively through that point or so they don’t communicate directly.
  • They don’t have any references or pointers to one another but still, they can send messages for example in this case I’m using the name of a person which has the kind of key for actually message passing and the chat room is the mediator who actually takes care of the glue. The thing which kind of binds everything together.

Benefits of Mediator Design Pattern

  1. You can replace any component in the system without affecting other component & system.
  2. Mediator Design Pattern reduces the complexity of communication between the different components in a system. Thus promoting loose coupling & less number of subclasses.
  3. As to overcome the limitation of the Observer Design Pattern which works in a one-to-many relationship, Mediator Design Pattern can be employed for a many-to-many relationship.

Summary by FAQs

Mediator vs Facade Design Pattern?

Mediator pattern can be seen as a multiplexed facade pattern. In mediator, instead of working with an interface of a single object, you are making a multiplexed interface among multiple objects to provide smooth transitions.

Mediator vs Observer Design Pattern?

  • Observer Design Pattern = one-to-many relationship
  • Mediator Design Pattern = many-to-many relationship
    Due to centralized control of communication, maintenance of the system designed using Mediator Design Pattern is easy.

Senders & Receivers Patterns

Chain of Responsibility, Command, Mediator, and Observer, address how you can decouple senders and receivers, but with different trade-offs. Chain of Responsibility passes a sender request along a chain of potential receivers. Command normally specifies a sender-receiver connection with a subclass. Mediator has senders and receivers reference each other indirectly. Observer defines a very decoupled interface that allows for multiple receivers to be configured at run-time.