Domanda

Sono occupato l'aggiunta di un meccanismo di osservatore generico per un'applicazione legacy C ++ (usando Visual Studio 2010, ma non utilizzando .Net, quindi i delegati Net sono fuori discussione).

Nella progettazione voglio separare la parte specifica per l'applicazione il più possibile dal meccanismo osservatore generico.

Il modo più logico degli osservatori di attuazione sembra in questo modo:

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

Per ogni tipo di osservatore (IDoThisObserver, IDoThatObserver, ...) gli argomenti dei metodi (handleDoThis, handleDoThat) sono diversi.

Ciò che rimane in modo generico di memorizzare gli osservatori, in questo modo:

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

Chiamare un osservatore non può essere generalizzata in quanto gli argomenti sono diversi per ogni tipo di osservatore.

Un modo alternativo sarebbe quello di 'pacchetto' tutti gli argomenti in un unico argomento, come questo:

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

E poi definire un osservatore più generico, in questo modo:

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

E una raccolta di questi osservatori sarebbe poi diventata questa:

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

Ora, molto più logica può essere aggiunto a questa centrale ObserverContainer, inclusi chiamando tutti gli osservatori. Il 'iniziatore' della chiamata ha solo bisogno di creare e di riempimento nella struttura di notifica.

Le classi che vogliono ereditare da più tipi di osservatori, hanno bisogno di fare in questo modo:

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

Quale di questi approcci (osservatori con più argomenti espliciti o con un argomento struct) sembra il migliore? Ci sono vantaggi o svantaggi per uno di questi approcci?

Modifica : Ho guardato un po 'più di approcci alternativi, e il / approccio Slot segnale sembra un altro buon candidato. Ci sono degli svantaggi importanti Slot / Segnale che dovrei sapere di?

È stato utile?

Soluzione

Il design con l'argomento struct è decisamente migliore in quanto consente di codice generico da scrivere nel ObserverContainer. E 'generalmente una buona pratica di progettazione per sostituire liste di argomenti piuttosto lunga con gli oggetti che incapsulano gli argomenti e questo è un buon esempio di payoff. Con la creazione di un'astrazione più generale per il metodo notify (con la struct si sta definendo notify come un metodo che prende un pezzo di "dati" che, con l'elenco arg si sta definendo un metodo che prende due numeri) ti permettono di scrivere il codice generico che utilizza il metodo e non deve a se stessa preoccupazione con la composizione esatta del passato nel blocco di dati.

Altri suggerimenti

Perché non basta fare:

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;
};

Poi si deve:

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

Hai guardato in Boost.Signals? Meglio di reimplementare la ruota.

Per quanto riguarda i parametri: Chiamare un osservatore / Slot dovrebbe concettualmente essere lo stesso come se si potrebbe chiamare una funzione ordinaria. La maggior parte dei SignalSlots-implementazioni consentono a più parametri, in modo da utilizzare. E si prega di utilizzare segnali differenti per i diversi tipi di osservatori, allora non c'è bisogno di passare in giro i dati in varianti.

Due Svantaggi dell'osservatore-pattern / SignalSlots che abbia mai visto:
1) Il flusso del programma è difficile o addirittura impossibile da capire, cercando solo alla fonte.
2) programmi fortemente dinamici con un sacco di osservatori / SignalSlots possono incontrare un "cancellare questo"

Tutto parte, i come osservatori / SignalSlots più di sottoclasse e quindi accoppiamento alto.

Non credo che uno dei tuoi approcci misura il vostro requisito come è. Tuttavia una piccola modifica utilizzando un DataCarrier contenente il set di dati passati attraverso tutti gli osservatori in cui ogni osservatore avrebbe saputo cosa leggere farebbe il trucco. Il codice di esempio riportato di seguito potrebbe cancellarlo (nota non ho compilato)

 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;
 }

In questo modo u necessità di modificare solo l'enum se u aggiungere una nuova struttura. Anche u possibile utilizzare boost :: shared_ptr per gestire il pasticcio di puntatori.

Non avrebbe avuto il diritto di sintassi quindi sto solo andando a elencare le dichiarazioni per illustrare le strutture. Un osservatore generica potrebbe essere fatto di aspettarsi un parametro che è o sottoclassi a specifiche forme dei parametri richiesti o è struct tra cui una mappatura orizzontale di tutti i parametri primitive che saranno richieste dai vostri osservatori. Poi l'ObserverContainer potrebbe funzionare come AbstractFactory e ogni sottoclasse della ObserverContainer potrebbe essere DoThatObserverFactory e DoThisObserverFactory. La fabbrica avrebbe costruito un osservatore e assegnare una configurazione per l'osservatore a raccontarla quale parametro aspettarsi.

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);
};
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top