My favourite design pattern: The Observer
Why Observer is so cool?
Managing complexity in large code base is all about decoupling. Observer is a perfect tool to do that. The Observed
object has no idea about its observers, no dependency injection is required at all, as a result a loose coupling is achieved between two classes of objects where the only common part is the interface they agree upon.
First iteration
It’s a classic design pattern and I’m sure majority (if not all) of engineers know the details behind it, most of us implement it in a slightly different way, depending on the requirements and the code base we are working with though. Here’s my flavour of this design pattern.
|
|
This implementation would be used in a following way:
|
|
That’s how the fundamentals usually look. The Observed
object generates update()
events to notify its observers that something has happened. There’s a couple of problems with this code though, preventing it from being a generic, library implementation that could be used independently. Let’s address these.
Generic Observer type
First of all, the implementation dictates the API for Observers
. Each Observer
can only be updated using a single update
function which doesn’t take any arguments. It’s impossible to pass any extra data along with the notification. One could argue that you could change the signature to i.e.:
|
|
But that is not really a good solution. What if I want a different type
than std::string
?
Often, you’ll want to receive updates of more than one type.
Additionally, you’ll most likely want to get some information passed in
update()
as an argument to understand what has changed within the
Observed
object. To conclude, the Observer
can’t be defined upfront - it has to be a template parameter.
|
|
Since the Observer
’s API is unknown up front it’s impossible to
implement the notification functions in a generic way. This could be
solved in a simple manner by delegating the problem to the clients:
|
|
In the code above, the client defines, the Observer
interface (which
it then implements as well). Having an abstract observer interface,
allows for creation of Observed
instances with that particular
type.
Sure, this will work but it leads to a terrible amount of code
duplication. Each notify
function is roughly duplicating the same
pattern but with a slightly different update call. This problem can be
solved in two ways.
Pointer to members
Firstly, I’m gonna generalise the implementation of the notify
function. It’ll now be a function template and it’ll look like so:
|
|
Granted, it looks a bit scary but after a minute it becomes clear. I’m using a pointer to member here and a variadic template to handle function arguments which are perfectly forwarded. How to use it?
|
|
That looks good. The provided implementation is generic enough to exist independently and be reusable. Just for reference, I’m gonna show another way to implement notifications, using functors.
Functors
The implementation of notify
member function becomes simpler and more concise:
|
|
In here I expect to receive a functor taking a pointer to Observer*
type explicitly. In other words, the function signature becomes as following:
|
|
The complexity is shifted to the client side. In order to use the notify
function in its current form, functors have to be created in place:
|
|
All details and a more concise reference can be found on gitlab.