0

观察者模式在事件驱动系统中非常有用。以下是它可能以两种语言实现的方式:

爪哇

使用 AOP 库或字节码工程(BCEL、cglib、asm 等)动态创建子类。对被观察属性的 getter 或 setter 的任何调用都会通知任何附加的观察者。

Objective-C

这类似于 Java - 使用 isa swizzling 动态创建子类。任何对被观察属性的调用都会通知附加的观察者。有趣的是,在 Objective-C 中,如果所有观察者都被移除,我们可以在没有包装属性方法的情况下回滚到原始类。而在 Java 中,一个类通常只加载一次,因此您总是通知一组(可能是空的)观察者。

C++ 怎么样?

由于 C++ 中的反射有限,因此很难使用上述方法。C++ 中的“最佳”(我的意思是典型或事实上的标准)方法是什么?有什么方法可以避免像我上面提到的 Java 和 Objective-C 实现中的样板代码?也许使用 C++ 元编程特性?

4

2 回答 2

3

我不相信有一种方法可以仅使用反射在 C++ 中实现观察者模式。如果您不使用任何外部工具,则必须手动实现所有内容。例如,我会像这样实现它:

#include <iostream>
#include <set>
using namespace std;

class Impl;

class ObserverBase {
public:
    virtual void propertyChanged(Impl *impl, int value) = 0;
};

class Impl {
public:
    void setProperty(int value) {
        if (m_property != value) {
            m_property = value;
            for(auto observer:m_observers) {
                observer->propertyChanged(this, value);
            }
        }
    }
    int getProperty() {
        return m_property;
    }

    void addObserver(ObserverBase *observer) {
        m_observers.insert(observer);
    }
private:
    int m_property;
    set<ObserverBase *> m_observers;
};

class Observer : public ObserverBase {
public:
    virtual void propertyChanged(Impl *impl, int value) {
        cout << "Saw new value of " << value << "!" << endl;
    }
};

int main() {
    Impl impl;
    impl.addObserver(new Observer());
    impl.setProperty(5);
}

如果您希望自动生成 ObserverBase 和 Impl 中的 for 循环,您可以在编译时解析 C++。我不知道有什么可以为你做的。

如果您使用的是第三方库,它们可能包含提供帮助的工具。例如,如果您使用 Qt,您可以使用信号/插槽来通知观察者更改。

于 2013-10-20T04:04:55.113 回答
1

我编写了很多 C++ 代码,并且需要为我正在开发的一些游戏组件创建一个 Observer。我需要一些东西来将“帧开始”、“用户输入”等作为游戏中的事件分发给感兴趣的各方。

我还希望它是纯 C++,不依赖于平台或特定技术(例如 boost、Qt 等),因为我经常在不同的项目中构建和重用组件(以及它们背后的想法)。

这是我提出的解决方案的粗略草图:

  1. Observer 是一个带有键(枚举值,而不是字符串)的单例,供 Subjects 注册感兴趣。因为它是一个单例,所以它始终存在。
  2. 每个主题都派生自一个公共基类。基类有一个抽象虚函数 Notify(...) 必须在派生类中实现,以及一个析构函数,当它被删除时,它会从 Observer 中删除(它总是可以到达)。
  3. 在观察者本身内部,如果在 Notify(...) 正在进行时调用 Detach(...),则任何分离的 Subjects 最终都会出现在列表中。
  4. 当在 Observer 上调用 Notify(...) 时,它会创建主题列表的临时副本。当它迭代它时,它将它与最近分离的进行比较。如果目标不在其上,则在目标上调用 Notify(...)。否则,将被跳过。
  5. Observer 中的 Notify(...) 还跟踪处理级联调用的深度(A 通知 B、C、D,并且 D.Notify(...) 触发对 E 的 Notify(...) 调用, ETC。)

这是界面最终的样子:

/* 
 The Notifier is a singleton implementation of the Subject/Observer design
 pattern.  Any class/instance which wishes to participate as an observer
 of an event can derive from the Notified base class and register itself
 with the Notiifer for enumerated events.

 Notifier derived classes MUST implement the notify function, which has 
 a prototype of:

 void Notify(const NOTIFIED_EVENT_TYPE_T& event)

 This is a data object passed from the Notifier class.  The structure 
 passed has a void* in it.  There is no illusion of type safety here 
 and it is the responsibility of the user to ensure it is cast properly.
 In most cases, it will be "NULL".

 Classes derived from Notified do not need to deregister (though it may 
 be a good idea to do so) as the base class destrctor will attempt to
 remove itself from the Notifier system automatically.

 The event type is an enumeration and not a string as it is in many 
 "generic" notification systems.  In practical use, this is for a closed
 application where the messages will be known at compile time.  This allows
 us to increase the speed of the delivery by NOT having a 
 dictionary keyed lookup mechanism.  Some loss of generality is implied 
 by this.

 This class/system is NOT thread safe, but could be made so with some
 mutex wrappers.  It is safe to call Attach/Detach as a consequence 
 of calling Notify(...).  

 */


