Observer Pattern — Part 2 of a design patterns book study

Graham Holtslander
Vendasta
Published in
4 min readMar 25, 2020
Photo by Felix Wegerer on Unsplash

This is a pattern I am much more familiar with, as I have a stronger frontend skill set. In this chapter, we implemented a weather data collection station. Let’s have a look at some code. In our weather station example, it’s laid out like this: there is a weather station class that gets updated weather data intermittently. When it does so, it updates three (for now) weather displays. He re’s the first go at it, imperative-style:

type WeatherStation interface {
UpdateWeather(temp, humidity, pressure float64)
}
type weatherStation struct {
forecast Display
current Display
statistics Display
}
func NewWeatherStation(forecast, current, statistics Display) WeatherStation {
return &weatherStation{
forecast: forecast,
current: current,
statistics: statistics,
}
}
func (w *weatherStation) UpdateWeather(temp, humidity, pressure float64) {
// Update each of our displays!
w.forecast.Update(temp, humidity, pressure)
w.current.Update(temp, humidity, pressure)
w.statistics.Update(temp, humidity, pressure)
}
// Stitch it all together in a main function (not shown)

This code works, but it is fragile and tightly coupled to the three specific displays we have available to us right now. How can we use object-oriented principles and patterns to our advantage here?

One of the places they suggest you try and apply design patterns is any spots in the code that are likely to change frequently. One of the principles they talk about in this chapter is the *Open-Closed principle*: Our code should be open for extension but closed for modification. This is a principle that I’ve longed to understand, and now it finally feels like I do after reading a few of these chapters! In our weather station as it stands today, every time we add a new weather display we will need to modify the weather data station code! Not very “closed” at all.

Enter: the Observer pattern. Rather than adding code to our “update weather” function in the weather data station, we give displays (observers) the ability to subscribe to updates from the weather station (the observable). Therefore when we want to add a new weather display at some point in the future we don’t have to modify the weather data station code (it’s closed for modification, after all), we just subscribe through the interface we already have available. This decouples the display from the station and vice versa. Here’s what some observer and observable interfaces could look like for our weather station scenario:

type Observer interface {
Update(temp, humidity, pressure float64)
}
type Observable interface {
RegisterSubscriber(o Observer)
RemoveSubscriber(toRemove Observer)
NotifySubscribers(temp, hum, pressure float64)
}
type weatherData struct {
temp, humidity, pressure float64
observers []Observer
}
// RegisterSubscriber adds an observer to the update queue
func (w *weatherData) RegisterSubscriber(o Observer) {
w.observers = append(w.observers, o)
}
// RemoveSubscriber removes an observer from the update queue
func (w *weatherData) RemoveSubscriber(toRemove Observer) {
for i, o := range w.observers {
if o == toRemove {
// Slice out the observer to be removed
w.observers = append(w.observers[:i], w.observers[i+1:]…)
break
}
}
}
func (w *weatherData) NotifySubscribers(temp, hum, pres float64) {
w.temp = temp
w.humidity = hum
w.pressure = pres
for _, o := range w.observers {
o.Update(w.temp, w.humidity, w.pressure)
}
}

Then it’s a simple matter of calling NotifySubscribers() whenever you “get a new update” in the WeatherData implementation. Here’s a possible
main function implementation for the station:

w := weatherdata.New()
curr := &displays.CurrentConditions{}
fore := &displays.ForecastDisplay{}
stat := &displays.StatisticsDisplay{}
w.RegisterSubscriber(curr)
w.RegisterSubscriber(fore)
w.RegisterSubscriber(stat)
w.NotifySubscribers(23.4, 90, 32)
curr.Display() // See the current temperature and such
w.NotifySubscribers(20.3, 80, 40)
curr.Display() // See the new current temperature and such

A real-life example of the observer pattern is one I used often when working in more vanilla Javascript — addEventListener. You add a callback that should be run on a particular event on pretty well anything in Javascript (classic), but most often DOM nodes. Something like myButton.addEventListener(‘onclick’, function() { console.log(‘hello!’) })where myButton is a DOM node you’d grabbed earlier. Any time the onclick event happens on myButton, your anonymous function will fire and myButton is none the wiser. In the Angular/RxJS world, there is a much stronger analogue with actual Observables that you can subscribe to.

A distributed version of the observer pattern is implemented by PubSub. You have observables (topics and, more specifically, subscriptions to topics) and observers — client code that subscribes to a topic and does something whenever there’s a new message.

This chapter was a good one for boosting my confidence in reading the book as I understood it fairly well.

--

--