Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Mediator Design Pattern Enhanced as a C++ Component

4.21/5 (9 votes)
11 Apr 2009CPOL3 min read 40.7K   272  
Mediator Design Pattern enhanced as a reusable C++ template class.

Introduction

This article introduces a reusable C++ template class that can be readily used in place of a Mediator Design Pattern implementation. It brings the following benefits:

  • It eliminates the repeated task of pattern's skeleton implementation.
  • Even novice developers unfamiliar with the Mediator pattern's structure can use it very easily.
  • Code re-usability in-turn results in reduced bugs.
  • Community based event broadcasting gives further flexibility to your design.
  • Your Colleague classes need not necessarily implement virtual functions.

[Concept courtesy: From Patterns to Components -Doctoral thesis by Karine Arnout]

Background

The Mediator Design Pattern is one among the 23 popular Design Patterns identified by the Gang of Four (GoF). The traditional implementation of the Observer pattern can be found here.

Image 1

Every time you implement the Mediator pattern as shown above, you will have to repeatedly make the Mediator, Colleague, and ConcreteMediator classes which are required only for the design infrastructure. Your real business logic will be lying in the ConcreteColleague1 and ConcreteColleague1 classes.

The template class introduced in this article helps you to focus on your Concrete Colleague classes rather than drafting the design infrastructure every time.

Furthermore, this template class gives you an added advantage, that is 'Community Based Event Subscription'. The idea behind this technique is that, in real world, the Colleagues communicate in communities based on some common interests. Let's take an online community for Photography as an example; this community has the following characteristics;

  • You can join the community by going to its website and registering yourself.
  • The community will have many members all broadcasting and receiving common Notification on Photography.
  • You don't necessarily need to know all members of the community.
  • You can send broadcast notifications to all the others in the community as well as receive notifications posted by other members.
  • At the same time as being a Photography member, you can join other communities like a 'Skiing' community.
  • Notifications from the Photography and Skiing communities will be delivered to you independently.
  • At any time, you can unsubscribe yourself from a community.

The traditional implementation of the Mediator pattern cannot provide all this flexibility; also, that is not reusable as code. The template class introduced here gives you all this flexibility by using a ready-made template class.

Using the code

The following code has two sections.

Section 1 is the implementation of the IColleague, Mediator<>, and ColleagueEvent<> classes. These three classes together constitute the reusable mediator library header file (attached with this article).

Section 2 is an example program to demonstrate the usage of the ColleagueEvent<> class in a typical Office system simulator.

C++
#ifndef MEDIATOR_H_
#define MEDIATOR_H_
#include <vector>
#include <algorithm>
namespace GoFPatterns
{
using namespace std;
/*
 * All colleagues are expected to be inherited from this class.
 * Even though it has no functionality now, it is left as a -
 * place holder for future improvements.
 */
class IColleague
{
protected:
    IColleague()
    {
    }
};
/*
 * Forward declaration of ColleagueEvent class
 */
template<typename EventArgType>
class ColleagueEvent;
/*
 * Mediator class is a singleton class and its only one instance of it will -
 * be created for each type of colleague collaboration.
 * An Instance of this class hold the reference to all ColleagueEvent objects -
 * which fire/ receive same Event Argument Type. Since this class is not -
 * meant to used public, it is exposed only to ColleagueEvent class as -
 * a friend.
 */
template<typename EventArgType>
class Mediator
{
    typedef vector<ColleagueEvent<EventArgType>*> EventList;
private:
    //List of ColleagueEvents in this community.
    EventList colleagues;

