In software design, the Observer pattern is a behavioral design pattern where an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. The Observer pattern is used to create a one-to-many dependency between objects where if one object changes state, all its dependents are notified.
Basic Implementation of Observer Pattern
Let's start with a basic implementation of the Observer pattern in Go. We'll create an example of a simple weather station that observers can listen to.
package main
import "fmt"
// The Subject interface that maintains the list of observers.
type Subject interface {
RegisterObserver(o Observer)
RemoveObserver(o Observer)
NotifyObservers()
}
// The Observer interface that should be implemented by observers.
type Observer interface {
Update(temp, humidity, pressure float64)
}
// WeatherData holds data and the observers.
type WeatherData struct {
temperature, humidity, pressure float64
observers []Observer
}
// RegisterObserver adds an observer to the list.
func (wd *WeatherData) RegisterObserver(o Observer) {
wd.observers = append(wd.observers, o)
}
// RemoveObserver removes an observer from the list.
func (wd *WeatherData) RemoveObserver(o Observer) {
for i, observer := range wd.observers {
if observer == o {
wd.observers = append(wd.observers[:i], wd.observers[i+1:]...)
}
}
}
// NotifyObservers calls the Update method of each observer.
func (wd *WeatherData) NotifyObservers() {
for _, observer := range wd.observers {
observer.Update(wd.temperature, wd.humidity, wd.pressure)
}
}Intermediate Implementation with Concrete Observers
Next, let us create concrete observers that will react to changes in weather data. We can implement multiple observers with different responsibilities.
// CurrentConditionsDisplay implements the Observer interface.
type CurrentConditionsDisplay struct {
temperature, humidity float64
}
func (cc *CurrentConditionsDisplay) Update(temp, humidity, pressure float64) {
cc.temperature = temp
cc.humidity = humidity
fmt.Printf("Current conditions: %.2fF degrees and %.2f%% humidity\n", temp, humidity)
}
// StatisticsDisplay implements the Observer interface.
type StatisticsDisplay struct {
maxTemp, minTemp, tempSum float64
numReadings int
}
func (sd *StatisticsDisplay) Update(temp, humidity, pressure float64) {
sd.tempSum += temp
sd.numReadings++
if temp > sd.maxTemp {
sd.maxTemp = temp
}
if sd.minTemp == 0 || temp < sd.minTemp {
sd.minTemp = temp
}
fmt.Printf("Avg/Max/Min temperature = %.2f/%.2f/%.2f\n",
sd.tempSum/float64(sd.numReadings), sd.maxTemp, sd.minTemp)
}Advanced Implementation
Finally, we can see how all these components integrate together to form a complete Observer design pattern-based system.
func main() {
weatherData := &WeatherData{}
currentDisplay := &CurrentConditionsDisplay{}
statsDisplay := &StatisticsDisplay{}
weatherData.RegisterObserver(currentDisplay)
weatherData.RegisterObserver(statsDisplay)
// Simulate new weather measurements
weatherData.temperature = 80
weatherData.humidity = 65
weatherData.pressure = 30.4
weatherData.NotifyObservers()
// Updated weather measurements
weatherData.temperature = 82
weatherData.humidity = 70
weatherData.pressure = 29.2
weatherData.NotifyObservers()
}In this code, we've implemented an Observer pattern where our WeatherData acts as the subject, and the CurrentConditionsDisplay and StatisticsDisplay act as different observers. When weather data is updated through the NotifyObservers method, each observer updates accordingly.
This design pattern's advantage is that it keeps the subject and observers loosely coupled. The subject only knows the list of observers, while observers only know the subject's updates when they get triggered via their Update method.