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.