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.
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.
#ifndef MEDIATOR_H_
#define MEDIATOR_H_
#include <vector>
#include <algorithm>
namespace GoFPatterns
{
using namespace std;
class IColleague
{
protected:
IColleague()
{
}
};
template<typename EventArgType>
class ColleagueEvent;
template<typename EventArgType>
class Mediator
{
typedef vector<ColleagueEvent<EventArgType>*> EventList;
private:
EventList colleagues;
static Mediator<EventArgType>* instance;
static Mediator<EventArgType>& GetInstance()
{
if (!instance)
{
instance = new Mediator();
}
return *instance;
}
void RegisterCollegue(ColleagueEvent<EventArgType> *colEvent)
{
colleagues.push_back(colEvent);
}
void FireEvent(ColleagueEvent<EventArgType> *source, EventArgType eventArg)
{
for (unsigned int i = 0; i < colleagues.size(); i++)
{
if (colleagues[i] != source)
{
colleagues[i]->handlerProc(source->eventContext, eventArg,
colleagues[i]->eventContext);
}
}
}
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> ;
};
template<typename EventArgType>
class ColleagueEvent
{
typedef void (*ColleagueEventHandler)(IColleague *source,
EventArgType eventArg, IColleague* context);
IColleague * eventContext; ColleagueEventHandler handlerProc; public:
ColleagueEvent(IColleague *source, ColleagueEventHandler eventProc) :
eventContext(source), handlerProc(eventProc)
{
Mediator<EventArgType>::GetInstance().RegisterCollegue(this);
}
virtual ~ColleagueEvent()
{
Mediator<EventArgType>::GetInstance().UnregisterCollegue(this);
}
void FireEvent(EventArgType eventArg)
{
Mediator<EventArgType>::GetInstance().FireEvent(this, eventArg);
}
friend class Mediator<EventArgType> ;
};
template<typename EventArgType>
Mediator<EventArgType>* Mediator<EventArgType>::instance = 0;
} #endif /* MEDIATOR_H_ */
EXample usage:
#include <iostream>
#include "../GOFLib/Mediator.h"
namespace GoFExample
{
using namespace std;
class StaffMsg
{
public:
string msgName;
string msgData;
StaffMsg(string eName, string eData) :
msgName(eName), msgData(eData)
{
}
};
class AdminMsg
{
public:
string eventName;
string eventTime;
AdminMsg(string eName, string eTime) :
eventName(eName), eventTime(eTime)
{
}
};
class Employee: public GoFPatterns::IColleague
{
public:
string title; string name;
Employee(string eTitle, string eName) :
title(eTitle) , name(eName)
{
}
virtual ~Employee()
{
}
};
class GeneralStaff: public Employee
{
protected:
GoFPatterns::ColleagueEvent<StaffMsg> generalStaffEvent;
public:
GeneralStaff(string eTitle, string eName) :
Employee(eTitle, eName) , generalStaffEvent(this, OnColleagueEvent)
{
}
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;
}
};
class SalesMen: public GeneralStaff
{
public:
SalesMen(string eName) :
GeneralStaff("Sales Man", eName)
{
}
};
class Manager: public GeneralStaff
{
GoFPatterns::ColleagueEvent<AdminMsg> adminEvent;
public:
Manager(string eName) :
GeneralStaff("Manager", eName),
adminEvent(this, OnAdminEvent)
{
}
void BookMeetingRoom(string meetingRoomName)
{
generalStaffEvent.FireEvent(StaffMsg("Meeting Room Booking",
meetingRoomName));
}
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;
}
};
class SysAdmin: public GeneralStaff
{
public:
SysAdmin(string eName) :
GeneralStaff("Sys Admin", eName)
{
}
void AdviceForSoftwareUpdate(string swName)
{
generalStaffEvent.FireEvent(StaffMsg("Software Update Advice", swName));
}
};
class FinanceManager: public GeneralStaff
{
GoFPatterns::ColleagueEvent<AdminMsg> adminEvent;
public:
FinanceManager(string eName) :
GeneralStaff("Finance Manager", eName), adminEvent(this, OnAdminEvent)
{
}
void RequestForIncomeTaxDocument(string docName)
{
generalStaffEvent.FireEvent(StaffMsg("IT Doc Request", docName));
}
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;
}
};
class CEO: public Employee
{
GoFPatterns::ColleagueEvent<AdminMsg> adminEvent;
public:
CEO(string eName) :
Employee("CEO", eName), adminEvent(this, OnAdminEvent)
{
}
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;
}
void HoldAdministrationMeeting(string agenda, string time)
{
adminEvent.FireEvent(AdminMsg(agenda, time));
}
};
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;
}
}
Program output:
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.