    static Mediator<EventArgType>* instance; //Singleton implementation
    /*
     * Access to the only one possible type specific object of this class.
     */
    static Mediator<EventArgType>& GetInstance()
    {
        if (!instance)
        {
            instance = new Mediator();
        }
        return *instance;
    }
    /*
     * Register a ColleagueEvent in the list of colleagues in this community.
     */
    void RegisterCollegue(ColleagueEvent<EventArgType> *colEvent)
    {
        colleagues.push_back(colEvent);
    }
    /*
     * When a ColleagueEvent is fired, notify all other Events in -
     * this community
     */
    void FireEvent(ColleagueEvent<EventArgType> *source, EventArgType eventArg)
    {
        for (unsigned int i = 0; i < colleagues.size(); i++)
        {
            //Notify all Events other than the one who fired the event.
            if (colleagues[i] != source)
            {
                colleagues[i]->handlerProc(source->eventContext, eventArg,
                        colleagues[i]->eventContext);
            }
        }
    }
    /*
     * Remove a ColleagueEvent from the list of colleagues in this community.
     */
    void UnregisterCollegue(ColleagueEvent<EventArgType> *colEvent)
    {
        typename EventList::iterator itr = find(colleagues.begin(),
                colleagues.end(), colEvent);
        if (itr != colleagues.end())
        {
            colleagues.erase(itr);
        }
    }
    friend class ColleagueEvent<EventArgType> ;
};

/*
 * The CollegueEvent template class is instantiated by means of -
 * an EventArgumentType.When an object is created, it registers itself with -
 * Mediator<> matching its EventArgAtype. Thus it becomes a member of the -
 * community of ColleagueEvents with same EventArgType.
 * When a ColleagueEvent object is fired, it is informed to the community -
 * Mediator<> and Mediator broadcast the event to all other CollegueEvents -
 * in that community.
 */
template<typename EventArgType>
class ColleagueEvent
{
    typedef void (*ColleagueEventHandler)(IColleague *source,
            EventArgType eventArg, IColleague* context);
    IColleague * eventContext; //Context colleague who fires the event
    ColleagueEventHandler handlerProc; //Event handler function pointer
public:
    /*
     * Constructor receives the event source context and the EventHandler
     * function pointer. Also register this object with the Mediator<> -
     * to join the community.
     */
    ColleagueEvent(IColleague *source, ColleagueEventHandler eventProc) :
        eventContext(source), handlerProc(eventProc)
    {
        //Register with mediator
        Mediator<EventArgType>::GetInstance().RegisterCollegue(this);
    }
    /*
     * Destructor - unregister the object from community.
     */
    virtual ~ColleagueEvent()
    {
        Mediator<EventArgType>::GetInstance().UnregisterCollegue(this);
    }
    /*
     * FireEvent - Inform the Mediator<> that the event object is triggered.
     * The Mediator<> then will broadcast this to other ColleagueEvents -
     * in this community.
     */
    void FireEvent(EventArgType eventArg)
    {
        Mediator<EventArgType>::GetInstance().FireEvent(this, eventArg);
    }
    friend class Mediator<EventArgType> ;
};
//Define the static member of Mediator<> class
template<typename EventArgType>
Mediator<EventArgType>* Mediator<EventArgType>::instance = 0;

} //End name space GoFPatterns
#endif /* MEDIATOR_H_ */

EXample usage:

C++
//==========EXAMPLE USAGE OF MEDIATOR PATTERN LIBRARY =======================

#include <iostream>
#include "../GOFLib/Mediator.h"
/*
 * This example illustrates an office scenario.
 * In this office, there are different kind of Employee s as
 * Salesmen, Managers, System Administrators, Financial Managers and CEO
 * Two communities are identified like general Staff and Administration staff
 * SalesMen, Managers, SysAdmins & Finance Managers
 * are joined in general staff community.
 * Managers, Finance Managers and CEO belong to Administration community.
 * These two communities, General Staff & Administration Staff are defined by two
 * Event Argument types such as StaffMsg (for General Staff )
 * and AdminMsg (for Administration Staff).
 * For ease of event handling, a class named GeneralStaff is there from which the
 * SalesMen, Managers, SysAdmins and FinanceMangers are inherited from.
 * Employee is the root class which is inherited from
 * IColleague which is a must for all Colleagues.
 */
