Должен ли я использовать динамическое приведение в шаблоне предметного наблюдателя с шаблонами - PullRequest
1 голос
/ 20 октября 2010

Ссылаясь на статью Реализация шаблона субъекта / наблюдателя с шаблонами

template <class T>
class Observer
   {
   public:
      Observer() {}
      virtual ~Observer() {}
      virtual void update(T *subject)= 0;
   };

template <class T>
class Subject
   {
   public:
      Subject() {}
      virtual ~Subject() {}
      void attach (Observer<T> &observer)
         {
         m_observers.push_back(&observer);
         }
      void notify ()
         {
         std::vector<Observer<T> *>::iterator it;
         for (it=m_observers.begin();it!=m_observers.end();it++) 
              (*it)->update(static_cast<T *>(this));
         }
   private:
      std::vector<Observer<T> *> m_observers;
   };

Мне было интересно, вместо static_cast, мне использовать dynamic_cast?

Это потому, что если я использую static_cast, я получу ошибку компиляции в следующем случае.

class Zoo : public Observer<Animal> {
public:
    Zoo() {
        animal = new Bird();
        animal->attach(this);
    }

    virtual ~Zoo() {
    delete animal;
    }

    virtual void update(Animal* subject) {
    }

    Animal* animal;
}

// If using static_cast, compilation error will happen here.
class Bird : public Animal, public Subject<Animal> {
public:
    virtual ~Bird() {
    }
}

Есть ли побочный эффект от использования dynamic_cast?

Ответы [ 2 ]

4 голосов
/ 20 октября 2010

Лучшее, конечно, было бы не иметь на всех.Вы можете изменить свою notify() функцию так, чтобы она принимала правильный аргумент:

  void notify (T* obj)
     {
     std::vector<Observer<T> *>::iterator it;
     for (it=m_observers.begin();it!=m_observers.end();it++) 
          (*it)->update(obj);
     }

Теперь производные классы могут передавать нужный объект (this, если это необходимо) без базового класса, которому нужно знать отношениепроизводных классов до T.


Если смотреть на ваш код как есть, то static_cast зависит от того факта, что все, что происходит от Observer, будет также извлекаться из того, что оно передает в качестве аргумента шаблона.Я думаю, что если это не сработает, оно будет перехвачено во время компиляции, потому что вы не можете static_cast с this до T*.

Однако ваш код очень близок к шаблону, известному как шаблон любопытно повторяющегося шаблона.Чтобы он идеально подходил, передайте тип производного класса в Observer:

class Bird : public Subject<Bird> // note the template argument

Теперь вам больше не нужно наследовать от Observer s T и от того, кто на него смотрит (надеюсь) распознает шаблон и легче понимает код.

2 голосов
/ 20 октября 2010

На тангенциальной линии рассуждений вы можете использовать для этого существующие библиотеки, например boost::signal, которые позволяют вам определять событие и подключать слушателей (наблюдателей) к этому событию.

// Forgive the lack of encapsulation and const-correctness to keep the example simple:
struct Animal {
   boost::signal< void ( Animal& )> signal_update;
   std::string name;
};
class Bird : public Animal {
public:
   void rename( std::string const & n ) { 
      name = n;
      signal_update(*this);
   }
};
class Zoo
{
public:
   Zoo() : bird() {
      bird.signal_update.connect( boost::bind( &Zoo::an_animal_changed, this, _1 ) );
   }
   void an_animal_changed( Animal & a ) {
      std::cout << "New name is " << a.name << std::endl;
   }
   Bird bird;
};
int main() {
   Zoo zoo;
   zoo.bird.rename( "Tweety" ); // New name is Tweety
}

Преимущество (и недостаток) этого решения заключается в том, что оно ослабляет связь между наблюдателем и субъектом. Это означает, что вы не можете обеспечить, чтобы только Zoo s могли наблюдать за животными или чтобы метод, используемый для наблюдения , имел конкретную подпись / имя. В то же время это преимущество, если ваш Animal не знает или не заботится о том, кто наблюдает:

class Scientist {
public:
   Scientist( Zoo & zoo )
   {
      zoo.bird.signal_update.connect( boost::bind( &Scientist::study, this, _1 ) );
   }
   void study( Animal & a ) {
      std::cout << "Interesting specimen this " << a.name << std::endl;
   }
};
int main() {
   Zoo zoo;
   Scientist pete(zoo);
   zoo.bird.rename( "Tweety" ); // New name is: Tweety
                                // Interesting specimen this Tweety
}

Обратите внимание, что как тип, так и имя функции могут быть адаптированы с помощью boost::bind. Если ученый работает в двух зоопарках, он даже может быть уведомлен о том, к какому зоопарку принадлежит животное, которому принадлежит смена:

// [skipped]: added a name to the zoo
void Scientist::work_at( Zoo & zoo ) {
   zoo.bird.signal_update.connect( boost::bind( &Scientist::study, this, _1, zoo.name ) );
}
// updated signature:
void Scientist::study( Animal & a, std::string const & zoo_name )
{
   std::cout << "Interesting animal " << a.name << " in zoo " << zoo_name << std::endl;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...