class Notified;

class Notifier : public SingletonDynamic<Notifier>
{
public:
   typedef enum
   {
      NE_MIN = 0,
      NE_DEBUG_BUTTON_PRESSED = NE_MIN,
      NE_DEBUG_LINE_DRAW_ADD_LINE_PIXELS,
      NE_DEBUG_TOGGLE_VISIBILITY,
      NE_DEBUG_MESSAGE,
      NE_RESET_DRAW_CYCLE,
      NE_VIEWPORT_CHANGED,
      NE_MAX,
   } NOTIFIED_EVENT_TYPE_T;

private:
   typedef vector<NOTIFIED_EVENT_TYPE_T> NOTIFIED_EVENT_TYPE_VECTOR_T;

   typedef map<Notified*,NOTIFIED_EVENT_TYPE_VECTOR_T> NOTIFIED_MAP_T;
   typedef map<Notified*,NOTIFIED_EVENT_TYPE_VECTOR_T>::iterator NOTIFIED_MAP_ITER_T;

   typedef vector<Notified*> NOTIFIED_VECTOR_T;
   typedef vector<NOTIFIED_VECTOR_T> NOTIFIED_VECTOR_VECTOR_T;

   NOTIFIED_MAP_T _notifiedMap;
   NOTIFIED_VECTOR_VECTOR_T _notifiedVector;
   NOTIFIED_MAP_ITER_T _mapIter;

   // This vector keeps a temporary list of observers that have completely
   // detached since the current "Notify(...)" operation began.  This is
   // to handle the problem where a Notified instance has called Detach(...)
   // because of a Notify(...) call.  The removed instance could be a dead
   // pointer, so don't try to talk to it.
   vector<Notified*> _detached;
   int32 _notifyDepth;

   void RemoveEvent(NOTIFIED_EVENT_TYPE_VECTOR_T& orgEventTypes, NOTIFIED_EVENT_TYPE_T eventType);
   void RemoveNotified(NOTIFIED_VECTOR_T& orgNotified, Notified* observer);

public:

   virtual void Reset();
   virtual bool Init() { Reset(); return true; }
   virtual void Shutdown() { Reset(); }

   void Attach(Notified* observer, NOTIFIED_EVENT_TYPE_T eventType);
   // Detach for a specific event
   void Detach(Notified* observer, NOTIFIED_EVENT_TYPE_T eventType);
   // Detach for ALL events
   void Detach(Notified* observer);

   /* The design of this interface is very specific.  I could 
    * create a class to hold all the event data and then the
    * method would just have take that object.  But then I would
    * have to search for every place in the code that created an
    * object to be used and make sure it updated the passed in
    * object when a member is added to it.  This way, a break
    * occurs at compile time that must be addressed.
    */
   void Notify(NOTIFIED_EVENT_TYPE_T, const void* eventData = NULL);

   /* Used for CPPUnit.  Could create a Mock...maybe...but this seems
    * like it will get the job done with minimal fuss.  For now.
    */
   // Return all events that this object is registered for.
   vector<NOTIFIED_EVENT_TYPE_T> GetEvents(Notified* observer);
   // Return all objects registered for this event.
   vector<Notified*> GetNotified(NOTIFIED_EVENT_TYPE_T event);
};

/* This is the base class for anything that can receive notifications.
 */
class Notified
{
public:
   virtual void Notify(Notifier::NOTIFIED_EVENT_TYPE_T eventType, const void* eventData) = 0;
   virtual ~Notified();

};

typedef Notifier::NOTIFIED_EVENT_TYPE_T NOTIFIED_EVENT_TYPE_T;

注意:Notified 类有一个函数,Notify(...) 这里。因为 void* 不是类型安全的,所以我创建了其他版本,其中 notify 如下所示:

virtual void Notify(Notifier::NOTIFIED_EVENT_TYPE_T eventType, int value); 
virtual void Notify(Notifier::NOTIFIED_EVENT_TYPE_T eventType, const string& str);

相应的 Notify(...) 方法已添加到通知程序本身。所有这些都使用一个函数来获取“目标列表”,然后在目标上调用适当的函数。这很好用,并且使接收器不必进行难看的演员表。

这似乎运作良好。该解决方案与源代码一起发布在网络上这是一个相对较新的设计,因此非常感谢任何反馈。

于 2013-11-13T01:39:18.773 回答