2013-07-29 19 views
9

Ich habe eine Animationsklasse. Ich brauche einige Beobachter für Play, Pause und Stop Ereignisse in der Animation. Ich habe 2 Lösungen für dieses Problem gefunden, aber ich weiß nicht, was ich wählen soll.Wie zu implementieren Observer-Muster in C++

  1. Verwendung boost :: Signale oder etwas ähnliches und registrieren Rückrufe für jede Veranstaltung

  2. Stellen Sie eine einfache Schnittstelle mit 3 rein virtuellen Funktionen (OnPlay(), OnPause(), OnStop()) und übergeben, um die Animation Klassenobjekte, dass Implementieren Sie diese Schnittstelle.

Es gibt Vor- und Nachteile für jede Methode. Ich werde versuchen, diejenigen aufzuzählen, die ich bisher gefunden:

Vorteile für 1.

  • kann ich
  • jede Memberfunktion/freie Funktion als Callback verwenden
  • ich habe nicht alle 3-Funktionen zu implementieren, wenn ich über alle von ihnen
  • gleiche Objekt aus Animation-Klasse ohne Durch zusätzliche Parameter für mehrere Animationen als Beobachter verwendet do not care
werden kann 10

Nachteile für 1.

  • muss ich für jeden Rückruf
  • eine aufrufbare Objekt erstellen Wenn ich später ein neues Ereignis hinzufügen möchten wird es schwer sein, die Orte zu finden, wo es verwendet wurde (Compiler kann mich nicht zwingen, neues Ereignis zu implementieren oder zu ignorieren).
  • Irgendwie seltsame Syntax (ich muss std :: bind/boost :: bind überall verwenden).

Vorteile für 2.

  • Einfache Konstruktion
  • zu verstehen, wenn ich ein neues Ereignis in Zeichentrick/Observer-Interface-Klasse hinzufügen, werden der Compiler erzwingen mich (leer vielleicht) zu implementieren die neue Funktion.

Nachteile für 2.

  • Ich muss (leer vielleicht) implementieren 3 Funktionen, auch wenn ich werde nur ein
  • gleiche Objekt kann nicht als Beobachter für verschiedene verwendet werden Animation ohne einige zusätzliche Parameter aus der Animation (ID oder etwas) zu senden.
  • Kostenlose Funktionen können nicht verwendet werden.

Können Sie mir bitte beraten, was zu verwenden? Aus Ihrer Erfahrung, was ist besser für dieses Problem - die Freiheit von der ersten Approach oder klar und leicht zu verstehen Code aus der zweiten?Können Sie mir bitte andere Vorteile/Nachteile für beide Methoden oder andere Lösungen geben?

+6

In C++ 11 (die ich nehme an, Sie verwenden können, da Sie die Frage mit ihm getaggt) lambdas entfernen die meisten der „Nachteile für 1 ". –

+0

Wenn die Verwendung von 'std :: bind 'eine seltsame Syntax für Sie ist (besonders im Vergleich zu Interfaces mit virtuellen' OnWhatever'-Funktionen), sollten Sie lieber Ihre bevorzugte Sprache überdenken. –

+0

@ChristianRau Nicht für mich, aber ich bin nicht der Einzige, der an der Codebasis arbeitet. – Felics

Antwort

3

Zuerst wäre es nützlich zu wissen, ob die "Bindung" zur Kompilierzeit bekannt ist oder nicht. Wenn dem so ist, würde ich Ihnen empfehlen, sich mit den Policies zu befassen.

Abgesehen davon würde ich für eine Mischung der beiden Lösungen gehen, d. H. Den Schnittstellenansatz verwenden und eine Schnittstelle implementieren, die als Relayer für Signale/Frei-Funktionen dient. Auf diese Weise können Sie Standardverhalten haben, Sie können benutzerdefinierte Objekte hinzufügen, die die gesamte Schnittstelle implementieren, und haben im Grunde die Vorteile der beiden Ansätze sowie viel Flexibilität.

Hier ist ein grundlegendes Beispiel für den vorgeschlagenen Ansatz, ich hoffe, es ist hilfreich.

#include <functional> 

using namespace std; 

template <class ObserverPolicy> 
class Animation : public ObserverPolicy{ 

}; 

class MonolithicObserver{ 
    public: 
    void play(){ 
     state = playing; 
    } 
    void pause(){ 
     if(playing == state) 
      state = stopped; 
    } 
    void stop(){ 
     state = stopped; 
    } 
    private: 
    enum {playing, paused, stopped} state; 
}; 

struct doNothing{ 
    static void play(){} 
    static void pause(){} 
    static void stop(){} 
}; 

struct throwException{ 
    class noPlay{}; 
    class noPause{}; 
    class noStop{}; 
    static void play(){ 
     throw noPlay(); 
    } 
    static void pause(){ 
     throw noPause(); 
    } 
    static void stop(){ 
     throw noStop(); 
    } 
}; 

template <class DefaultPolicy = doNothing> 
class FreeFunctionObserver{ 
    public: 
    void play(){ 
     if(playHandle) 
      playHandle(); 
     else 
      DefaultPolicy::play(); 
    } 
    void pause(){ 
     if(pauseHandle) 
      pauseHandle(); 
     else 
      DefaultPolicy::pause(); 
    } 
    void stop(){ 
     if(stopHandle) 
      stopHandle(); 
     else 
      DefaultPolicy::stop(); 
    } 
    void setPlayHandle(std::function<void(void)> p){ 
     playHandle = p; 
    } 
    void setPauseHandle(std::function<void(void)> p){ 
     pauseHandle = p; 
    } 
    void setStopHandle(std::function<void(void)> p){ 
     stopHandle = p; 
    } 
    private: 
    std::function<void(void)> playHandle; 
    std::function<void(void)> pauseHandle; 
    std::function<void(void)> stopHandle; 
}; 

void play(){} 
void pause(){} 
void stop(){} 

int main(){ 
    Animation<FreeFunctionObserver<> > affo; 
    affo.setPlayHandle(play); 
    affo.setPauseHandle(pause); 
    affo.setStopHandle(stop); 
    affo.play(); 
    affo.pause(); 
    affo.stop(); 

    Animation<FreeFunctionObserver<throwException> > affot; 
    try{ 
     affot.play(); 
    } 
    catch(throwException::noPlay&){} 

    Animation<MonolithicObserver> amo; 
    amo.play(); 
    amo.pause(); 
    amo.stop(); 
} 

, die Sie können here versuchen. Dieses Beispiel verwendet insbesondere eine Richtlinienklasse (daher ist keine Schnittstelle "formal" definiert und Sie können die Schnittstelle "anreichern", wie es mit setPlayHandle gemacht wurde). Sie können jedoch auch etwas ähnliches mit der Laufzeitbindung tun.

0

Ich denke, Sie können beide verwenden, aber es hängt von den Bedürfnissen ab. Ich habe einen Code, wo ich diese beiden Muster verwende. Es gibt viele Funktionen namens onSomething() (onMouseButton(), onKey(), onDragStart() usw.), aber es gibt auch Callbacks. Wenn ich etwas Verhalten implementieren muss, aber für die gesamte Klasse von Objekten, verwende ich onSomething() -Ansatz. Aber wenn ich ein paar Objekte der gleichen Klasse habe, aber nur ein Teil von ihnen eine erweiterte Funktionalität benötigt, ist Callback der perfekte Weg.

In der Umsetzung ist es so gemacht: es gibt einige Dispatching-Code, der onSomething() -Methode (die bool zurückgibt) verwendet, wenn es Ergebnis ist falsch - dann gibt es überprüfen, ob ein Callback definiert ist, wenn ja, es wird ausgeführt.

1

Für alle außer die einfachsten von Spielzeugbeispielen wäre Boost.Signals2 meiner Meinung nach die überlegene Lösung. Es ist gut gestaltet, gut getestet und gut dokumentiert. Das Rad neu zu erfinden ist gut für die Hausübung, aber nicht für den Produktionscode. Z.B. Deinen eigenen Beobachter threadsicher zu machen, ist nicht trivial, um richtig und effizient zu werden.

Insbesondere Ihre aufgeführten Nachteile

  • diskutieren Sie können anstelle von benannten Funktionsobjekte C++ 11 lambdas schreiben oder mit boost::bind Syntax (die ohnehin für die meisten Nutzung kompliziert ist nicht wirklich)
  • ich nicht verstehe deinen Standpunkt über ungenutzte Ereignisse. Sie können ziemlich weit gehen connnection management, um Signale von Steckplätzen abzufragen und zu trennen.

TL; DR: Machen Sie sich mit Boost.Signals2

+0

Ich würde zögern zu sagen, dass entweder Boost.Signals oder Boost.Signals2 vom Leistungsaspekt "effizient" sind. Ich stimme jedoch zu, dass sie gründlich dokumentiert und gepflegt werden. @OP: Du solltest eigentlich versuchen, beide Wege damit zu gehen, da du lernen würdest, ob deine ursprünglichen Vor- und Nachteile von jedem tatsächlich deine _ Erwartungen erfüllen.Wählen Sie für eine Signalbibliothek eine, die regelmäßig gewartet wird, über eine gute Dokumentation verfügt und, für mögliche Thread-Sicherheitsanforderungen, eine Bibliothek, die Thread-sicher ist. – ApEk

+0

@ApEk Ich würde sagen, dass (fast) alle Boost-Bibliotheken * effizient sind, was sie anbieten *, und der einzige Grund, sie nicht zu benutzen, ist, dass sie manchmal mehr bieten als Sie brauchen, und Sie würden für etwas bezahlen wird nicht verwendet. – TemplateRex