2013-10-20 3 views
0

Шаблон наблюдателя может быть действительно полезен в управляемых событиями системах. Вот как это может быть реализовано на двух языках:Реализация шаблона наблюдателя в C++

Java

Используйте библиотеку AOP или байт-код машиностроения (BCEL, CGLIB, ASM, и т.д.), чтобы создать суб-класс на лету. Любые обращения к получателю или сеттеру наблюдаемого имущества уведомляют всех прикрепленных наблюдателей.

Objective-C

Это похоже на Java - использует иша swizzling создать суб-класс на лету. Любые обращения к наблюдаемому имуществу уведомляют приложенных наблюдателей. Интересно, что в Objective-C мы можем вернуться к исходному классу без методов обернутых свойств, если все наблюдатели будут удалены. В то время как в Java класс обычно загружается один раз, поэтому вы всегда уведомляете (возможно, пустой) набор наблюдателей.

Как насчет C++?

С ограниченным отражением на C++ было бы трудно использовать вышеуказанные подходы. Что такое «лучший» (под этим я подразумеваю стандартный или дефакто-стандартный) подход в C++? Есть ли способ избежать кода котельной, как в реализациях Java и Objective-C, на которые я ссылался выше? Возможно, используя функции мета-программирования C++?

+1

Что именно вы спрашиваете? Вы ищете автоматический способ реализовать это на C++? Как вы помните, C++ не имеет очень сильного отражения, поэтому автоматическая реализация, вероятно, невозможна. Вы ищете общий пример? Или для автоматического метода? – MJD

+1

Почему вы отметили этот Obj-C и Java? Ваш вопрос касается C++. У вас уже есть ответы на Java и Obj-C. – CaptJak

+0

Шаблон наблюдателя не имеет особой зависимости от отражения или аспектно-ориентированного программирования. Это детали реализации. –

ответ

3

Я не верю, что есть способ реализовать шаблон Observer в 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 и цикл в осущ, чтобы быть автоматически сгенерирован, можно разобрать C++ во время компиляции. Я ничего не знаю об этом.

Если вы используете стороннюю библиотеку, они могут включать в себя инструменты, которые помогут вам. Например, если вы используете Qt, вы можете использовать сигнальные/слоты для уведомления наблюдателей об изменениях.

+0

Спасибо @MJD. Я проверю, что делает Qt. (У меня нет насущного требования сделать это прямо сейчас, просто интересно узнать больше на C++). , , будет принимать ответ в течение следующих 24 часов. –

1

Я пишу много кода на C++ и должен был создать Observer для некоторых игровых компонентов, над которыми я работал. Мне нужно было что-то, чтобы распространять «начало кадра», «ввод пользователя» и т. Д., Как события в игре заинтересованным сторонам.

Я также хотел, чтобы он был прямым C++, не зависящим от платформы или конкретной технологии (например, boost, Qt и т. Д.), Поскольку я часто создаю и повторно использую компоненты (и идеи, стоящие за ними) в разных проекты.

Вот грубый набросок того, что я придумал, как решение:

  1. Наблюдатель одноэлементно с ключами (перечислимых значениями, а не строки) для субъектов для регистрации интереса Потому что это. singleton, он всегда существует.
  2. Каждый субъект получен из общего базового класса. Базовый класс имеет абстрактную виртуальную функцию Notify (...), которая должна быть реализована в производных классах, и деструктор, который удаляет его из Observer (который он всегда может достичь) при его удалении.
  3. Внутри самого наблюдателя, если вызывается Detach (...) во время выполнения Notify (...), любые отдельные объекты попадают в список.
  4. Когда уведомление (...) вызывается в Observer, он создает временную копию списка Subject. По мере того как он выполняет итерацию, он сравнивает его с недавно отделившимся. Если цель не указана, на цель вызывается Notify (...). В противном случае он пропускается.
  5. Уведомление (...) в Observer также отслеживает глубину обработки каскадных вызовов (A уведомляет B, C, D и D.Notify (...) вызывает вызов Notify (...) Е и т.д.)

Это то, что интерфейс в конечном итоге выглядит как:

/* 
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; 

ПРИМЕЧАНИЕ: Заявленный класс имеет одну функцию, 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 (...) методы были добавлены к самому Notifier. Все они использовали одну функцию для получения «списка целей», а затем называли соответствующую функцию для целей. Это хорошо работает и не позволяет приемнику делать уродливые броски.

Это похоже на работу. Решение размещено в сети here вместе с исходным кодом. Это относительно новый дизайн, поэтому любая обратная связь очень ценится.

+0

Отличная работа! Хороший код, хорошие документы, отличный блог :) Посмотрите вперед, чтобы попробовать это. –

+0

Добро пожаловать. Не стесняйтесь, дайте мне знать, как это работает. – FuzzyBunnySlippers

Смежные вопросы