Observer design pattern in Crystal language
Being a developer you probably have heart about Observer design pattern. Perhaps, you have even used it in your complex system with subscribers and notifications. This article is about how to implement Observer pattern in Crystal language and what are the common features of this language we can use there to tune it. If you are not familiar with Observer design pattern I will suggest you to read this article before.
Say, we are developing a game where two units (fighters) fight with each other. Each fighter has a name and amount of health. Fighter can make a damage to other fighter. If fighter’s health is 0 - fighter is dead. Our another requirement is to update stats when fighter is damaged to let player know current health of his fighter. And the last thing we would like to notice is a notification about death. We want to congratulate a winner or do some other actions.
Of course, first that comes to the mind is a popular Mortal Kombat video game and I will suggest you to do not hesitate to imagine it in that way. Actually, we will not write a new game or something like that, we just need a concept.
Next are going to implement this in high level manner.
Observable (or Subject)
At first we need to develop a Fighter
class. Here is how it might look:
It meets our fighter’s requirements: fighter has a name, health and can be damaged by another fighter.
The idea of an Observer pattern is to notify subscribers when subject’s state changes. Subject in our case is represented by Fighter
class. But it need to be able to notify observers when fighter is damaged. This is where Observable
modules comes (the most interesting part):
We want to emphasize few points:
This is a module (not a class) because we want to include all this functionality in our
Fighter
class and leave a way to inheritFighter
from another class in future. Crystal does not support multiple inheritance thus we can use the same approach as used by Ruby’s built-in Observer.We used generics (type
T
) to define type of an observer. This makes our subject more general and it is not coupled with concrete class.We initialize a list of observers on demand (another idea from Ruby’s built-in Observer). That’s why our list of observers at some point of time may be
nil
and that’s why we need to usetry
andnot_nil!
methods to ensure that we do not call observer’s methods onnil
object and prevent compile errors.
We can’t include Observable
module in Fighter
class currently because we do not have an Observer
. In other words, we do not know a type of T
. So, let’s create few observers.
Observer
Here is how an interface for our Observer might look:
Then we can implement concrete observers (Stats
and DieAction
):
The last thing we need to do is to include Observable
module into our Fighter
class:
Notice how we define a type of our Observable
module when we include it.
Wrapup
We are ready to run a simple example:
Crystal’s type system is very flexible. It allows us to use generics, helps to prevent runtime errors and gives ability to write concise and easy to read code. In our implementation of Observer pattern we may found examples of all mentioned points.
Source code for example used in this article you may find in Crystal Patterns Github repo.
Leave a Comment