3

私は汎用オブザーバー メカニズムを従来の C++ アプリケーションに追加するのに忙しくしています (Visual Studio 2010 を使用していますが、.Net は使用していないため、.Net デリゲートは問題外です)。

設計では、アプリケーション固有の部分を一般的なオブザーバー メカニズムからできるだけ分離したいと考えています。

オブザーバーを実装する最も論理的な方法は、次のようになります。

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

オブザーバーのタイプ (IDoThisObserver、IDoThatObserver、...) ごとに、メソッド (handleDoThis、handleDoThat) の引数は異なります。

次のように、オブザーバーを格納する一般的な方法に残っているもの:

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

オブザーバーの呼び出しは、オブザーバーの種類ごとに引数が異なるため、一般化できません。

別の方法は、次のように、すべての引数を 1 つの引数に「パック」することです。

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

次に、次のように、より一般的なオブザーバーを定義します。

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

そして、これらのオブザーバーのコレクションは次のようになります。

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

これで、すべてのオブザーバーの呼び出しなど、さらに多くのロジックをこの ObserverContainer に一元的に追加できます。呼び出しの「開始者」は、通知構造を作成して入力するだけで済みます。

複数の種類のオブザーバーから継承したいクラスは、次のようにする必要があります。

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

これらのアプローチ (複数の明示的な引数を持つオブザーバーまたは 1 つの構造体引数を持つオブザーバー) のどれが最適だと思われますか? これらのアプローチのいずれかに利点または欠点はありますか?

編集:私は代替アプローチをもう少し調べました.Slot/Signalアプローチは別の良い候補のようです. Slot/Signal に知っておくべき重要な欠点はありますか?

4

5 に答える 5

2

なぜそうしないのですか:

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

?

次に、次のようになります。

class ObserverContainer
{
   public:
      void addObserver (IObserver* t) {m_observers.push_back(t);}
   private:
      std::list<IObserver*> m_observers;
};
于 2010-08-30T15:43:16.660 に答える
1

あなたのアプローチのどちらも、そのままではあなたの要件に合わないと思います。ただし、データセットを含む DataCarrier を使用して少し変更すると、すべてのオブザーバーに渡され、各オブザーバーは何を読み取るべきかを知ることができます。以下のサンプルコードはそれをクリアするかもしれません(私はコンパイルしていないことに注意してください)

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

このようにして、新しい構造を追加する場合、列挙型のみを変更する必要があります。また、boost::shared_ptr を使用してポインターの混乱を処理することもできます。

于 2010-08-30T18:37:23.740 に答える
1

Boost.Signals を調べましたか? ホイールを再実装するよりも優れています。

パラメーターについて: オブザーバー/スロットの呼び出しは、通常の関数を呼び出す場合と概念的に同じにする必要があります。ほとんどの SignalSlots-Implementation は複数のパラメーターを許可するので、それを使用してください。また、オブザーバーの種類ごとに異なるシグナルを使用してください。バリアントでデータを渡す必要はありません。

私が見た Observer-Pattern/SignalSlots の 2 つの欠点:
1) ソースだけを見ただけでは、プログラムの流れを理解するのが難しいか、不可能ですらあります。
2) 多くの Observers/SignalSlots を持つ非常に動的なプログラムでは、「delete this」が発生する場合があります。

すべてはさておき、私は Observers/SignalSlots がサブクラス化よりも好きなので、結合度が高くなります。

于 2010-08-30T18:37:24.170 に答える
1

struct引数を使用した設計は、一般的なコードを に記述できるため、明らかに優れていますObserverContainer。長い引数リストを引数をカプセル化するオブジェクトに置き換えることは、一般的に良い設計方法であり、これは見返りの良い例です。メソッドのより一般的な抽象化を作成することによりnotify(構造体を使用してnotify「データ」のチャンクを受け取るメソッドとして定義し、引数リストを使用して2つの数値を受け取るメソッドを定義します)、ジェネリックを書くことができますメソッドを使用するコードであり、渡されたデータのチャンクの正確な構成に関与する必要はありません。

于 2010-08-30T16:26:41.517 に答える
0

構文を正しく理解できないので、構造を説明するために宣言をリストします。ジェネリック オブザーバーは、必要なパラメーターの特定の形式にサブクラス化されたパラメーター、またはオブザーバーが必要とするすべてのプリミティブ パラメーターの水平マッピングを含む構造体であるパラメーターを予期するように作成できます。次に、ObserverContainer をAbstractFactoryとして機能させ、ObserverContainerの各サブクラスを DoThatObserverFactory および DoThisObserverFactory にすることができます。ファクトリはオブザーバーを構築し、構成をオブザーバーに割り当てて、期待するパラメーターを通知します。

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);
};
于 2010-08-30T17:58:40.377 に答える