In a C++ code base I’ve been working on, they have a bunch of instances of the observer pattern, but it’s a little different from the classical pattern. In the classic, the Observer is a specific interface with specific method(s) that are called when the Observable changes or has new data.
Observable Observer
register( Observer* ) update( data1, data2 ... )
In our code base, we use a template class that takes the number and type of arguments to the update() method as parameters, and allows you to specify any pointer-to-method as the recipient of the data.
Observable
register( Functor<data1, data2> )
Is this an improvement over the original pattern? Here’s what I think are the pros of each approach:
Classic:
- clarity – the interface marks which classes are recipients of observable data
- less code – the boiler plate and ugly template syntax to create the functor are somewhat onerous
Variant:
- flexibility – I can make any object in my system receive the update (if it has the right method signature) without having to change the original object
As a another option, do I have it all wrong that this is the Observer pattern in practice?
1
Your idea was already invented 10 years ago by Herb Sutter. See this article: http://www.drdobbs.com/cpp/generalizing-observer/184403873, it contains a full explanation.
So the answer is
-
yes, it is an improvement over the original GOF variant of the pattern
-
no, it is not better than every other known implementation of the Observer pattern
Also note that what you suggested here is the “standard observer implementation” in functional languages or languages with functional elements where events/event sinks are typically implemented in a comparable way (for example, in C# using delegates).
2
Just to check, you do know that your Functor is just std::function
, right?
Secondly, it absolutely is better. You can register lambdas, function objects, etc, so it’s far more flexible than requiring derivation, and you don’t have to define ten billion interfaces, all of which are dupes of each other.
4
I implemented a lot of variants of Observer pattern in C++ and one thing I learnt is that there is no generic way to do it unfortunately, a bit like trying to make a unique compiler.
It depends on what feature you need for the specific usage. One important feature that is or not required is disconnection.
Disconnecting an observer requires that we can identify the observer to disconnect. There are several ways to do this. Boost.Signals(2) do this by providing an object that is basically a handle to the connection and can be used to disconnect.
Your first example allow disconnection, as we can identify an observer by comparing pointers; your second example don’t allow disconnection if Functor is std::function<> as it can’t be compared (there is explicitly no == operator).
If your Functor<> can be compared correctly, then it is possible to disconnect. If it’s not, then depending on the use case, it can be or not a problem.
Also, note that an Observer class can be more helpful in defining a set of messages that Must be all handled by the observer.
Really, it depends on the use case.