namespace GoFExample
{
using namespace std;
/*
 * Staff message will be used as an EventArgument and
 * All General Staff are expected to subscribe / publish this kind of Events
 */
class StaffMsg
{
public:
    string msgName;
    string msgData;
    StaffMsg(string eName, string eData) :
        msgName(eName), msgData(eData)
    {
    }
};

/*
 * Admin message will be used as an EventArgument and
 * All Administration Staff are expected to subscribe / publish this kind of Events
 */
class AdminMsg
{
public:
    string eventName;
    string eventTime;
    AdminMsg(string eName, string eTime) :
        eventName(eName), eventTime(eTime)
    {
    }
};
/*
 * Base class for all employees in the company
 * It is extended from abstract class IColleague to become
 * eligible to subscribe colleague events
 */
class Employee: public GoFPatterns::IColleague
{
public:
    string title; //Designation of employee
    string name; // Employee name
    /*
     * Constructor to set Designation and name
     */
    Employee(string eTitle, string eName) :
        title(eTitle) // Set designation & name
                , name(eName)
    {
    }
    virtual ~Employee()
    {
    }

};
/*
 * General staff class handles the common events to which most -
 * of the employees are interested in.
 */
class GeneralStaff: public Employee
{
protected:
    //Declare a colleague event which represent General Staff events.
    GoFPatterns::ColleagueEvent<StaffMsg> generalStaffEvent;
    //Join the General Staff community
public:
    /*
     * Constructor for Initializing GeneralStaffEvent
     */
    GeneralStaff(string eTitle, string eName) :
        Employee(eTitle, eName) //Initialize title and name
                , generalStaffEvent(this, OnColleagueEvent)
                //Initialize GeneralStaff Colleague Event
    {
    }
    /*
     * Display details of the received event, its data,
     * its source and recipient context.
     */
    static void OnColleagueEvent(IColleague *source, StaffMsg data,
            IColleague* context)
    {
        Employee *srcCollegue = static_cast<Employee*> (source);
        Employee *ctxCollegue = static_cast<Employee*> (context);
        cout << endl << ctxCollegue->title 
                << " - " << ctxCollegue->name
                << " is notified by " 
                << srcCollegue->title << " - "
                << srcCollegue->name 
                << " of STAFF Event " << data.msgName
                << " with " << data.msgData;
    }
};

/*
 * Sales men is a general staff who receives all general staff notification
 * Now he does not raise any General Staff event.
 */
class SalesMen: public GeneralStaff
{
public:
    SalesMen(string eName) :
        GeneralStaff("Sales Man", eName)
    {
    }
};

/*
 * Manager is a General staff by primary design.
 * Also he has an additional subscription to the Administration community.
 * His general staff events are handled from the base class itself.
 * But the Administration events are handled in this class itself to show -
 * better fine tuned response.
 */
class Manager: public GeneralStaff
{
    //Join the administration community
    GoFPatterns::ColleagueEvent<AdminMsg> adminEvent;
public:
    Manager(string eName) :
        GeneralStaff("Manager", eName),
        adminEvent(this, OnAdminEvent)
        // Initialize adminEvent with Event Handler function name
    {
    }
    /*
     * Book a meeting room and notify all other General staff that
     * the meeting room is reserved by this Manager.
     */
    void BookMeetingRoom(string meetingRoomName)
    {
        //Fire a GeneralStaff Event that the meeting room is booked.
        generalStaffEvent.FireEvent(StaffMsg("Meeting Room Booking",
                meetingRoomName));
    }
    /*
     * Handle an administration event notification..
     * Now it just prints the event details to screen.
     */
    static void OnAdminEvent(IColleague *source, AdminMsg data,
            IColleague* context)
    {
        Employee *srcCollegue = static_cast<Employee*> (source);
        Employee *ctxCollegue = static_cast<Employee*> (context);
        cout << endl << "Manager - " 
                << ctxCollegue->name << " is notified by "
                << srcCollegue->title 
                << " - " << srcCollegue->name
                << " of Admin Event " 
                << data.eventName << " @ "
                << data.eventTime;
    }
};

/*
 * SysAdmin is a General staff. He receives all GenaralStaff events
 * Further more, he notifies all General staff when there is a
 * Software updation is required.
 */
class SysAdmin: public GeneralStaff
{
public:
    SysAdmin(string eName) :
        GeneralStaff("Sys Admin", eName)
    {
    }
    /*
     * Notify all General staff that the Software of each staff has to be updated.
     */
    void AdviceForSoftwareUpdate(string swName)
    {
        //Fire a GeneralStaff Event that the Software of each staff has to be updated.
        generalStaffEvent.FireEvent(StaffMsg("Software Update Advice", swName));
    }
};

/*
 * Finance Manager is a General staff by primary design.
 * Also he has an additional subscription to the Administration community.
 * His general staff events are handled from the base class itself.
 * But the Administration events are handled in this class itself to show -
 * better fine tuned response.
 */
class FinanceManager: public GeneralStaff
{
    //Join the administration community
    GoFPatterns::ColleagueEvent<AdminMsg> adminEvent;
public:
    FinanceManager(string eName) :
        GeneralStaff("Finance Manager", eName), adminEvent(this, OnAdminEvent)
    {
    }
    /*
     * Finance manager can raise an event to all General staff
     * to request for the Income tax document.
     */
    void RequestForIncomeTaxDocument(string docName)
    {
        generalStaffEvent.FireEvent(StaffMsg("IT Doc Request", docName));
    }
    /*
     * Handle an administration event notification..
     * Now it just prints the event details to screen.
     */
    static void OnAdminEvent(IColleague *source, AdminMsg data,
            IColleague* context)
    {
        Employee *srcCollegue = static_cast<Employee*> (source);
        Employee *ctxCollegue = static_cast<Employee*> (context);
        cout << endl << "Finance Manager - " 
                << ctxCollegue->name
                << " is notified by " 
                << srcCollegue->title << " - "
                << srcCollegue->name 
                << " of Admin Event " << data.eventName
                << " @ " << data.eventTime;
    }
};

/*
 * CEO - is not a General staff so he does not receive the
 * General staff events like, Meeting room Request ,  -
 * Software Update request, IncomeTaxDocumentRequest etc
 * CEO Has joined in the Administration Community where -
 * Managers and Finance Manager are members of.
 * Also CEO can raise an Administration Event to hold a -
 * Marketing strategy discussion.
 */
class CEO: public Employee
{
    //Join the administration community
    GoFPatterns::ColleagueEvent<AdminMsg> adminEvent;

public:
    CEO(string eName) :
        Employee("CEO", eName), adminEvent(this, OnAdminEvent)
    {
    }

