設計模式 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。
觀察者模式變體:
觀察者模式有幾種變體,可以根據系統的具體需求來選擇使用:
-
推送 (Push)/ 拉取 (Pull): 在 push 變體中,主題將更新的狀態直接發送給觀察者。在 pull 變體中,當需要時觀察者會從主題請求最新的狀態。push 和 pull 的選擇取決於諸如狀態的複雜性和更新頻率等因素。
-
事件驅動: 在事件驅動系統中,觀察者模式通常用於處理事件和通知。事件由源對象生成,並傳播給感興趣的觀察者,使它們能夠相應地做出反應。
-
響應式編程: 響應式編程框架和庫經常實現觀察者模式來處理數據流和事件流。觀察者訂閱這些流並在實時中對變化作出響應。
與其他模式關係:
觀察者模式可以與其他設計模式以以下方式相關:
-
中介者模式 (Mediator Pattern): 中介者模式可以與觀察者模式結合使用,解耦對象之間的通信。中介者作爲中央樞紐,協調主題和觀察者之間的通信。
-
發佈 / 訂閱模式 (Publish/Subscribe Pattern): 發佈 / 訂閱模式是觀察者模式的一種變體,在這種模式中,觀察者訂閱特定類型的事件或消息。主題被稱爲發佈者,負責發佈事件或消息;觀察者則稱爲訂閱者,接收並處理這些事件。
-
MVC 模式: Model-View-Controller (MVC) 模式經常包含觀察者模式。模型代表主題,視圖代表觀察者。當模型的狀態發生變化時,它會通知視圖進行更新。
本文總結:
觀察者模式提供了一種在對象之間建立一對多關係的方式,使得當被觀察對象的狀態發生變化時,相關對象能夠自動收到通知並進行更新。它包括主題(Subject)和觀察者(Observers),其中主題維護一個觀察者的列表,並在狀態變化時通知它們。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/9V-4068ePN711vNIiUHmAA