Brian
Brian

Reputation: 255

Observer design pattern interface contract design issue

I'm working on implementing an observer design pattern with a notification object that I can change to suit various observed classes.

Here is the observer framework:

notify.h:

class INotification //Notification container
{
    public:
        virtual ~INotification()=0;
};

inline INotification::~INotification() {}

class IObserver
{
    public:
        virtual ~IObserver();
        virtual void update(INotification*)=0;
};

inline IObserver::~IObserver() {}

class ISubject
{
    public:
        virtual ~ISubject();
        virtual void attach(IObserver*)=0;
        virtual void detach(IObserver*)=0;
        virtual void notify()=0; //Note: observer deletes notifications
};

inline ISubject::~ISubject() {}

I am implementing a timer class that I want other classes to observe for timer events:

timer.h:

class ITimerObserver;

class ITimer : public ISubject
{
    public:
        virtual ~ITimer();
        virtual void setInterval(const unsigned int,const unsigned int)=0; //Seconds, Microseconds
        virtual void run()=0; //Check for triggering
        virtual const timeval& now()=0;
        virtual bool isItTime(const timeval&,const timeval&)=0;
};

inline ITimer::~ITimer() {}

class CTimer : public ITimer
{
    protected:
        std::vector<IObserver*> observers;
        timeval interval; //How often we are triggering
        timeval lastTrigger; //When we were last triggered
        timeval current; //Our current time
    private:
        virtual ~CTimer();
        virtual void attach(IObserver*);
        virtual void detach(IObserver*);
        virtual void notify();
        virtual void setInterval(const unsigned int,const unsigned int); //Seconds, Microseconds
        virtual void run(); //Check for triggering
        virtual const timeval& now();
        virtual bool isItTime(const timeval&,const timeval&);
};

class ITimerNotification : public INotification
{
    public:
        virtual ~ITimerNotification();
        virtual const timeval& getTime()=0;
};

inline ITimerNotification::~ITimerNotification() {}

class CTimerNotification : public ITimerNotification
{
    public:
        CTimerNotification(const timeval& t)
        {
            time = t;
        }
    protected:
        timeval time;
    private:
        virtual ~CTimerNotification();
        virtual const timeval& getTime()
        {
            return time;
        }
};

class ITimerObserver : public IObserver
{
    public:
        virtual void update(ITimerNotification*)=0;
};

So I want to be able to pass a more specific Notification object (A TimerNotification) whenever a timer event happens, so that I can call a specific update() function on the observer, so I made a new Observer class (ITimerObserver).

Here is the function that notifies the observer on a timer event:

void CTimer::notify()
{
    std::vector<IObserver*>::iterator it;
    for(it=observers.begin();it!=observers.end();++it)
    {
        ITimerNotification* notification = new CTimerNotification(now());
        (*it)->update(notification);
    }
}

Here is the actual observer itself:

class TestObserver : public ITimerObserver
{
    public:
        virtual void update(INotification* note)
        {
            std::cout<<"???: TestObserver: update()!\n";
        }
        virtual void update(ITimerNotification* note)
        {
            std::cout<< note->getTime().tv_sec << "." << note->getTime().tv_usec <<": TestObserver: update()!\n";
        }
};

When run, the program runs the interface method, void update(INotification) instead of the more specific ITimerNotification as I would expect. The trouble is, how do I get the CTimer class to know about the TimerObserver without breaking the interface contract that says it only takes a Base Observer pointer?

Upvotes: 1

Views: 777

Answers (1)

Keith
Keith

Reputation: 6834

To answer the first part of the question:

 ITimerNotification* notification = new CTimerNotification(now());
 (*it)->update(notification);

This code passes notification to an IObserver::update method, of which there is only one:

virtual void update(INotification*)=0;

Hence the call to that virtual method in TestObserver.

For the second part, you need to recognise that you wish the call to in a sense be virtual on two types, the observer and notification. This is known as double dispatch, and requires some work in C++.

The key point to understand is the static and run-time binding of the function calls. At the calling point, for example:

(*it)->update(notification);

the compiler can only do static resolution of the names of functions. Where these are virtual calls, there will be run-time binding to the actual method based on the type of object the method is being called on (not the parameter). So in order to do double dispatch through built-in mechanisms, you need to call a virtual method on both the notification and the observer.

See for example:

Multiple dispatch in C++

How does double dispatch work in Visitor pattern?

This topic is also covered in great detail in one of the Meyer's books (I forget which.)

Upvotes: 1

Related Questions