    /*
     * Handle an administration event notification..
     * Now it just prints the event details to screen.
     */
    static void OnAdminEvent(IColleague *source, AdminMsg data,
            IColleague* context)
    {
        Employee *srcCollegue = static_cast<Employee*> (source);
        Employee *ctxCollegue = static_cast<Employee*> (context);
        cout << endl << "CEO- " 
                << ctxCollegue->name << " is notified by "
                << srcCollegue->title 
                << " - " << srcCollegue->name
                << " of Admin Event " 
                << data.eventName << " @ "
                << data.eventTime;
    }
    /*
     * Raise an Admin. Event to hold a Marketing Strategy discussion.
     */
    void HoldAdministrationMeeting(string agenda, string time)
    {
        // Fire and Admin Event to notify all other Administration community members.
        adminEvent.FireEvent(AdminMsg(agenda, time));
    }
};

/*
 * Program entry point; main() function
 */
int main()
{
    Manager mng1("Vivek"), mng2("Pradeep");
    SysAdmin sys1("Sony");
    SalesMen sl1("Biju"), sl2("Santhosh"), sl3("David");
    CEO ceo1("Ramesh");
    mng1.BookMeetingRoom("Zenith Hall");
    cout << endl
            << "===========================================================";
    FinanceManager fin1("Mahesh");
    sys1.AdviceForSoftwareUpdate("Win XP SP3");
    cout << endl
            << "===========================================================";
    fin1.RequestForIncomeTaxDocument("Form 12C");
    cout << endl
            << "===========================================================";
    ceo1.HoldAdministrationMeeting("European Marketing Plan",
            "Wednesday 4:00PM");
    cout << endl
            << "===========================================================";

    return 0;
}
} //End name space GoFExample

Program output:

Image 2

Points of interest

I tried to implement the above pattern like the Eiffel example shown in Karine's thesis document.

After I finished, I felt really unpleasant that it looked too imperfect. So I deleted all the code and restarted from scratch depending only on my imagination and thus reached the Community Event concept. I think it is better to follow your imagination than a pre-written document to find new ideas.

History

  • 11-April-2009 - Initial version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)