Design Patterns: Subscribe to change with the Observer pattern
Introduction
This is the third instalment of my series on design patterns in software. For the introduction to the whole series, definitely check out Design Patterns: Introduction with Singletons. This initial post describes general information about design patterns as well. The complexity of the observer pattern is slightly higher compared to the previous strategy pattern, but all design patterns should be understandable.
As a side note: Don't forget that you don't have to use these patterns, or use them in this exact way. My goal with these posts is to let people think about better code design, to make it easier to maintain and extend later. You might be actually already applying the patterns in some form or another.
Observer Pattern
The observer pattern helps you notify a collection of objects of some change, without having to know what those objects are. The object keeping track of this list of dependants (observers), is called a subject.
The purpose of it is to make it easy and clear to notify certain elements in a package of change or ask them to update, whatever that means for that set of observers. An observer can do whatever it is required to do when it receives an update notification - it shouldn't matter to the subject (loosely coupled).
This also lets you easily define all sorts of behaviour that should be executed when something changes in the subject, therefore this pattern is another behavioural pattern.
An example can be found in many MVC frameworks, where the view merely knows some data or has some data bindings. The view can be considered as an observer, and will update when requested. Many UI frameworks actually use this observer, even for distributed event handling.
As per usual, a simple UML diagram showing the outline of this pattern:
But... why?
Once again, you're applying the open/closed principle. The way you are defining behaviour that should be executed when something changes in the subject, is completely dynamic. You can put as many observers in a subject as you like. You can extend the various observers easily by simply adding a class that implements IObserver.
I talked a little more about the open/closed principle in the Strategy pattern post.
Dynamic behaviour
It's more dynamic behaviour, like in the strategy pattern, it will be easier to define behaviour and dynamically assign it to an object (the subject). You can even use the same behaviour for multiple subjects. Another aspect of this pattern is that you can not only attach observers to the subject, but you should be able to detach them as well.
Extension
The same as with the strategy pattern, extension is a big plus here. Adding new observers to a set is easy. You simply create a new class that implements the corresponding observer interface, and you're done. The subject's collection of observers is completely dynamic, so more can be added at any time (run time).
If you look at the class diagram, you see that adding a class that implements IObserver will simply allow you to add an observer to the system. You will then only have to attach it to the subject by calling AttachObserver, and it will be automatically notified on change.
Imagine
Only imagine having to maintain dynamic behaviour like this without loosely coupled code. You could then add all this behaviour as a bunch of methods to the subject class, and call each of them in the 'NotifyObservers' method. Each new piece of logic that has to be executed when something changes has to be added in several places. That's going to go wrong at some point and the code will look like a mess.
I remember seeing something like this in a system I had to maintain:
void DSW::SetAchievementAchieved(Achievement_t *achievement) {
this->engine->SetAchievementState(achievement->m_rgchName, true);
this->engine->SaveChanges();
this->achDialog->Update(achievement);
this->goalDialog->Update(achievement);
this->progress->Update(achievement);
}
Let's be honest, that's fairly useless... Those three Update rules could be converted to the Observer pattern.
Using patterns that help you to keep your code loosely coupled will in turn help you to make the code look clean and clear. Even your own repeated methods of coding are patterns you should remember!
Example in TypeScript
This example is based on the same render system I've used in previous posts. There is a RenderSystem, an IRenderable interface and an x amount of IRenderables in that render system which will be automatically stepped and rendered.
All objects that are added to the RenderLoop system via AddRenderable have to implement the IRenderable interface. There is also the ISubject interface for the subject in the Observer pattern. Then there obviously also is the IObserver interface, which is the stalker that stalks the subject. I chose to name them ISubject and IObserver to better demonstrate which part of the pattern they represent, but in real life, you can name them anything that is logical to you.
Check out this pen.
What's next?
This post cost me a little bit more effort than usual, a fever was bothering me. I love feedback, so let me know if I need to change anything or adjust the format. I want this to be as educational and understandable as it can be! I'll write about the Decorator pattern next, which can be a complicated one, but it decorates things!