Patterns: behavior · 3 MIN READ

Observer: subscribe, notify and unsubscribe

Keep notification lifetime and delivery semantics explicit.

Keep notification lifetime and delivery semantics explicit. Observer lets interested listeners subscribe to changes without the subject knowing their concrete types. Trace the worked Go example, then compare the benefit with the cost before applying this design to your own project.

Behavioral pattern · A MAP OF THE IDEA

  1. Listener subscribes to a subject
  2. Subject notifies registered listeners
  3. Owner unsubscribes at end of lifetime

Useful when

  • Several local components react to changes
  • The subject should not know listener types

Watch out

  • Slow callbacks can block the producer
  • Subscriptions can retain objects unexpectedly

Implementation & operation cost: Medium

Fit depends on the task

Small tool or CLI3/5
Domain-rich application4/5
Independent distributed services3/5

Editorial suitability, not benchmarks or a universal ranking. Styles describe different axes and can be combined.

01

The design decision

Observer lets interested listeners subscribe to changes without the subject knowing their concrete types. Local synchronous callbacks are often enough. This is not a durable event bus: a crashed process loses listeners and an in-process notification has no automatic redelivery guarantee.

02

A complete Go example

Read this program from the entry point and follow the calls shown in the diagram. The output is included at the end so you can check the behavior without a compiler.

go
package main

import (
	"fmt"
)

type Subject struct {
	listeners map[int]func(int)
	next      int
}

func (s *Subject) Subscribe(f func(int)) func() {
	id := s.next
	s.next++
	s.listeners[id] = f
	return func() { delete(s.listeners, id) }
}
func (s *Subject) Notify(n int) {
	for _, f := range s.listeners {
		f(n)
	}
}

func main() {
	s := Subject{listeners: make(map[int]func(int))}
	total := 0
	stop := s.Subscribe(func(n int) { total += n })
	s.Notify(3)
	stop()
	s.Notify(5)
	fmt.Println(total)
}

// Output:
// 3
Read the code

The subscription returns its own cleanup operation. After stop, a later notification has no effect on total. The example assumes a single owner, no reentrant map mutation and listeners whose order does not matter.

03

Tradeoffs and the simpler alternative

Decide whether delivery is synchronous, ordered, droppable or buffered before introducing goroutines. Slow or panicking listeners can affect the subject. Retained callbacks can also retain large object graphs, so unsubscribe on the owner’s lifecycle boundary.

TAKE THIS WITH YOU

Needs explicit delivery and unsubscribe semantics. Decide whether delivery is synchronous, ordered, droppable or buffered before introducing goroutines. Slow or panicking listeners can affect the subject. Retained callbacks can also retain large object graphs, so unsubscribe on the owner’s lifecycle boundary.

Check your understanding

Which statement best describes Observer: subscribe, notify and unsubscribe?

Go to the source

Progress stays in this browser. No account needed.