First of all, I would like to apologize for having not worked on Banshee at all for at least a week. All the comments on the last post have been making me feel very bad, so I will get back on that soon. I’m frantically looking for a job, so I haven’t had enough time to focus on Banshee.

If you’ve ever used Java for GUI development, there’s a good chance you have used the Observer/Observable classes supplied by the standard library. They are Java’s implementation of the Model-View-Controller design pattern. The implementation is very simple but very handy nonetheless. It allows you to have an Observable object which contains some data and a whole slew of observers itching to use the data for some purpose. In a graphical app, observers will most likely be the classes that deal with presenting the data to the user. This setup ensures there is a regulated way for the flow of information. Whenever the model changes, all the views will receive a message that they need to redo their work of presenting the data to the user.

Lately, I have been learning some GTK+ programming using C++. The result of this exercise has been a Tetris game which allows you to play head-to-head against the computer, though I would recommend you play on your own. Tetris is one of those games where computers have the upper hand. Anyway, as I was happily using C++ , which is my language of choice, I noticed that I was in need of the aforementioned simple Java construct. A quick look on the Interwebs showed there wasn’t really a standard way to go about this. That’s why I decided to implement it myself.

While this project shouldn’t have required that much work, I made it a lot harder than it should have been. After all, it was all in the spirit of learning. In Java, the observer is an interface which supplies one method:

update(Observable o, Object arg)

The method gets called whenever the model changes. It supplies a pointer to the model and an arbitrary argument. The problem I have with this design is twofold but stems from one common grievance: weak typing. Firstly, the argument is the most generic type possible, so you have to cast it to something to use it. Secondly, the observable will most likely be a subclass of Observable which means that if you want to use some functionality of the model that is specific to your subclass, you have to cast the Observable argument as well. This surely defeats the purpose of Java’s strength as a strongly typed language. I am one of those people who loves static typing – catch all the mistakes at compile-time, not at runtime in some obscure code path. Luckily, I was able to do better with C++.

I had a simple set of requirements:

  1. The Observable/Observer classes should work exactly the same as those in Java.
  2. The implementation should be strongly typed, especially with regard to the argument.

Unfortunately, I didn’t keep the intermediate versions of the current implementation, so I’m just going to paste the final versions in here and discuss the reasoning behind the design. I apologize for the code formatting. I battled with it for a while and then just gave up. I have better things to do. I’ll start with the Observer because it is very short and simple.

template <class DerivedObserver,
class DerivedObservable,
typename Argument>
class Observer : boost::noncopyable
{
// Befriending Observable allows it to call the updateObserver method.
// No one else can.
template <class DerivedObserver,
class DerivedObservable,
typename Argument>
friend class Observable;

void updateObserver(DerivedObservable* o,
Argument arg)
{
static_cast<derivedObserver*>(this)->update(o, arg);
}
};

This is the basic contract that the Observer should satisfy. There are two things of note here. I declare the Observable class as a friend, so that it can call the updateObserver method, which is private, whenever there are changes to the model. Secondly, I chose to make the updateObserver method non-virtual. I decided to use static polymorphism largely to try it out in the spirit of learning. I don’t think the overhead of a virtual method call would be that major when the model doesn’t change a million times per second. Oh well – I went with this. This works by supplying an update method in any subclass of Observer. The Observer’s updateObserver method will call forward the call to that method. This is completely statically typed which is quite nice. It means that in any MVC I create, the view will know exactly what the model’s type is when the update call is made. Of course, the argument is also strongly typed. When I create my own subclasses of Observer and Observable, I can specify what that argument must look like. This is all accomplished through those marvelous, if a bit scary, tools called templates. Now we turn to the implementation of the Observable class.

template <class DerivedObserver,
class DerivedObservable,
typename Argument>
class Observable : boost::noncopyable
{
public:
Observable() : observers(), numObservers(0) { }

const int countObservers() const {
return numObservers;
}

void addObserver(DerivedObserver *o)
{
observers.push_back(o);
++numObservers;
}

void deleteObserver(DerivedObserver o)
{
observers.remove(o);
--numObservers;
}

void deleteObservers()
{
observers.clear();
numObservers = 0;
}

void notifyObservers(Argument arg)
{
for (ObserversContainer::const_iterator i = observers.begin();
i != observers.end(); ++i)
{
(*i)->updateObserver(static_cast<derivedObservable*>(this), arg);
}
}

private:
typedef std::list<derivedObserver*> ObserversContainer;
ObserversContainer observers;
int numObservers;
};

Not much to see here. Really straightforward. Hmmm, and I just noticed that the deleteObserver implementation is a bit flaky. What happens if the particular observer doesn’t exist in the list? I’ll fix that. This is still a work in progress after all.

The only thing of note is that I decided to go with pointers to observers rather than shared_ptr’s like I originally wanted to. Unfortunately, gtkmm doesn’t play nicely with them and that was causing me some headaches. I’ll be getting back to this issue.

Well, it turns out the code really isn’t that great, having talked about it a little bit. I still thought it was worth talking about if only to understand it better myself.

Post a Comment

*
*