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