Table of Contents
Open Table of Contents
Introduction
In this post, we’ll walk through how to implement the Observer Pattern in Go using a practical example: a YouTube channel that notifies subscribers when new videos are published.
The Observer Pattern is useful when you want to notify multiple components about an event — like subscribers being notified when a new video is out.
Define the Interfaces
We start by defining two interfaces:
type Observer interface {
Update(msg string)
}
type Subject interface {
RegisterObserver(Observer)
RemoveObserver(Observer)
Notify(msg string)
}
Observer
: Anything that wants to receive updates.Subject
: Anything that can send updates to multiple observers.
Create the Subject — YouTubeChannel
This struct will manage the list of observers and notify them:
type YouTubeChannel struct {
name string
observers map[Observer]struct{}
}
We’re using a map[Observer]struct to efficiently:
- Avoid duplicate observers.
- Add and remove observers in constant time.
Implement the Subject Methods
func NewYouTubeChannel(name string) *YouTubeChannel {
return &YouTubeChannel{
name: name,
observers: make(map[Observer]struct{}),
}
}
func (c *YouTubeChannel) RegisterObserver(o Observer) {
if o != nil {
c.observers[o] = struct{}{}
}
}
func (c *YouTubeChannel) RemoveObserver(o Observer) {
delete(c.observers, o)
}
func (c *YouTubeChannel) Notify(videoName string) {
msg := fmt.Sprintf("%s by %s", videoName, c.name)
for o := range c.observers {
o.Update(msg)
}
}
func (c *YouTubeChannel) ReleaseNewVideo(videoName string) {
c.Notify(videoName)
}
Create Observers — Subscribers
type Subscriber struct {
name string
}
func NewSubscriber(name string) *Subscriber {
return &Subscriber{name: name}
}
func (s *Subscriber) Update(msg string) {
fmt.Printf("I'm %q, and I'll watch the video: %q\n", s.name, msg)
}
Each Subscriber
implements the Observer
interface. When notified, it prints a message with the video title.
Put It All Together in main()
package main
import (
"fmt"
)
// Observer defines an interface for receiving updates from a Subject.
type Observer interface {
Update(msg string)
}
// Subject defines an interface for managing and notifying observers.
type Subject interface {
RegisterObserver(Observer)
RemoveObserver(Observer)
Notify(msg string)
}
// Compile-time check to ensure YouTubeChannel implements Subject interface.
var _ Subject = (*YouTubeChannel)(nil)
// YouTubeChannel represents a subject that notifies subscribers about new videos.
type YouTubeChannel struct {
name string
observers map[Observer]struct{}
}
// NewYouTubeChannel creates a new instance of YouTubeChannel.
func NewYouTubeChannel(name string) *YouTubeChannel {
return &YouTubeChannel{
name: name,
observers: make(map[Observer]struct{}),
}
}
// RegisterObserver adds a new observer to the map (no duplicates allowed).
func (c *YouTubeChannel) RegisterObserver(o Observer) {
if o == nil {
return // Skip nil observers
}
c.observers[o] = struct{}{} // Add observer as key
}
// RemoveObserver deletes the observer from the map.
func (c *YouTubeChannel) RemoveObserver(o Observer) {
delete(c.observers, o)
}
// Notify sends a message to all registered observers.
func (c *YouTubeChannel) Notify(videoName string) {
msg := fmt.Sprintf("%s by %s", videoName, c.name)
for observer := range c.observers {
observer.Update(msg)
}
}
// ReleaseNewVideo publishes a new video and notifies observers.
func (c *YouTubeChannel) ReleaseNewVideo(videoName string) {
c.Notify(videoName)
}
// Subscriber represents an observer that receives notifications from a channel.
type Subscriber struct {
name string
}
// NewSubscriber creates a new instance of Subscriber.
func NewSubscriber(name string) *Subscriber {
return &Subscriber{name: name}
}
// Update is called when the subscriber receives a notification.
func (s *Subscriber) Update(msg string) {
fmt.Printf("I'm %q, and I'll watch the video: %q\n", s.name, msg)
}
func main() {
channel := NewYouTubeChannel("gustavocd")
john := NewSubscriber("John Doe")
alice := NewSubscriber("Alice Doe")
lee := NewSubscriber("Lee Mon Chu-pao")
channel.RegisterObserver(john)
channel.RegisterObserver(alice)
channel.RegisterObserver(lee)
channel.ReleaseNewVideo("How to implement the Observer Pattern in Go? 🔥")
channel.RemoveObserver(alice)
fmt.Printf("\nAfter removing Alice:\n\n")
channel.ReleaseNewVideo("Working with maps in Go 🐹")
}
Execute the code in this playground
Conclusion
- We used
map[Observer]struct{}
to prevent duplicates and simplify removals. - Each
Observer
receives updates automatically when theSubject
notifies them. - This pattern decouples the broadcaster (
YouTubeChannel
) from the receivers (Subscribers
).