65.9K
CodeProject 正在变化。 阅读更多。
Home

观察者模式作为 C++ 组件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.26/5 (13投票s)

2009年4月9日

CPOL

2分钟阅读

viewsIcon

34636

downloadIcon

347

观察者模式被制作成一个可重用的 C++ 组件

引言

本文介绍了一个可复用的 C++ 泛型组件,该组件可以代替观察者设计模式。它具有以下优点:

  • 消除了模式骨架实现的重复任务。
  • 即使是不熟悉观察者模式结构的初学者开发人员也可以轻松使用它。
  • 代码可重用性反过来减少了错误。
  • 无需从 abstract 观察者类继承观察者类。
  • 事件订阅可以使用 C# 风格的现代语法完成。

[概念来源:来自模式到组件 - Karine Arnout 的博士论文]

背景

观察者设计模式是 Gang of Four (GoF) 确定的 23 种流行设计模式中使用最广泛的设计模式之一。传统观察者模式的实现可以在 这里 找到。

Observer pattern UML Diagram

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 日:将头文件附件添加到文章中
© . All rights reserved.