观察者模式作为 C++ 组件
观察者模式被制作成一个可重用的 C++ 组件
引言
本文介绍了一个可复用的 C++ 泛型组件,该组件可以代替观察者设计模式。它具有以下优点:
- 消除了模式骨架实现的重复任务。
- 即使是不熟悉观察者模式结构的初学者开发人员也可以轻松使用它。
- 代码可重用性反过来减少了错误。
- 无需从
abstract
观察者类继承观察者类。 - 事件订阅可以使用 C# 风格的现代语法完成。
[概念来源:来自模式到组件 - Karine Arnout 的博士论文]
背景
观察者设计模式是 Gang of Four (GoF) 确定的 23 种流行设计模式中使用最广泛的设计模式之一。传统观察者模式的实现可以在 这里 找到。

GoF 仅提供了该模式的设计概念。在 C++ 中,每次你都必须自己实现 Subject
和 Observer abstract
类。此外,这种设计迫使你的具体类从 AbstractSubject
和 AbstractObserver
类继承,以获得该模式的优势。
这里介绍的 C++ 组件更灵活,因为你无需进行一些继承约定即可使用观察者模式。它更像是将 Event
组件放在你的 Subject
类中,而你的 Observer
类可以简单地订阅你 Subject
类中的 Event
对象。
Using the Code
以下代码显示了 Event<>
类的模板定义以及 Event
模板的示例用法。你不能将 Event<>
组件用作二进制组件,例如 activeX。相反,你可以像使用 STL 类一样使用它。
使用此组件的主要限制是
- 你将不得不在你的应用程序中使用下面显示的头文件。
- 此组件使用函数指针来提供
Event
回调,并且相应的事件处理函数应该是一个static
函数或一个全局函数。 - 尽管订阅者对象指针可以作为上下文信息传递,但在
static
事件处理函数中,你必须执行static_cast<>(contextptr)
以获取指向订阅了该事件的观察者对象的指针。
#ifndef OBSERVER_EVENT_H_
#define OBSERVER_EVENT_H_
#include <vector>
#include <algorithm>
/*
* An Event component which encapsulates the Observer pattern.
* Observer pattern is one among popular 23 design patterns listed by Gang of Four (GoF).
* The Event class's object can be used in a subject class to represent
* an event of the subject.
* Make this object public so that subscribers can easily subscribe to this event
*/
namespace GoFPatterns
{
using namespace std;
template<typename SourceType, typename EventArgType>
class Event
{
protected:
/*
* Event handler function pointer definition
* source - Subject - the object which fired the event.
* eventArg - The event argument
* context - Context information, which a subscriber needs to get with an
* event notification
* Usually, this can be a pointer to the subscriber object itself.
*/
typedef void (*EventHandler)(SourceType *source, EventArgType eventArg,
void* context);
/*
* This inner class, for each EventHandler, stores the associated context information -
* pointer. This context pointer can be used to pass additional information
* from the point of subscription to the event handler function.
* The best use of context pointer is to use the "this" pointer of subscriber itself.
*/
class SubscriberRecord
{
private:
EventHandler handlerProc; // The event handler function pointer
void *handlerContext; // pointer to context information
SubscriberRecord(EventHandler handler, void* context = 0) :
handlerProc(handler), handlerContext(context)
{
}
friend class Event;
};
protected:
vector<SubscriberRecord> Subscribers;
SourceType *eventSource;
public:
/*
* Constructor - sets the Event source
*/
Event(SourceType *source) :
eventSource(source)
{
}
/*
* Virtual destructor - perform clean up if any.
*/
virtual ~Event()
{
}
/*
* Operator used to subscribe a handler C# style event subscription
*/
void operator +=(EventHandler handler)
{
Subscribers.push_back(SubscriberRecord(handler));
}
/*
* Operator used to unsubscribe a handler C# style event subscription
*/
void operator -=(EventHandler handler)
{
typename vector<SubscriberRecord>::iterator itr =
find(Subscribers.begin(),Subscribers.end(),handler);
if( itr != Subscribers.end())
{
Subscribers.erase(itr);
}
}
/*
* Function used to subscribe a handler with optional context information
*/
void Subscribe(EventHandler handler, void* context = 0)
{
Subscribers.push_back(SubscriberRecord(handler, context));
}
/*
* Function used to unsubscribe a handler with optional context information
*/
void Unsubscribe(EventHandler handler, void* context = 0)
{
typename vector<SubscriberRecord>::iterator itr =
find(Subscribers.begin(),Subscribers.end(),SubscriberRecord(handler, context));
if( itr != Subscribers.end())
{
Subscribers.erase(itr);
}
}
/*
* Fire the event and notify all observers with event argument, -
* source and context information if any provided.
*/
void FireEvent(EventArgType eventArg)
{
for (unsigned int i = 0; i < Subscribers.size(); i++)
{
Subscribers[i].handlerProc(eventSource, eventArg,
Subscribers[i].handlerContext);
}
}
};
}
#endif /* OBSERVER_EVENT_H_ */
//####################### EXAMPLE USAGE OF EVENT COMPONENT ######################
#include <iostream>
#include "../GOFLib/Event.h"
#include "GOFExample.h"
using namespace std;
namespace GoFExample
{
/* This example shows a Library lending system in a College
* The College has different Libraries for different disciplines
* When a Student is enrolled to a discipline, they are advised to join
* the respective Library.
* A Library contains a collection of Books.
* Whenever a new Book is added to the Library, it notifies all members -
* through an Event.
*
* Classes involved are
* Book - has an id and name
* Library - has a collection of Books and a newBookEvent
* Student - has an id and name, subscribes to a Library 's -
* newBookEvent when advised from college.
* College - has a collection of students for different disciplines -
* and a Library for each discipline
*/
/*
* A simple Book class
*/
class Book
{
private:
/*
* Private constructor to prevent making nameless books
*/
Book()
{
}
public:
int bookId;
string bookName;
Book(int id, string name) :
bookId(id), bookName(name)
{
}
};
/* A typical Library with a collection of books.
* This library will publish an event to all members whenever a new book is arrived
*/
class Library
{
private:
vector<Book> bookList; //Collection of books.
Library() :
newBookEvent(this) //Set the source of event as the current object
{
}
public:
string libraryName;
GoFPatterns::Event<Library, Book&> newBookEvent; //Declaring an event
//which will be fired when a
//new book is added to library
/*
* Library public constructor which initializes library name and the Event
*/
Library(string name) :
libraryName(name), newBookEvent(this) //Set the source of event will
//be the current object
{
}
/*
* Function to add a new book to library
*/
void AddBook(Book newBook)
{
//Add the book to collection
bookList.push_back(newBook);
//Fire the newBookEvent with the newly added book as argument
newBookEvent.FireEvent(newBook);
}
};
/*/
* A Student class used as a library event subscriber
*/
class Student
{
public:
int studentId;
string studentName;
private:
/*
* private constructor - to prevent making nameless books
*/
Student()
{
}
public:
Student(int id, string name) :
studentId(id), studentName(name)
{
}
/*
* The student gets advised to subscribe for a library so that -
* she will get notifications when ever new books arrive in library.
*/
void AdviseLibrary(Library& library)
{
//Subscribe to the event and specify the context as this object itself.
library.newBookEvent.Subscribe(onNewBookArrived, this);
}
/*
* Sample for handling event in a class's static member function
*/
static void onNewBookArrived(Library *eventSrc, Book &newBook,
void* context)
{
//Cast the context to Student pointer so we can know which object got this event.
Student* stPtr = static_cast<Student*> (context);
cout << endl << "==========================" << endl
<< "New Book notification" << endl
<< "==========================" << endl << "Book name = "
<< newBook.bookName << endl << "From library = "
<< eventSrc->libraryName << endl;
if (stPtr)
cout << "To Student = " << stPtr->studentName << endl;
cout << "==========================" << endl;
}
};
/*
* College class holds many libraries and many students.
* When a student is enrolled in the college, he is advised to -
* subscribe to an appropriate library.
*/
class College
{
public:
Library phyLib;
Library bioLib;
vector<Student> physicsStudents;
vector<Student> biologyStudents;
College() :
phyLib("Physics"), bioLib("Biology")
{
}
void EnrollStudentForPhysics(Student& newStudent)
{
newStudent.AdviseLibrary(phyLib);
physicsStudents.push_back(newStudent);
}
void EnrollStudentForBiology(Student& newStudent)
{
newStudent.AdviseLibrary(bioLib);
biologyStudents.push_back(newStudent);
}
};
/*
* Sample for handling event in a global function
*/
void GlobalNewBookHandler(Library *eventSrc, Book& newBook, void* context)
{
cout << endl << "============================" << endl
<< "Global New Book notification" << endl
<< "============================" << endl << "Book name = "
<< newBook.bookName << endl << "From library = "
<< eventSrc->libraryName << endl;
}
/*
* Program entry point; main() function
*/
int main()
{
College col1;
//Subscribe to newBookEvent in C# style using the += operator
//Sample for event handling in global functions.
col1.bioLib.newBookEvent += GlobalNewBookHandler;
//Enroll Students to college
col1.EnrollStudentForBiology(*(new Student(1, "Jibin")));
col1.EnrollStudentForBiology(*(new Student(2, "Vinod")));
col1.EnrollStudentForBiology(*(new Student(3, "Anish")));
col1.EnrollStudentForPhysics(*(new Student(1, "Xavier")));
col1.EnrollStudentForPhysics(*(new Student(2, "Shine")));
//Add Books to college libraries
col1.bioLib.AddBook(Book(1, "Human brain"));
col1.bioLib.AddBook(Book(2, "Stem cells"));
col1.phyLib.AddBook(Book(1, "Quantum mechanics"));
col1.phyLib.AddBook(Book(2, "Velocity and force"));
col1.phyLib.AddBook(Book(3, "Atom secrets"));
return 0; }
输出
============================
Global New Book notification
============================
Book name = Human brain
From library = Biology
==========================
New Book notification
==========================
Book name = Human brain
From library = Biology
To Student = Jibin
==========================
==========================
New Book notification
==========================
Book name = Human brain
From library = Biology
To Student = Vinod
==========================
==========================
New Book notification
==========================
Book name = Human brain
From library = Biology
To Student = Anish
==========================
============================
Global New Book notification
============================
Book name = Stem cells
From library = Biology
==========================
New Book notification
==========================
Book name = Stem cells
From library = Biology
To Student = Jibin
==========================
==========================
New Book notification
==========================
Book name = Stem cells
From library = Biology
To Student = Vinod
==========================
==========================
New Book notification
==========================
Book name = Stem cells
From library = Biology
To Student = Anish
==========================
==========================
New Book notification
==========================
Book name = Quantum mechanics
From library = Physics
To Student = Xavier
==========================
==========================
New Book notification
==========================
Book name = Quantum mechanics
From library = Physics
To Student = Shine
==========================
==========================
New Book notification
==========================
Book name = Velocity and force
From library = Physics
To Student = Xavier
==========================
==========================
New Book notification
==========================
Book name = Velocity and force
From library = Physics
To Student = Shine
==========================
==========================
New Book notification
==========================
Book name = Atom secrets
From library = Physics
To Student = Xavier
==========================
==========================
New Book notification
==========================
Book name = Atom secrets
From library = Physics
To Student = Shine
==========================
关注点
为了制作此示例程序,我使用了带有 MinGW 的 Eclipse CDT IDE。
我认为它是一个很好的经济型工具,可以用来玩 C++。
历史
在他的博士论文中,Karine Arnout 用 Eiffel 语言给出了一个示例实现。
我不知道是否已经发布了一些其他更好的方法来做到这一点。无论如何,我希望有所改进。
历史
- 2009 年 4 月 9 日:初始发布
- 2009 年 4 月 11 日:将头文件附件添加到文章中