概念
觀察者模式定義了一種'一對多'的得依賴關(guān)系靴寂, 讓多個觀察者對象同時監(jiān)聽某一個主題對象。這個主題對象在狀態(tài)發(fā)生變化時, 會通知所有觀察者對象馆纳, 使它們能夠根據(jù)根據(jù)變化做一些事情邻遏。
具體實現(xiàn):
- 定義一個主題接口糠亩, 接口方法有:添加主題虐骑, 刪除主題, 通知函數(shù)
- 定義一個觀察者接口赎线, 接口方法:OnNotify 被通知
- 實現(xiàn)主題接口廷没, 主題接口保存一個觀察者列表
- 實現(xiàn)不同的觀察者, 觀察者可以注冊到主題
觀察者模式和發(fā)布訂閱模式的區(qū)別:
- 觀察者模式有一個主題對象垂寥, 一個觀察者對象颠黎。主題變化,主動通知觀察者滞项。
- 發(fā)布訂閱模式:有3個對象狭归, 發(fā)布者, 訂閱者文判, 調(diào)度中心过椎。 發(fā)布者發(fā)布一個主題消息, 調(diào)度中心將消息轉(zhuǎn)發(fā)給訂閱了這個主題的訂閱者律杠。
使用觀察者模式的場景和優(yōu)缺點
使用場景
關(guān)聯(lián)行為場景潭流,需要注意的是,關(guān)聯(lián)行為是可拆分的柜去,而不是“組合”關(guān)系灰嫉。
事件多級觸發(fā)場景。
跨系統(tǒng)的消息交換場景嗓奢,如消息隊列讼撒、事件總線的處理機制。
優(yōu)點
- 解除耦合股耽,讓耦合的雙方都依賴于抽象根盒,從而使得各自的變換都不會影響另一邊的變換。
缺點
- 在應(yīng)用觀察者模式時需要考慮一下開發(fā)效率和運行效率的問題物蝙,程序中包括一個被觀察者炎滞、多個觀察者,開發(fā)诬乞、調(diào)試等內(nèi)容會比較復(fù)雜册赛,如果一個觀察者卡頓,會影響整體的執(zhí)行效率震嫉,在這種情況下森瘪,一般會采用異步實現(xiàn)。
代碼實現(xiàn)
package main
import (
"fmt"
)
// Event 消息 1:吃飯
type Event struct {
Data int64
}
// 觀察者接口
type Observer interface {
// 消息變動通知觀察者
OnNotify(Event)
}
// 主題
type Subject interface {
// 添加主題
Attach(Observer)
// 刪除主題
DeAttach(Observer)
// 通知消息
Notify(Event)
}
// 主題具體實現(xiàn)
type SubjectImpl struct {
// 用map來存放Observer
observers map[Observer]struct{}
}
// Attach ...
// 傳入的l變量(ConcreteObserver1結(jié)構(gòu)體變量)無論是指針還是值類型, 根據(jù)實現(xiàn)Observer結(jié)構(gòu)體的接收器類型有關(guān), 當(dāng)然無論接收器類型是指針類型,值類型無所謂, map 匹配結(jié)構(gòu)體本質(zhì)上, 是判斷結(jié)構(gòu)體變量是否相等, 并不關(guān)心是否是源數(shù)據(jù)還是copy的數(shù)據(jù).
func (o *SubjectImpl) Attach(l Observer) {
o.observers[l] = struct{}{}
}
// DeAttach ...
func (o *SubjectImpl) DeAttach(l Observer) {
delete(o.observers, l)
}
// Notify 把變化的消息發(fā)送給所有的觀察者
func (o *SubjectImpl) Notify(e Event) {
for i := range o.observers {
i.OnNotify(e)
}
}
// 具體觀察者1
type ConcreteObserver1 struct {
}
// OnNotify 收到消息變動
func (o *ConcreteObserver1) OnNotify(e Event) {
if e.Data == 1 {
fmt.Println("我是1號票堵, 我要吃面包")
}
}
// 具體觀察者2
type ConcreteObserver2 struct {
}
// OnNotify 收到消息變動
func (o *ConcreteObserver2) OnNotify(e Event) {
if e.Data == 1 {
fmt.Println("我是2號扼睬, 我要吃面條")
}
}
func main() {
// 初始化
n := SubjectImpl{
observers: map[Observer]struct{}{},
}
// 注冊兩個觀察者
n.Attach(&ConcreteObserver1{})
n.Attach(&ConcreteObserver2{})
// 消息通知
n.Notify(Event{Data: 1})
}
訂閱-發(fā)布模式
在現(xiàn)在的發(fā)布訂閱模式中,稱為發(fā)布者的消息發(fā)送者不會將消息直接發(fā)送給訂閱者悴势,這意味著發(fā)布者和訂閱者不知道彼此的存在窗宇。在發(fā)布者和訂閱者之間存在第三個組件措伐,稱為調(diào)度中心或事件通道,它維持著發(fā)布者和訂閱者之間的聯(lián)系担映,過濾所有發(fā)布者傳入的消息并相應(yīng)地分發(fā)它們給訂閱者废士。
舉一個例子,你在微博上關(guān)注了A蝇完,同時其他很多人也關(guān)注了A官硝,那么當(dāng)A發(fā)布動態(tài)的時候,微博就會為你們推送這條動態(tài)短蜕。A就是發(fā)布者氢架,你是訂閱者,微博就是調(diào)度中心朋魔,你和A是沒有直接的消息往來的岖研,全是通過微博來協(xié)調(diào)的(你的關(guān)注,A的發(fā)布動態(tài))警检。
訂閱發(fā)布模式和觀察者模式的區(qū)別
- 訂閱發(fā)布模式, 發(fā)布訂閱模式相比觀察者模式多了個事件通道孙援,事件通道作為調(diào)度中心,管理事件的訂閱和發(fā)布工作扇雕,徹底隔絕了訂閱者和發(fā)布者的依賴關(guān)系拓售。即訂閱者在訂閱事件的時候,只關(guān)注事件本身镶奉,而不關(guān)心誰會發(fā)布這個事件础淤;發(fā)布者在發(fā)布事件的時候,只關(guān)注事件本身哨苛,而不關(guān)心誰訂閱了這個事件鸽凶。
- 觀察者模式有兩個重要的角色,即目標(biāo)和觀察者建峭。在目標(biāo)和觀察者之間是沒有事件通道的玻侥。一方面,觀察者要想訂閱目標(biāo)事件亿蒸,由于沒有事件通道使碾,因此必須將自己添加到目標(biāo)(Subject) 中進行管理;另一方面祝懂,目標(biāo)在觸發(fā)事件的時候,也無法將通知操作(notify) 委托給事件通道拘鞋,因此只能親自去通知所有的觀察者砚蓬。
概念
觀察者模式定義了一種'一對多'的得依賴關(guān)系, 讓多個觀察者對象同時監(jiān)聽某一個主題對象盆色。這個主題對象在狀態(tài)發(fā)生變化時灰蛙, 會通知所有觀察者對象祟剔, 使它們能夠根據(jù)根據(jù)變化做一些事情。
具體實現(xiàn):
- 定義一個主題接口摩梧, 接口方法有:添加主題物延, 刪除主題, 通知函數(shù)
- 定義一個觀察者接口仅父, 接口方法:OnNotify 被通知
- 實現(xiàn)主題接口叛薯, 主題接口保存一個觀察者列表
- 實現(xiàn)不同的觀察者, 觀察者可以注冊到主題
觀察者模式和發(fā)布訂閱模式的區(qū)別:
- 觀察者模式有一個主題對象笙纤, 一個觀察者對象耗溜。主題變化,主動通知觀察者省容。
- 發(fā)布訂閱模式:有3個對象抖拴, 發(fā)布者, 訂閱者腥椒, 調(diào)度中心阿宅。 發(fā)布者發(fā)布一個主題消息, 調(diào)度中心將消息轉(zhuǎn)發(fā)給訂閱了這個主題的訂閱者笼蛛。
使用觀察者模式的場景和優(yōu)缺點
使用場景
關(guān)聯(lián)行為場景洒放,需要注意的是,關(guān)聯(lián)行為是可拆分的伐弹,而不是“組合”關(guān)系拉馋。
事件多級觸發(fā)場景。
跨系統(tǒng)的消息交換場景惨好,如消息隊列煌茴、事件總線的處理機制。
優(yōu)點
- 解除耦合日川,讓耦合的雙方都依賴于抽象蔓腐,從而使得各自的變換都不會影響另一邊的變換。
缺點
- 在應(yīng)用觀察者模式時需要考慮一下開發(fā)效率和運行效率的問題龄句,程序中包括一個被觀察者回论、多個觀察者,開發(fā)分歇、調(diào)試等內(nèi)容會比較復(fù)雜傀蓉,如果一個觀察者卡頓,會影響整體的執(zhí)行效率职抡,在這種情況下葬燎,一般會采用異步實現(xiàn)。
代碼實現(xiàn)
package main
import (
"fmt"
)
// Event 消息 1:吃飯
type Event struct {
Data int64
}
// 觀察者接口
type Observer interface {
// 消息變動通知觀察者
OnNotify(Event)
}
// 主題
type Subject interface {
// 添加主題
Attach(Observer)
// 刪除主題
DeAttach(Observer)
// 通知消息
Notify(Event)
}
// 主題具體實現(xiàn)
type SubjectImpl struct {
// 用map來存放Observer
observers map[Observer]struct{}
}
// Attach ...
// 傳入的l變量(ConcreteObserver1結(jié)構(gòu)體變量)無論是指針還是值類型, 根據(jù)實現(xiàn)Observer結(jié)構(gòu)體的接收器類型有關(guān), 當(dāng)然無論接收器類型是指針類型,值類型無所謂, map 匹配結(jié)構(gòu)體本質(zhì)上, 是判斷結(jié)構(gòu)體變量是否相等, 并不關(guān)心是否是源數(shù)據(jù)還是copy的數(shù)據(jù).
func (o *SubjectImpl) Attach(l Observer) {
o.observers[l] = struct{}{}
}
// DeAttach ...
func (o *SubjectImpl) DeAttach(l Observer) {
delete(o.observers, l)
}
// Notify 把變化的消息發(fā)送給所有的觀察者
func (o *SubjectImpl) Notify(e Event) {
for i := range o.observers {
i.OnNotify(e)
}
}
// 具體觀察者1
type ConcreteObserver1 struct {
}
// OnNotify 收到消息變動
func (o *ConcreteObserver1) OnNotify(e Event) {
if e.Data == 1 {
fmt.Println("我是1號, 我要吃面包")
}
}
// 具體觀察者2
type ConcreteObserver2 struct {
}
// OnNotify 收到消息變動
func (o *ConcreteObserver2) OnNotify(e Event) {
if e.Data == 1 {
fmt.Println("我是2號谱净, 我要吃面條")
}
}
func main() {
// 初始化
n := SubjectImpl{
observers: map[Observer]struct{}{},
}
// 注冊兩個觀察者
n.Attach(&ConcreteObserver1{})
n.Attach(&ConcreteObserver2{})
// 消息通知
n.Notify(Event{Data: 1})
}
訂閱-發(fā)布模式
在現(xiàn)在的發(fā)布訂閱模式中窑邦,稱為發(fā)布者的消息發(fā)送者不會將消息直接發(fā)送給訂閱者,這意味著發(fā)布者和訂閱者不知道彼此的存在壕探。在發(fā)布者和訂閱者之間存在第三個組件冈钦,稱為調(diào)度中心或事件通道,它維持著發(fā)布者和訂閱者之間的聯(lián)系李请,過濾所有發(fā)布者傳入的消息并相應(yīng)地分發(fā)它們給訂閱者瞧筛。
舉一個例子,你在微博上關(guān)注了A捻艳,同時其他很多人也關(guān)注了A驾窟,那么當(dāng)A發(fā)布動態(tài)的時候,微博就會為你們推送這條動態(tài)认轨。A就是發(fā)布者绅络,你是訂閱者,微博就是調(diào)度中心嘁字,你和A是沒有直接的消息往來的恩急,全是通過微博來協(xié)調(diào)的(你的關(guān)注,A的發(fā)布動態(tài))纪蜒。
訂閱發(fā)布模式和觀察者模式的區(qū)別
- 訂閱發(fā)布模式, 發(fā)布訂閱模式相比觀察者模式多了個事件通道衷恭,事件通道作為調(diào)度中心,管理事件的訂閱和發(fā)布工作纯续,徹底隔絕了訂閱者和發(fā)布者的依賴關(guān)系随珠。即訂閱者在訂閱事件的時候,只關(guān)注事件本身猬错,而不關(guān)心誰會發(fā)布這個事件窗看;發(fā)布者在發(fā)布事件的時候,只關(guān)注事件本身倦炒,而不關(guān)心誰訂閱了這個事件显沈。
- 觀察者模式有兩個重要的角色,即目標(biāo)和觀察者逢唤。在目標(biāo)和觀察者之間是沒有事件通道的拉讯。一方面,觀察者要想訂閱目標(biāo)事件鳖藕,由于沒有事件通道魔慷,因此必須將自己添加到目標(biāo)(Subject) 中進行管理;另一方面著恩,目標(biāo)在觸發(fā)事件的時候院尔,也無法將通知操作(notify) 委托給事件通道纹烹,因此只能親自去通知所有的觀察者。
代碼實現(xiàn)
package main
import "fmt"
type Event struct {
Message string
}
type ControlCenter interface {
Subscribe(topic string, f func(e *Event))
Unsubscribe()
Publish()
}
// 每個訂閱者對應(yīng)一個callback
type Subscribers map[string]func(e *Event)
type ControlCenterIml struct {
// 一個主題對應(yīng)多個訂閱者
TopicEvent map[string]Subscribers
}
func (c *ControlCenterIml) Subscribe(subcriberId, topic string, f func(e *Event)) {
if c.TopicEvent == nil {
return
}
if v, ok := c.TopicEvent[topic]; !ok {
subscribers := make(Subscribers)
subscribers[subcriberId] = f
c.TopicEvent[topic] = subscribers
} else {
v[subcriberId] = f
c.TopicEvent[topic] = v
}
}
func (c *ControlCenterIml) Unsubscribe(subcriberId, topic string) {
if c.TopicEvent == nil {
return
}
if subscribers, ok := c.TopicEvent[topic]; !ok {
return
} else {
delete(subscribers, subcriberId)
}
}
func (c *ControlCenterIml) Publish(topic string, e *Event) bool {
if c.TopicEvent == nil {
return false
}
var subscribers Subscribers
if v, ok := c.TopicEvent[topic]; !ok {
return false
} else {
subscribers = v
}
for k, v := range subscribers {
fmt.Println(k)
v(e)
}
return true
}
func main() {
subPub := ControlCenterIml{TopicEvent: make(map[string]Subscribers)}
subPub.Subscribe("123", "study", func(e *Event) {
fmt.Println("recv something:", e.Message)
})
subPub.Subscribe("124", "study", func(e *Event) {
fmt.Println("recv something:", e.Message)
})
subPub.Publish("study", &Event{Message: "hello lili"})
subPub.Unsubscribe("123", "study")
subPub.Publish("study", &Event{Message: "hello vivi"})
}
代碼實現(xiàn)
package main
import "fmt"
type Event struct {
Message string
}
type ControlCenter interface {
Subscribe(topic string, f func(e *Event))
Unsubscribe()
Publish()
}
// 每個訂閱者對應(yīng)一個callback
type Subscribers map[string]func(e *Event)
type ControlCenterIml struct {
// 一個主題對應(yīng)多個訂閱者
TopicEvent map[string]Subscribers
}
func (c *ControlCenterIml) Subscribe(subcriberId, topic string, f func(e *Event)) {
if c.TopicEvent == nil {
return
}
if v, ok := c.TopicEvent[topic]; !ok {
subscribers := make(Subscribers)
subscribers[subcriberId] = f
c.TopicEvent[topic] = subscribers
} else {
v[subcriberId] = f
c.TopicEvent[topic] = v
}
}
func (c *ControlCenterIml) Unsubscribe(subcriberId, topic string) {
if c.TopicEvent == nil {
return
}
if subscribers, ok := c.TopicEvent[topic]; !ok {
return
} else {
delete(subscribers, subcriberId)
}
}
func (c *ControlCenterIml) Publish(topic string, e *Event) bool {
if c.TopicEvent == nil {
return false
}
var subscribers Subscribers
if v, ok := c.TopicEvent[topic]; !ok {
return false
} else {
subscribers = v
}
for k, v := range subscribers {
fmt.Println(k)
v(e)
}
return true
}
func main() {
subPub := ControlCenterIml{TopicEvent: make(map[string]Subscribers)}
subPub.Subscribe("123", "study", func(e *Event) {
fmt.Println("recv something:", e.Message)
})
subPub.Subscribe("124", "study", func(e *Event) {
fmt.Println("recv something:", e.Message)
})
subPub.Publish("study", &Event{Message: "hello lili"})
subPub.Unsubscribe("123", "study")
subPub.Publish("study", &Event{Message: "hello vivi"})
}