Introduction

The observer pattern is a software design pattern that allows an object, usually called the subject, to maintain a list of dependents, called observers, and notify them automatically of any state-changes. Many languages have this pattern either built-in or in their standard libraries.

Examples:

  • C# has the concept of Observable in the standard .NET library. Also it has the concept of delegates.
  • Although Dart doesn’t have that, in Flutter we have the SetState method which automatically notifes any observer, and it is mainly used to refresh the UI.

Go however, does not have this pattern built, yet it is quite easy to build it.

First a look at a very simplified version of the pattern:

A breakdown of this diagram:

  1. First there is the Observable, this could be an interface, the object which is to be observed. The observable holds a list of observers.
  2. The Observer, which sends out a message to the observers. This could also be an interface.
  3. The ConcreteObservable, the concrete class which holds the state. If anything changes in the state, the setState method is called, which in turn calls the notify method. That in turn notifies the observers.
  4. The concrete observers which handle the state change.

Here the notify and update methods have no parameters, you could of course send extra data to the observers, and in most cases that will happen.

Implementation in Go

Open your terminal in an empty directory and type:

go mod init github.com/observerpattern

First add these lines:

package main

import "fmt"

Now define the Observer:

type Observer interface {
	Update()
}

And the Subject with its methods:

type Subject struct {
	observers []Observer
	state     string
}

func (s *Subject) Attach(o Observer) {
	s.observers = append(s.observers, o)
}

func (s *Subject) Detach(o Observer) {
	for i, observer := range s.observers {
		if observer == o {
			s.observers = append(s.observers[:i], s.observers[i+1:]...)
			break
		}
	}
}

func (s *Subject) Notify() {
	for _, observer := range s.observers {
		observer.Update()
	}
}

func (s *Subject) SetState(state string) {
	s.state = state
	s.Notify()
}

We do not need the GetState method here, as this example does not deal with the state of the subject itself.

Furthermore we see in the SetState method a call to the Notify method. So, any changes on the Subject will have to be done through the SetState method, in order for the Observers to fire their Update method.

The Notify method iterates over the list of observers, and sends each one the Update message.

Now we need to define some concrete observers:

type ConcreteObserver1 struct {
	subject *Subject
}

func (o *ConcreteObserver1) Update() {
	fmt.Println("ConcreteObserver1 received notification:", o.subject.state)
}

type ConcreteObserver2 struct {
	subject *Subject
}

func (o *ConcreteObserver2) Update() {
	fmt.Println("ConcreteObserver2 received notification:", o.subject.state)
}

You can see two things here:

  1. Each observer holds a reference to the subject
  2. The only method we need to implements is the Update method.

Now it is time to put it to the test:

func main() {
	subject := &Subject{}

	observer1 := &ConcreteObserver1{subject: subject}
	observer2 := &ConcreteObserver2{subject: subject}

	subject.Attach(observer1)
	subject.Attach(observer2)

	subject.SetState("state 1")
	subject.SetState("state 2")

	subject.Detach(observer2)

	subject.SetState("state 3")

	subject.Detach(observer1)

}

Line by line:

  1. We instantiate a subject and two observers
  2. We attach the observers to the subject
  3. We set the state of the subject, thereby firing the Update methods on the observers
  4. We detach observer2
  5. We send another setState message, and observe that only observer1 gets the Update
  6. We detach observer2

Detaching is very important because in some edge cases, the connection between the subject and observer might not be garbage collected. In this example that is not very important, but in larger project this could be.

Conclusion

As you can see, the Observer pattern is not very difficult to implement, yet very powerful. It can be used to communicate safely between disparate portions of your program.

One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *