Wie passieren Daten zu ‚generic‘ Beobachter? Als Argument oder als eine einzige Struktur?

StackOverflow https://stackoverflow.com/questions/3601886

  •  25-09-2019
  •  | 
  •  

Frage

Ich bin beschäftigt das Hinzufügen eines generischen Beobachter Mechanismus zu einer Legacy-C ++ Anwendung (Visual Studio 2010 verwenden, aber .NET nicht verwenden, so .Net Delegierten der Frage sind out).

Bei der Gestaltung Ich mag aus dem generischen Beobachter Mechanismus, um den anwendungsspezifischen Teil so weit wie möglich trennen.

Die logischste Art und Weise der Umsetzung Beobachter scheint auf diese Weise:

class IDoThisObserver
   {
   public:
      void handlDoThis(int arg1, int arg2) = 0;
   };

Für jede Art von Beobachtern (IDoThisObserver, IDoThatObserver, ...) die Argumente der Verfahren (handleDoThis, handleDoThat) unterschiedlich sind.

Was bleibt in allgemeiner Weise die Beobachter zu speichern, wie folgt aus:

template<typename T>
class ObserverContainer
   {
   public:
      void addObserver (T &t) {m_observers.push_back(&t);}
   private:
      std::list<T*> m_observers;
   };

einen Beobachter aufrufen kann nicht verallgemeinert werden, da die Argumente unterschiedlich für jeden Beobachter-Typ sind.

Ein alternativer Weg zu ‚Pack‘ würde alle Argumente in ein Argument, wie folgt aus:

struct DoThisInfo
   {
   DoThisInfo (int arg1, int arg2) : m_arg1(arg1), m_arg2(arg2) {}
   int m_arg1;
   int m_arg2;
   };

Und dann definieren eine allgemeinere Beobachter, wie folgt aus:

template<typename T>
class IObserver
   {
   public:
      void notify(const T &t) = 0;
   };

Und eine Sammlung dieser Beobachter wäre dies dann werden:

template<typename T>
class ObserverContainer
   {
   public:
      void addObserver (IObserver<T> &obs) {m_observers.push_back(&obs);}
   private:
      std::list<IObserver<T>*> m_observers;
   };

Nun, viel Logik zentral zu diesem ObserverContainer hinzugefügt werden können, alle Beobachter einschließlich aufrufen. Der ‚Initiator‘ des Anrufs muss nur in Infostruktur erstellen und zu füllen.

Klassen, die von mehreren Arten von Beobachtern erben will, muß es wie dies zu tun:

class MyObserver : public IObserver<NotifyThis>, public IObserver<NotifyThat>
   {
   ...
   };

Welche diese Ansätze (Beobachter mit mehreren expliziten Argumenten oder mit einem struct Argumente) scheint die beste? Gibt es irgendwelche Vorteile oder Nachteile zu einer dieser beiden Ansätze?

Bearbeiten : Ich sah ein bisschen weiter nach alternativen Ansätzen und die Slot / Signal Ansatz scheint einen weiteren guten Kandidaten. Gibt es wichtige Nachteile in Slot / Signal, die ich kenne soll?

War es hilfreich?

Lösung

Das Design mit dem struct Argumente ist auf jeden Fall besser, da es für generischen Code ermöglicht in der ObserverContainer geschrieben werden. Es ist generell eine gute Designpraxis longish Argument Listen mit Objekten zu ersetzen, die die Argumente einkapseln und das ist ein gutes Beispiel für die Auszahlung. Durch eine allgemeine Abstraktion für Ihre notify Methode zu schaffen (mit der Struktur Sie notify als Methode sind definiert, die ein Stück von „Daten“, während mit der ARG-Liste nimmt sind Sie eine Methode definieren, die zwei Zahlen nimmt) Sie lassen sich schreibt generischen Code, den die Methode verwendet und muss nicht betreffen selbst mit der genauen Zusammensetzung des in Teil der Daten übergeben.

Andere Tipps

Warum nicht einfach tun:

class IObserver {
    // whatever is in common
};

class IDoThisObserver : public IObserver
{
   public:
      void handlDoThis(int arg1, int arg2) = 0;
};

class IDoThatObserver : public IObserver
{
   public:
      void handlDoThat(double arg1) = 0;
};

Dann haben Sie:

class ObserverContainer
{
   public:
      void addObserver (IObserver* t) {m_observers.push_back(t);}
   private:
      std::list<IObserver*> m_observers;
};

Haben Sie in Boost.Signals geschaut? Besser als das Rad neu zu implementieren.

Wie für Parameter: Aufruf eines Beobachters / Schlitz konzeptuell das gleiche wie sein sollte, wenn Sie eine normale Funktion aufrufen würde. Die meisten SignalSlots-Implementierungen können mehrere Parameter, es so verwenden. Und bitte unterschiedliche Signale für verschiedene Beobachter Typen verwenden, dann gibt es keine Notwendigkeit, Daten in Varianten laufen um.

