中介者设计模式增强为 C++ 组件
Mediator 设计模式增强版,作为可重用的 C++ 模板类。
引言
本文介绍了一个可重用的 C++ 模板类,它可以很容易地代替 Mediator 设计模式的实现。它带来了以下好处:
- 它消除了重复实现模式骨架的任务。
- 即使是不熟悉 Mediator 模式结构的新手开发者,也可以很容易地使用它。
- 代码重用反过来可以减少错误。
- 基于社区的事件广播为您的设计提供了更大的灵活性。
- 您的 Colleague 类不一定需要实现虚函数。
[概念致谢:从模式到组件 - Karine Arnout 的博士论文]
背景
Mediator 设计模式是四人帮 (GoF) 确定的 23 个流行的设计模式之一。传统的观察者模式的实现可以在这里找到。
每次您像上面那样实现 Mediator 模式时,您都必须重复创建 `Mediator`、`Colleague` 和 `ConcreteMediator` 类,而这些类只是设计基础设施所需要的。您的真正业务逻辑将位于 `ConcreteColleague1` 和 `ConcreteColleague1` 类中。
本文中介绍的模板类可以帮助您专注于您的 Concrete Colleague 类,而不是每次都起草设计基础设施。
此外,此模板类还为您提供了一个额外的优势,即**“基于社区的事件订阅”**。 这种技术背后的想法是,在现实世界中,同事们基于一些共同的兴趣在社区中进行交流。 让我们以一个在线 *摄影 * 社区为例; 这个社区有以下特点:
- 您可以通过访问社区网站并注册来**加入**该社区。
- 社区将有许多成员,他们都在广播和接收关于摄影的共同**通知**。
- 您不一定需要认识社区的所有成员。
- 您可以向社区中的所有其他人发送广播通知,也可以接收其他成员发布的通知。
- 在成为摄影成员的同时,您也可以加入其他社区,例如“滑雪”社区。
- 来自摄影和滑雪社区的通知将独立地传递给您。
- 您可以随时取消订阅社区。
Mediator 模式的传统实现无法提供所有这些灵活性; 而且,它不能作为代码重用。这里介绍的模板类通过使用现成的模板类为您提供所有这些灵活性。
使用代码
以下代码有两个部分。
第 1 部分是 `IColleague`、`Mediator<>` 和 `ColleagueEvent<>` 类的实现。 这三个类共同构成了可重用的 mediator 库头文件(附在本文中)。
第 2 部分是一个示例程序,演示了在典型的 Office 系统模拟器中使用 `ColleagueEvent<>` 类。
#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 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
程序输出
关注点
我试图像 Karine 的论文文档中显示的 Eiffel 示例一样实现上述模式。
完成之后,我感觉非常不愉快,因为它看起来太不完美了。所以我删除了所有代码,然后仅仅依靠我的想象力从头开始,从而形成了社区事件的概念。我认为遵循你的想象力比预先写好的文档更好,才能找到新的想法。
历史
- 2009 年 4 月 11 日 - 初始版本。