0

I'm trying to use the Observer pattern for some input stuff like so:

class Observer
{
public:
    virtual void notify(Subject & o)=0;
};

class Subject
{
public:
    virtual void register(Observer * o)=0;
}

I have two concrete Subjects (Mouse, Keyboard), with class specific functions that I want the concrete observer to call( getkeypress, getmousemotion etc).

Is there any way to specialise the notify function in the concrete observer class without changing the interface, or downcasting the reference? I've tried overloading the function, but obviously that doesn't work because the concrete Subjects have no knowledge of derived Observers.

Ian Young
  • 1,712
  • 1
  • 16
  • 33

2 Answers2

1

You usually don't give the observer a pure virtual notify function. Instead, your Subjects should reimplemented a "changed" function that Observer::notify calls on all its Subjects. This, you can reimplement in both Mouse and Keyboard to call the functions you want.

This does require changes to your interface as presented, because right now it's not quite right.

rubenvb
  • 74,642
  • 33
  • 187
  • 332
0

Is there any way to specialise the notify function in the concrete observer class without changing the interface, or downcasting the reference?

I don't think there is.

However, you can minimize the number of places you use dynamic_cast by using a class template that is a sub-class of Observer and making the type used to instantiate the class template to be fully aware of the derived type.

I have used that pattern effectively numerous times.

Solution 1

This uses a pattern where an observer can observer only one type of object.

#include <iostream>

class Subject;

class Observer
{
   public:
      virtual void notify(Subject & o)=0;
};

class Subject
{
   public:

      // Remove the Observer argument from the public interface.
      // Make the derived class construct the right type of observer
      // and use the registerObserverImpl function to do the work.
      // With this, client code doesn't need to know the kind of Observer
      // a sub-class of Subject uses.
      virtual void registerObserver() = 0;

      void notifyObserver()
      {
         observer->notify(*this);
      }

   protected:

      // Helper function for derived classes.
      void registerObserverImpl(Observer * o)
      {
         observer = o;
      }

   private:
      // The observer.
      Observer* observer;
};

// A class template that is responsible for performing dynamic_cast
// and passing a reference to the derived type to the concrete Observer.
template <typename RealObserver>
class TemplateObserver : public Observer
{
   using ConcreteSubject = typename RealObserver::SubjectType;
   virtual void notify(Subject& o)
   {
      // The only place you need to use dynamic_cast.
      RealObserver().notify(dynamic_cast<ConcreteSubject&>(o));
   }
};

class Mouse : public Subject
{
   public:
      virtual void registerObserver();
};

// The concrete Observer of Mouse. It doesn't need to be derived from
// Observer since TemplateObserver takes care of that.
class MouseObserver
{
   public:
      using SubjectType = Mouse;
      void notify(Mouse& m)
      {
         std::cout << "In MouseObserver::notify\n";
         // Use the Mouse anyway you want.
      }
};

void Mouse::registerObserver()
{
   registerObserverImpl(new TemplateObserver<MouseObserver>());
}

class Keyboard : public Subject
{
   public:
      virtual void registerObserver();
};

// The concrete Observer of Keyboard. It doesn't need to be derived from
// Observer since TemplateObserver takes care of that.
class KeyboardObserver
{
   public:

      using SubjectType = Keyboard;
      void notify(Keyboard& k)
      {
         std::cout << "In KeyboardObserver::notify\n";
         // Use the Keyboard anyway you want.
      }
};

void Keyboard::registerObserver()
{
   registerObserverImpl(new TemplateObserver<KeyboardObserver>());
}

int main()
{
   // Client code does not need to know about MouseObserver or
   // KeyboardObserver.

   Mouse m;
   m.registerObserver();
   m.notifyObserver();

   Keyboard k;
   k.registerObserver();
   k.notifyObserver();
}

Output

In MouseObserver::notify
In KeyboardObserver::notify

Solution 2

This uses a pattern where an observer can observer any numbers of types of objects.

#include <iostream>

class Subject;

class Observer
{
   public:
      virtual void notify(Subject & o)=0;
};

class Subject
{
   public:

