設計模式 in Go: Observer

行爲模式旨在解決對象間通信和交互相關的問題,專注於定義那些複雜到無法靜態設計的協作策略,這些協作策略使得程序可以在運行時動態地進行職責派遣以實現更好的擴展。

今天我們開始第 8 個行爲模式的學習 —— Observer(觀察者模式)。

問題背景:

觀察者模式用於當對象之間存在一對多關係時,即一個對象的變化需要傳播給多個其他對象。它允許被觀察對象的狀態發生變化時,其他依賴對象能自動收到通知並進行更新。

解決方案:

觀察者模式涉及兩個主要組件:主題(Subject)和觀察者(Observers)。主題是被觀察的對象,它維護一個感興趣的觀察者的列表。觀察者通過註冊自己來獲取主題的狀態變化更新。

當主題的狀態發生變化時,它會調用預定義的方法通知所有已註冊的觀察者。這個方法由觀察者實現,使它們能夠根據主題的新狀態更新自身狀態或執行必要的操作。

這種主題和觀察者之間的解耦使得設計更加靈活且鬆散耦合。主題不需要直接瞭解觀察者的細節,並且可以自由地添加或移除新的觀察者而不影響主題或其他觀察者。

Weather Report / Observer

下面是一個示例:假設我們有一些客戶,他們希望關注天氣的變化,包括溫度、溼度和氣壓。當這些數據中的任何一個發生變化時,我們應該通知客戶進行更新。

Observer Pattern

示例代碼:

package observer

import (
"fmt"
)

// WeatherChangeEvent represents the event containing weather changes
type WeatherChangeEventstruct {
 Temperaturefloat64
 Humidityfloat64
 Pressurefloat64

// Add any additional indexes or properties as needed
}

// Observer interface represents the observers that will receive weather updates
type Observerinterface {
 Update(event WeatherChangeEvent)
}

// Subject interface represents the subject or publisher that will notify observers of weather changes
type Subjectinterface {
 RegisterObserver(observer Observer)
 RemoveObserver(observer Observer)
 NotifyObservers(event WeatherChangeEvent)
}

// WeatherForecastServer represents the weather forecast server, which is the subject or publisher
type WeatherForecastServerstruct {
 observers []Observer
}

func(w *WeatherForecastServer) RegisterObserver(observer Observer) {
 w.observers =append(w.observers, observer)
}

func(w *WeatherForecastServer) RemoveObserver(observer Observer) {
for i, obs :=range w.observers {
if obs == observer {
 w.observers =append(w.observers[:i], w.observers[i+1:]...)
break
 }
 }
}

func(w *WeatherForecastServer) NotifyObservers(event WeatherChangeEvent) {
for _, observer :=range w.observers {
 observer.Update(event)
 }
}

func(w *WeatherForecastServer) SetMeasurements(event WeatherChangeEvent) {
 w.NotifyObservers(event)
}

// Client represents a client that acts as an observer
type Clientstruct {
 namestring
}

func(c *Client) Update(event WeatherChangeEvent) {
 fmt.Printf("Client %s received weather update - Temperature: %.2f, Humidity: %.2f, Pressure: %.2f\n",
 c.name, event.Temperature, event.Humidity, event.Pressure)
// Handle the additional indexes or properties as needed
}

運行測試以演示:

package observer

import (
"testing"
"time"
)

funcTestObserver(t *testing.T) {
 server := &WeatherForecastServer{}

 client1 := &Client{name:"client1"}
 client2 := &Client{name:"client2"}

 server.RegisterObserver(client1)
 server.RegisterObserver(client2)

// Simulating weather updates
 event1 := WeatherChangeEvent{Temperature:25.5, Humidity:70.2, Pressure:1013.5}
 server.SetMeasurements(event1)
 time.Sleep(2 * time.Second)

 event2 := WeatherChangeEvent{Temperature:26.8, Humidity:68.9, Pressure:1012.8}
 server.SetMeasurements(event2)
 time.Sleep(2 * time.Second)

 event3 := WeatherChangeEvent{Temperature:24.3, Humidity:72.1, Pressure:1014.2}
 server.SetMeasurements(event3)

// Unregistering an observer
 server.RemoveObserver(client2)

// Simulating
 time.Sleep(2 * time.Second)
 event4 := WeatherChangeEvent{Temperature:22.2, Humidity:66.6, Pressure:1013.1}
 server.SetMeasurements(event4)

 time.Sleep(time.Second)
}
Running tool: /opt/go/bin/gotest -timeout 30s -run ^TestObserver$ observer -v -count=1 -timeout=1m

=== RUN TestObserver
Client client1 received weather update - Temperature: 25.50, Humidity: 70.20, Pressure: 1013.50
Client client2 received weather update - Temperature: 25.50, Humidity: 70.20, Pressure: 1013.50
Client client1 received weather update - Temperature: 26.80, Humidity: 68.90, Pressure: 1012.80
Client client2 received weather update - Temperature: 26.80, Humidity: 68.90, Pressure: 1012.80
Client client1 received weather update - Temperature: 24.30, Humidity: 72.10, Pressure: 1014.20
Client client2 received weather update - Temperature: 24.30, Humidity: 72.10, Pressure: 1014.20
Client client1 received weather update - Temperature: 22.20, Humidity: 66.60, Pressure: 1013.10
--- PASS: TestObserver (7.00s)
PASS
ok observer 7.007s

ps:此處我們優先考慮可讀性,不會太關注編碼標準,如註釋、camelCase 類型名等。我們將多個文件的代碼組織到一個 codeblock 中僅僅是爲了方便閱讀,如果您想測試可以通過 git 下載源碼 github.com/hitzhangjie/go-patterns。

觀察者模式變體:

觀察者模式有幾種變體,可以根據系統的具體需求來選擇使用:

與其他模式關係:

觀察者模式可以與其他設計模式以以下方式相關:

本文總結:

觀察者模式提供了一種在對象之間建立一對多關係的方式,使得當被觀察對象的狀態發生變化時,相關對象能夠自動收到通知並進行更新。它包括主題(Subject)和觀察者(Observers),其中主題維護一個觀察者的列表,並在狀態變化時通知它們。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/9V-4068ePN711vNIiUHmAA