Как передать данные «универсальному» наблюдателю? В качестве аргументов или в виде единой структуры? - PullRequest
2 голосов
/ 30 августа 2010

Я занят добавлением универсального механизма наблюдателя в унаследованное приложение 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;
   };

ВызовНаблюдатель не может быть обобщен, так как аргументы различны для каждого типа наблюдателя.

Альтернативным способом было бы «упаковать» все аргументы в один аргумент, например:

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

Нетw, гораздо больше логики может быть централизованно добавлено к этому ObserverContainer, включая вызов всех наблюдателей.«Инициатору» вызова нужно только создать и заполнить структуру уведомлений.

Классы, которые хотят наследовать от нескольких видов наблюдателей, должны сделать это так:

class MyObserver : public IObserver<NotifyThis>, public IObserver<NotifyThat>
   {
   ...
   };
* 1028Какой из этих подходов (наблюдатели с несколькими явными аргументами или с одним структурным аргументом) кажется лучшим?Есть ли какие-либо преимущества или недостатки у любого из этих подходов?

EDIT : Я посмотрел немного дальше к альтернативным подходам, и подход Slot / Signal кажется еще одним хорошим кандидатом.Есть ли какие-либо важные недостатки в слоте / сигнале, о которых мне следует знать?

Ответы [ 5 ]

2 голосов
/ 30 августа 2010

Почему бы просто не сделать:

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;
};
1 голос
/ 30 августа 2010

Вы смотрели в Boost.Signals?Лучше, чем переопределить колесо.

Что касается параметров: вызов наблюдателя / слота в принципе должен быть таким же, как если бы вы вызывали обычную функцию.Большинство реализаций SignalSlots допускают несколько параметров, поэтому используйте его.И, пожалуйста, используйте разные сигналы для разных типов наблюдателей, тогда нет необходимости передавать данные в Вариантах.

Два недостатка наблюдателя-паттерна / сигнальных слотов, которые я видел:
1) Выполнение программы затрудненоили даже невозможно понять, глядя только на источник.
2) Сильно динамичные программы с большим количеством Observers / SignalSlots могут столкнуться с «delete this»

Все в стороне, мне больше нравятся Observers / SignalSlots, чем подкласси, следовательно, высокая связь.

1 голос
/ 30 августа 2010

Я не думаю, что любой из ваших подходов будет соответствовать вашим требованиям как есть. Однако небольшая модификация с использованием 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 для обработки путаницы указателей.

1 голос
/ 30 августа 2010

Дизайн с аргументом struct определенно лучше, так как он позволяет писать общий код в ObserverContainer. Как правило, это хорошая практика проектирования - заменять длинные списки аргументов объектами, которые инкапсулируют аргументы, и это хороший пример выигрыша. Создавая более общую абстракцию для вашего метода notify (со структурой, которую вы определяете notify как метод, который принимает кусок "данных", тогда как со списком аргументов вы определяете метод, который принимает два числа) Вы позволяете себе писать общий код, который использует метод и не должен заботиться о точном составе переданных фрагментов данных.

0 голосов
/ 30 августа 2010

Я бы не понял правильный синтаксис, поэтому я просто перечислю объявления, чтобы проиллюстрировать структуры.Можно сделать общий Обозреватель, ожидающий параметр, который либо подклассифицируется к конкретным формам ваших требуемых параметров, либо имеет структуру, включающую горизонтальное отображение всех примитивных параметров, которые потребуются вашими Наблюдателями.Тогда 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);
};
...