      // Make the class polymorphic
      virtual ~Subject() {}

      void registerObserver(Observer * o)
      {
         observer = o;
      }
      void notifyObserver()
      {
         observer->notify(*this);
      }

   private:
      // The observer.
      Observer* observer;
};

// A class template that is responsible for performing dynamic_cast
// and passing a reference to the derived type to the concrete Observer.
template <typename RealObserver, typename RealSubject>
class TemplateObserver : public Observer
{
   virtual void notify(Subject& o)
   {
      // The only place you need to use dynamic_cast.
      RealObserver().notify(dynamic_cast<RealSubject&>(o));
   }
};

class Mouse : public Subject
{
};

// The concrete Observer of Mouse. It doesn't need to be derived from
// Observer since TemplateObserver takes care of that.
class MouseObserver
{
   public:
      using SubjectType = Mouse;
      void notify(Mouse& m)
      {
         std::cout << "In MouseObserver::notify\n";
         // Use the Mouse anyway you want.
      }
};

class Keyboard : public Subject
{
};

// The concrete Observer of Keyboard. It doesn't need to be derived from
// Observer since TemplateObserver takes care of that.
class KeyboardObserver
{
   public:

      using SubjectType = Keyboard;
      void notify(Keyboard& k)
      {
         std::cout << "In KeyboardObserver::notify\n";
         // Use the Keyboard anyway you want.
      }
};

class CombinedObserver
{
   public:

      void notify(Mouse& m)
      {
         std::cout << "In CombinedObserver::notify\n";
         // Use the Mouse anyway you want.
      }

      void notify(Keyboard& k)
      {
         std::cout << "In CombinedObserver::notify\n";
         // Use the Keyboard anyway you want.
      }
};

int main()
{
   // Client code does not need to know about MouseObserver or
   // KeyboardObserver.

   Mouse m;
   m.registerObserver(new TemplateObserver<MouseObserver, Mouse>());
   m.notifyObserver();

   Keyboard k;
   k.registerObserver(new TemplateObserver<KeyboardObserver, Keyboard>());
   k.notifyObserver();

   m.registerObserver(new TemplateObserver<CombinedObserver, Mouse>());
   m.notifyObserver();

   k.registerObserver(new TemplateObserver<CombinedObserver, Keyboard>());
   k.notifyObserver();
}

Output

In MouseObserver::notify
In KeyboardObserver::notify
In CombinedObserver::notify
In CombinedObserver::notify
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Hmm, an interesting approach, but as the concrete observer needs to interact with other objects (in this case, a camera class), I'm not sure how useful this would be. – Ian Young Mar 27 '17 at 06:11
  • @IanYoung, I suspect that won't be a problem but I won't be to able to suggest anything concrete until I see what `Camera` looks like and how you wish to use it as an observer. – R Sahu Mar 27 '17 at 19:18
  • @ R Sahu That's not exactly what I meant, but I can see the confusion. What I meant, to clarify, is that I have a module(observer) which acts as an mediator between the controller (subject, in this case a keyboard, or mouse), and a camera. All the mediator does is query for mouse motion and/or key presses, then calls movement/rotation functions on the camera. – Ian Young Mar 28 '17 at 21:02
  • @IanYoung, Sounds reasonable. You can create two observers instead of one -- one for keyboard and one for mouse. Together they can perform the role of `module`. Making `module` an observer of both keyboard and mouse will be a problem in my suggestion. – R Sahu Mar 28 '17 at 21:47
  • @R Sahu Sounds good so far, but I find myself back at the original problem, which is when `observer->notify(*this);` is called, by the keyboard, or mouse. I still need to downcast each subject to a `Keyboard`, or `Mouse` object in order to query them, and it is this downcast I am trying to avoid. – Ian Young Mar 28 '17 at 22:00
  • @IanYoung, I don't think you can avoid the `downcast`. What my answer suggests is that it is localized to one function. From then on, you have a reference to the derived object to work with. – R Sahu Mar 28 '17 at 22:06
  • @R Sahu fair enough. I'll see what I can do with that. – Ian Young Mar 28 '17 at 22:20