Zwei Nachteile der Observer-Muster / SignalSlots ich gesehen habe:
1) Der Programmablauf ist schwierig oder sogar unmöglich, indem man nur an der Quelle zu verstehen.
2) Stark dynamische Programme mit vielen Beobachtern / SignalSlots können ein „löschen diese“ Begegnung

Alles beiseite, ich wie Beobachter / SignalSlots mehr als Subklassen und damit eine hohe Kopplung.

Ich glaube nicht, entweder Ihre Ansätze Ihre Anforderung passen würde, wie ist. Doch eine kleine Änderung einer DataCarrier enthält den Datensatz über alle Beobachter geleitet, in dem jeder Beobachter würde wissen, was mit dem Trick tun würde, zu lesen. Der Beispielcode unten kann löscht es (Anmerkung Ich habe nicht kompiliert)

 enum Type {
    NOTIFY_THIS,
    NOTIFY_THAT
 };

 struct Data {
 virtual Type getType() = 0;
 };

 struct NotifyThisData: public Data {
    NotifyThisData(int _a, int _b):a(_a), b(_b) { }
    int a,b;
    Type getType() { return NOTIFY_THIS; }
 };

 struct NotifyThatData: public Data {
    NotifyThatData(std::string _str):str(_str) { }
    std::string str;
    Type getType() { return NOTIFY_THAT; }
 };

 struct DataCarrier {
    std::vector<Data*> m_TypeData;  
 };

 class IObserver {
 public:
     virtual void handle(DataCarrier& data) = 0;
 };

 class NotifyThis: public virtual IObserver {
 public:
         virtual void handle(DataCarrier& data) {
                 vector<Data*>::iterator iter = find_if(data.m_TypeData.begin(), data.m_TypeData.end(), bind2nd(functor(), NOTIFY_THIS);
                 if (iter == data.m_TypeData.end())
                         return;
                 NotifyThisData* d = dynamic_cast<NotifyThisData*>(*iter);
                 std::cout << "NotifyThis a: " << d->a << " b: " << d->b << "\n";
         }
 };

 class NotifyThat: public virtual IObserver {
 public:
         virtual void handle(DataCarrier& data) {
                 vector<Data*>::iterator iter = find_if(data.m_TypeData.begin(), data.m_TypeData.end(), bind2nd(functor(),NOTIFY_THAT);
                 if (iter == data.m_TypeData.end())
                         return;            
                 NotifyThatData* d = dynamic_cast<NotifyThatData*>(*iter);
                 std::cout << "NotifyThat str: " << d->str << "\n";
         }
 };

 class ObserverContainer
    {
    public:
       void addObserver (IObserver* obs) {m_observers.push_back(obs);}
       void notify(DataCarrier& d) {
                 for (unsigned i=0; i < m_observers.size(); ++i) {
                         m_observers[i]->handle(d);
                 }
         }
    private:
       std::vector<IObserver*> m_observers;
    };

 class MyObserver: public NotifyThis, public NotifyThat {
 public:
         virtual void handle(DataCarrier& data) { std::cout << "In MyObserver Handle data\n"; }
 };

 int main() {
         ObserverContainer container;
         container.addObserver(new NotifyThis());
         container.addObserver(new NotifyThat());
         container.addObserver(new MyObserver());

         DataCarrier d;
         d.m_TypeData.push_back(new NotifyThisData(10, 20));
         d.m_TypeData.push_back(new NotifyThatData("test"));

    container.notify(d);
    return 0;
 }

Auf diese Weise u müssen nur die Enum ändern, wenn u eine neue Struktur hinzuzufügen. u kann auch verwenden boost :: shared_ptr das Chaos von Zeigern zu behandeln.

Ich würde die Syntax ist gerade nicht bekommen, so werde ich nur die Erklärungen zeigen die Strukturen zu illustrieren. Ein generischer Beobachter könnte, einen Parameter zu erwarten, die auf bestimmte Formen des erforderlichen Parameter entweder subclassed ist oder Struktur eine horizontale Abbildung aller primitiven Parameter einschließlich, die von Ihrem Beobachter benötigt werden. Dann könnte die ObserverContainer Funktion als Abstrakte und jede Unterklasse der ObserverContainer könnte DoThatObserverFactory und DoThisObserverFactory sein. Die Fabrik würde einen Beobachter bauen und eine Konfiguration für die Beobachter weist es zu erklären, welche Parameter zu erwarten.

class AbstractObserverFactory {...};
class DoThatObserverFactory : AbstractObserverFactory {...};
class DoThisObserverFactory : AbstractObserverFactory {...};
class ObserverParam {...};
class DoThatObserverParam : ObserverParam {...};
class DoThisObserverParam : ObserverParam {...};
class Observer;
class DoThisObserver : public Observer
{
   public:
      void handlDoThis(DoThisObserverParam);
};
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top