觀察者模式
什么是觀察者
觀察者模式(Observer Pattern):定義對象之間的一種一對多依賴關系,使得每當一個對象狀態發生改變時,其相關依賴對象皆得到通知并被自動更新。觀察者模式的別名包括發布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式是一種對象行為型模式。
結構
- **Subject(主題):**保持一個觀察者列表,提供添加、刪除和通知觀察者的方法。
- **Observer(觀察者):**定義一個更新接口,使得在主題狀態變化時得到通知。
- Concrete Subject(具體主題):實現Subject接口,存儲狀態,當狀態發生改變時,通知所有觀察者。
- Concrete Observer(具體觀察者):實現Observer接口,根據主題更新來更新自己的狀態。
基本流程
- 注冊觀察者:觀察者向主題注冊自己。
- 狀態變更:主題的狀態發生變化。
- 通知觀察者:主題通過調用注冊的觀察者的方法來通知它們狀態已變化。
- 更新觀察者:觀察者接收到通知后更新自己的狀態。
優點
- 解耦:觀察者模式能夠將主題和觀察者解耦,它們之間不需要知道對方的存在。
- 可擴展性:新增觀察者時,不需要修改主題的代碼,符合開閉原則。
- 動態交互:可以實現動態的交互,主題可以在運行時添加或刪除觀察者。
缺點
- 循環引用:如果不當使用,可能會導致循環引用,增加內存管理的難度。
- 性能問題:當觀察者較多時,通知所有觀察者可能會造成性能問題。
- 順序不確定:觀察者接收通知的順序是不確定的,可能會導致不可預知的副作用。
使用場景
觀察者模式通常用于構建松耦合的系統,其中一個對象(稱為主題或發布者)可以通知多個其他對象(稱為觀察者或訂閱者)關于狀態的變化。
- 在線購物平臺訂單管理:
- 主題(Subject):訂單系統,負責在訂單狀態更新時(如確認、發貨、收貨)廣播變更事件。
- 觀察者(Observers):包括支付模塊、庫存管理、物流跟蹤等,它們監聽訂單狀態更新并執行相應操作。
- 圖形用戶界面(GUI)同步:
- 主題(Subject):文檔管理系統,監控文檔內容的更改并觸發更新事件。
- 觀察者(Observers):界面組件如文本框、滾動條、狀態欄等,它們接收更新事件并刷新顯示。
- 模型-視圖-控制器(MVC)架構:
- 主題(Subject):數據模型,實時更新數據狀態并通知視圖與控制器。
- 觀察者(Observers):視圖界面和控制器邏輯,訂閱數據變更,視圖更新顯示,控制器響應用戶交互。
- 社交媒體內容更新:
- 主題(Subject):用戶發布系統,當用戶發布新推文或狀態時觸發通知。
- 觀察者(Observers):粉絲和關注者,他們接收到新內容的通知并更新自己的信息流。
- 股票交易實時系統:
- 主題(Subject):股票行情中心,實時監控并發布股票價格的變動。
- 觀察者(Observers):交易平臺界面、分析工具、自動交易腳本等,它們根據行情變化進行決策和操作。
- 動態配置更新系統:
- 主題(Subject):配置服務器,負責維護應用配置并在配置更新時發送通知。
- 觀察者(Observers):應用服務和組件,它們監聽配置變更并實時調整自身設置。
注意事項
- 避免循環引用:確保主題和觀察者之間不會產生循環引用。
- 管理生命周期:合理管理主題和觀察者的生命周期,避免內存泄漏。
- 線程安全:在多線程環境中使用觀察者模式時,需要考慮線程安全問題
代碼案例
package designpatternimport ("fmt""sync"
)// Observer 觀察者接口
type Observer interface {Update() // Update方法用于接收主題狀態變化的通知
}// ConcreteObserver 具體觀察者
type ConcreteObserver struct {name string
}func (c *ConcreteObserver) Update() {fmt.Printf("%s is notified.\n", c.name) // 具體觀察者接收到通知后的具體處理邏輯
}// Subject 主題接口
type Subject interface {RegisterObserver(observer Observer) // 注冊觀察者DeregisterObserver(observer Observer) // 注銷觀察者NotifyObservers() // 通知所有觀察者
}// ConcreteSubject 具體主題
type ConcreteSubject struct {observers []Observer // 觀察者列表state int // 主題狀態mu sync.Mutex // 互斥鎖,用于保護并發訪問
}// NewConcreteSubject 創建具體主題實例
func NewConcreteSubject() *ConcreteSubject {return &ConcreteSubject{observers: make([]Observer, 0),mu: sync.Mutex{}, // 初始化互斥鎖}
}func (cs *ConcreteSubject) RegisterObserver(observer Observer) {cs.mu.Lock()defer cs.mu.Unlock()cs.observers = append(cs.observers, observer) // 注冊觀察者到列表中
}func (cs *ConcreteSubject) DeregisterObserver(observer Observer) {cs.mu.Lock()defer cs.mu.Unlock()for i, ob := range cs.observers {if ob == observer {cs.observers = append(cs.observers[:i], cs.observers[i+1:]...) // 從觀察者列表中注銷觀察者break}}
}func (cs *ConcreteSubject) NotifyObservers() {cs.mu.Lock()defer cs.mu.Unlock()for _, ob := range cs.observers {ob.Update() // 通知所有觀察者主題狀態變化}
}func (cs *ConcreteSubject) SetState(state int) {cs.mu.Lock()defer cs.mu.Unlock()cs.state = state // 設置主題狀態cs.NotifyObservers() // 通知所有觀察者主題狀態變化
}func main() {// 在 main 函數中演示了具體的使用方法,創建具體主題實例,注冊觀察者,并設置主題狀態,觸發通知subject := NewConcreteSubject()ob1 := &ConcreteObserver{"ob1"}ob2 := &ConcreteObserver{"ob2"}subject.RegisterObserver(ob1)subject.RegisterObserver(ob2)subject.SetState(1)
}
模擬一個新聞發布網站
package mainimport ("fmt""sync"
)// 新聞類型
type NewsType intconst (Business NewsType = iotaTechnologySportsWorldEntertainment
)// 觀察者接口
type Observer interface {Update(News)
}// 具體觀察者結構體
type Subscriber struct {Name stringInterests map[NewsType]boolRegister chan NewsTypeUnregister chan NewsType
}func NewSubscriber(name string) *Subscriber {return &Subscriber{Name: name,Interests: make(map[NewsType]bool),Register: make(chan NewsType),Unregister: make(chan NewsType),}
}func (s *Subscriber) Update(news News) {if _, ok := s.Interests[news.Type]; ok {fmt.Printf("%s received news: %s\n", s.Name, news.Headline)}
}func (s *Subscriber) RegisterInterest(interest NewsType) {s.Register <- interests.Interests[interest] = true
}func (s *Subscriber) UnregisterInterest(interest NewsType) {s.Unregister <- interestdelete(s.Interests, interest)
}// 主題接口
type Subject interface {Attach(Observer)Detach(Observer)Notify(string)
}// 具體主題結構體
type NewsAgency struct {observers map[Observer]boolnews chan Newsmu sync.Mutex
}func NewNewsAgency() *NewsAgency {return &NewsAgency{observers: make(map[Observer]bool),news: make(chan News),}
}func (a *NewsAgency) Attach(observer Observer) {a.mu.Lock()defer a.mu.Unlock()a.observers[observer] = true
}func (a *NewsAgency) Detach(observer Observer) {a.mu.Lock()defer a.mu.Unlock()delete(a.observers, observer)
}func (a *NewsAgency) Notify(headline string) {for observer, _ := range a.observers {news := News{Headline: headline}go observer.Update(news)}
}// 新聞結構體
type News struct {Headline stringType NewsType
}func main() {// 創建新聞機構agency := NewNewsAgency()// 創建訂閱者alice := NewSubscriber("Alice")bob := NewSubscriber("Bob")// 訂閱興趣alice.RegisterInterest(Business)alice.RegisterInterest(World)bob.RegisterInterest(Technology)bob.RegisterInterest(Entertainment)// 將訂閱者作為觀察者注冊到新聞機構agency.Attach(alice)agency.Attach(bob)// 新聞發布agency.Notify("Big Corp acquired Small Tech for $1B")// 訂閱者取消訂閱bob.UnregisterInterest(Entertainment)// 再次新聞發布agency.Notify("New breakthrough in AI technology")
}
- 定義了
NewsType
類型,用于區分不同類型的新聞。 Observer
接口有一個Update
方法,用于接收新聞更新。Subscriber
結構體代表具體的觀察者,它包含訂閱者的名字和興趣,以及注冊和注銷興趣的通道。Subject
接口包含Attach
、Detach
和Notify
方法。NewsAgency
結構體代表具體的主題,它維護了一個觀察者集合和一個發布新聞的通道。News
結構體包含新聞的標題和類型。- 在
main
函數中,我們創建了新聞機構和兩個訂閱者,將訂閱者的興趣注冊到新聞機構,并模擬了新聞發布。