Welcome back! After exploring the Strategy Pattern, it's time to delve into another important behavioral design pattern: the Observer Pattern. This pattern focuses on establishing one-to-many relationships between objects, allowing an object (subject) to notify other objects (observers) about changes in its state.
In this lesson, you will discover how to implement the Observer Pattern in Go, covering the following key concepts:
Let's look at an example to illustrate these concepts:
We start by defining the Observer interface with an Update method that observers must implement:
Go1type Observer interface { 2 Update(string) 3}
Next, we create a concrete observer that implements the Observer interface. When the Update method is called, the observer receives a message from the subject:
Go1type ConcreteObserver struct { 2 ID int 3 Name string 4} 5 6func (co *ConcreteObserver) Update(message string) { 7 fmt.Printf("Subscriber %s (ID: %d) received weather update: %s\n", co.Name, co.ID, message) 8}
Now, let's define the Subject interface and a concrete subject that implements it. The subject maintains a list of observers and provides methods to register, unregister, and notify them. When the NotifyObservers method is called, the subject sends a message to all observers by calling their Update methods, thereby notifying them of the state change:
Go1type Subject interface { 2 RegisterObserver(Observer) 3 UnregisterObserver(Observer) 4 NotifyObservers(string) 5} 6 7type ConcreteSubject struct { 8 observers []Observer 9} 10 11func (cs *ConcreteSubject) RegisterObserver(o Observer) { 12 cs.observers = append(cs.observers, o) 13} 14 15func (cs *ConcreteSubject) UnregisterObserver(o Observer) { 16 var indexToRemove int 17 for index, observer := range cs.observers { 18 if observer == o { 19 indexToRemove = index 20 break 21 } 22 } 23 cs.observers = append(cs.observers[:indexToRemove], cs.observers[indexToRemove+1:]...) 24} 25 26func (cs *ConcreteSubject) NotifyObservers(message string) { 27 for _, observer := range cs.observers { 28 observer.Update(message) 29 } 30}
Finally, we can put it all together in the main function:
Go1func main() { 2 subscriber1 := &ConcreteObserver{ID: 1, Name: "WeatherApp"} 3 subscriber2 := &ConcreteObserver{ID: 2, Name: "NewsWebsite"} 4 5 weatherStation := &ConcreteSubject{} 6 7 weatherStation.RegisterObserver(subscriber1) 8 weatherStation.RegisterObserver(subscriber2) 9 10 weatherStation.NotifyObservers("Sunny with a high of 25°C") // Both observers receive the message and print it to the console 11 12 weatherStation.UnregisterObserver(subscriber1) 13 14 weatherStation.NotifyObservers("Rainy with a chance of thunderstorms") // Only subscriber2 receives the message, as subscriber1 was unregistered 15}
Let's break down this example:
Observer
interface with an Update
method that observers must implement.ConcreteObserver
struct implements the Observer
interface and prints the received message when the Update
method is called.Subject
interface defines methods to register, unregister, and notify observers.ConcreteSubject
struct maintains a list of observers and implements the Subject
interface methods.main
function, we create two observers (subscriber1
and subscriber2
) and a subject (weatherStation
).The output of the example will look like this:
1Subscriber WeatherApp (ID: 1) received weather update: Sunny with a high of 25°C 2Subscriber NewsWebsite (ID: 2) received weather update: Sunny with a high of 25°C 3Subscriber NewsWebsite (ID: 2) received weather update: Rainy with a chance of thunderstorms
Notice how the observers receive the updates from the subject and react accordingly. This demonstrates the one-to-many relationship established by the Observer Pattern.
Let's understand what are the key components of the Observer Pattern:
Subject
is the interface that subjects must implement, and ConcreteSubject
is a concrete subject that implements this interface.Observer
is the interface that observers must implement, and ConcreteObserver
is a concrete observer that implements this interface.By following this example, you will gain a solid understanding of how the Observer Pattern works and how it can be applied in real-world scenarios.
The Observer Pattern is widely used in various scenarios, including:
The Observer Pattern offers several benefits, but it also has some drawbacks. Let's explore the pros and cons of this pattern:
Pros
Cons
Understanding the Observer Pattern is crucial because it promotes a flexible and scalable way to communicate changes between objects. This pattern is widely used in event-driven programming and is essential for designing systems that need to notify multiple components about state changes.
For example, imagine you are building a weather application. The weather station (subject) needs to notify various applications and websites (observers) about changes in weather conditions. Using the Observer Pattern, you can efficiently manage these updates without tightly coupling the weather station to each application.
Let's jump into the practice section to solidify your understanding and see the Observer Pattern in action!