Generisches Observer-Pattern mit void-Zeiger?!



  • Hallo!

    Ich möchte das Observerpattern einsetzen, allerdings reicht es mir nicht aus, "genotified" zu werden. Ich brauche den Zeiger auf das Objekt, damit ich entsprechend reagieren kann. Trotzdem soll mein Observer-Pattern generisch sein, damit ich nicht jedes Mal Code erzeugen muss, nur weil der Observer jetzt anders ist.

    Meine Idee war jetzt folgende:

    class IObservable {
       public:
          virtual ~IObservable() {}
    
          void attach (Observer* pObserver) {
             observers.push_back(pObserver);
    		}
    
          void notify() {   
    			 for (size_t i=0; i<observers.size(); ++i) {
    				observers[i]->update(this);
    			 }
             }
       private:
          std::vector<Observer*> observers;
    };
    

    Dazu der Observer:

    class Observer {
    	public:
    	    virtual ~Observer() {}
    	    virtual void update(void* pSubject) = 0;
    };
    

    Ich habe mich dabei versucht, an Java zu orientieren. Dort wird der Updatemethode zum einen das Observable-Objekt übergeben und zum anderen ein "Object" als Argument. Das versuche ich mit dem void-Zeiger nachzubilden, da es in C++ ja keine "OberBasisklasse" gibt.

    Frage Nr. 1) Hat wer andere Ideen, die besser sind als meine?

    Frage Nr. 2) Ist meine Idee überhaupt realisierbar? Mein Programm läuft genau solange gut, wie ich nicht versuche, einen Downcast durchzuführen:

    Geht:

    cout<<"Observer::Update aufgerufen"<<pSubject<<endl;
    

    Geht nicht (stürzt dann an einer ganz anderen Stelle im Objekt ab)

    cout<<"Observer::Update aufgerufen "<<static_cast<I2DGeometry*>(pSubject)->getPosition().x<<endl;
    

    Danke für's Zuhören



  • Mach den Observer zu einem Template. void* ist selten eine Lösung. Und Java würde ich mir bei solchen Dingen nicht als Vorbild nehmen, weil solche Dinge dort oft anders gehandhabt werden.

    Ansonsten könntest du dir vielleicht auch mal Boost.Function anschauen. Gibts übrigens auch als std::tr1::function (TR1) und std::function (C++0x). Siehe auch hier...



  • Warum übergibst du Update nicht ein Objekt vom Typ IObservable?



  • Wenn überhaupt brauchst dafür einen reinterpret_cast.

    Daraus ein template zu machen ist nur bedingt sinnvoll, außer man leitet von IObserver ab.

    template <class ObserverType>
    public TObserver : IObserver 
    {
      TObserver(ObserverType* observerThis);
    }
    
    void YourManagerClass::RegisterObserver<class ObserverType>(ObserverType* observeThis)
    {
      // std::vector< IObserver* > notifiers;
      notifiers.push_back(TObserver<ObserverType>(observerThis);
    }
    
    void YourManagerClass::notifiy();
    {
     for_each(notifiers, note)
     {
       // Spätestens hier gibts einen cast
       note->YourBaseClassFunction(this);
     }
    }
    


  • nurf schrieb:

    Wenn überhaupt brauchst dafür einen reinterpret_cast.

    Ne, void* castet man mit static_cast .



  • Klingt mir ein wenig nach CRTP 🙂

    template <typename Observable>
    class IObservable
    {
       public:
          virtual ~IObservable() {}
    
          void attach (Observer* pObserver) {
             observers.push_back(pObserver);
            }
    
          void notify() {   
                 for (size_t i=0; i<observers.size(); ++i) {
                    observers[i]->update(static_cast<Obserable>(this));
                 }
             }
       private:
          std::vector<Observer*> observers;
    };
    
    class DasDingDasObserviertWerdenSoll : public IObserable<DasDingDasObserviertWerdenSoll>
    {
      //...
    };
    


  • Diese Templatelösung ist jetzt aber nicht viel besser, schließlich hat update jetzt wieder gezwungenermaßen einen Zeiger als Argument, den man evtl. nicht will.

    In C++ gibt es mehr Möglichkeiten als in Java, weswegen ich einfach den signal/slot-Mechanismus anschauen würde. Dieser kann auf Methoden verweisen, nicht auf ganze Klassen. Das macht alles etwas dynamischer.

    Eine Dynamik in dem Sinne, dass die Observable-Klasse allgemein bleiben soll, aber man trotzdem einen Zeiger übergibt, geht so nicht, da muss man eben eine Spezialisierung bauen.

    #ifndef OBSERVABLE
    #define OBSERVABLE
    
    #include <vector>
    
    // General Observable for different Observers and their needs 
    
    template<typename Observer> 
    class Observable 
    { 
    private:
    	typedef typename Observer ObserverType;
    
    	typedef std::vector<ObserverType*> Observers; 
    	typedef typename Observers::iterator ObserversIt;     
    	Observers observers; 
    
    protected: 
    	Observers& getObservers() {return observers;} 
    
    public: 
    	void AddObserver(ObserverType& newObserver) {observers.push_back(&newObserver);} 
    
    	void notifyAll() 
    	{ 
    		for(ObserversIt it = observers.begin(); it != observers.end(); ++it) 
    			notify(*it); // nebenbei noch Template Method Pattern angewendet; das hat aber nix mit C++ Templates zu tun
    	} 
    
    	virtual void notify(ObserverType*) = 0; 
    
    	virtual ~Observable() {} 
    }; 
    
    #endif
    
    #ifndef PUSHOBSERVER
    #define PUSHOBSERVER
    
    // PushObserver - a specific observer which gets notification of source 
    
    class PushObservable; 
    
    class PushObserver 
    { 
    public:
    	virtual void onNotification(PushObservable* source) = 0; 
    };
    
    #endif
    
    #ifndef PUSHOBSERVABLE
    #define PUSHOBSERVABLE
    
    #include "Observable.hpp"
    #include "PushObserver.hpp"
    
    // PushObservable: A specific Observable which publishes the source 
    
    class PushObservable : public Observable<PushObserver> 
    { 
    public: 
    	/*virtual*/ void notify(PushObserver* observer) 
    	{ 
    		observer->onNotification(this); 
    	} 
    };
    
    #endif
    

    Edit: So, ist jetzt auch kompilierbar.

    Jetzt kann jeder Erbe vom Observable sich selbst aussuchen, was er dem jeweiligen Observer mitgibt. Aber ob das so wichtig ist... weiß ich nicht. Jedenfalls sind signal/slots deutlich kürzer und eleganter imo.


Anmelden zum Antworten