觀察者模式

概念

觀察者模式定義了一種'一對多'的得依賴關(guān)系靴寂, 讓多個觀察者對象同時監(jiān)聽某一個主題對象。這個主題對象在狀態(tài)發(fā)生變化時, 會通知所有觀察者對象馆纳, 使它們能夠根據(jù)根據(jù)變化做一些事情邻遏。

具體實現(xiàn):

  1. 定義一個主題接口糠亩, 接口方法有:添加主題虐骑, 刪除主題, 通知函數(shù)
  2. 定義一個觀察者接口赎线, 接口方法:OnNotify 被通知
  3. 實現(xiàn)主題接口廷没, 主題接口保存一個觀察者列表
  4. 實現(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ū)別

  1. 訂閱發(fā)布模式, 發(fā)布訂閱模式相比觀察者模式多了個事件通道孙援,事件通道作為調(diào)度中心,管理事件的訂閱和發(fā)布工作扇雕,徹底隔絕了訂閱者和發(fā)布者的依賴關(guān)系拓售。即訂閱者在訂閱事件的時候,只關(guān)注事件本身镶奉,而不關(guān)心誰會發(fā)布這個事件础淤;發(fā)布者在發(fā)布事件的時候,只關(guān)注事件本身哨苛,而不關(guān)心誰訂閱了這個事件鸽凶。
  2. 觀察者模式有兩個重要的角色,即目標(biāo)和觀察者建峭。在目標(biāo)和觀察者之間是沒有事件通道的玻侥。一方面,觀察者要想訂閱目標(biāo)事件亿蒸,由于沒有事件通道使碾,因此必須將自己添加到目標(biāo)(Subject) 中進行管理;另一方面祝懂,目標(biāo)在觸發(fā)事件的時候,也無法將通知操作(notify) 委托給事件通道拘鞋,因此只能親自去通知所有的觀察者砚蓬。

概念

觀察者模式定義了一種'一對多'的得依賴關(guān)系, 讓多個觀察者對象同時監(jiān)聽某一個主題對象盆色。這個主題對象在狀態(tài)發(fā)生變化時灰蛙, 會通知所有觀察者對象祟剔, 使它們能夠根據(jù)根據(jù)變化做一些事情。

具體實現(xiàn):

  1. 定義一個主題接口摩梧, 接口方法有:添加主題物延, 刪除主題, 通知函數(shù)
  2. 定義一個觀察者接口仅父, 接口方法:OnNotify 被通知
  3. 實現(xiàn)主題接口叛薯, 主題接口保存一個觀察者列表
  4. 實現(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ū)別

  1. 訂閱發(fā)布模式, 發(fā)布訂閱模式相比觀察者模式多了個事件通道衷恭,事件通道作為調(diào)度中心,管理事件的訂閱和發(fā)布工作纯续,徹底隔絕了訂閱者和發(fā)布者的依賴關(guān)系随珠。即訂閱者在訂閱事件的時候,只關(guān)注事件本身猬错,而不關(guān)心誰會發(fā)布這個事件窗看;發(fā)布者在發(fā)布事件的時候,只關(guān)注事件本身倦炒,而不關(guān)心誰訂閱了這個事件显沈。
  2. 觀察者模式有兩個重要的角色,即目標(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"})
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末召边,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子裹驰,更是在濱河造成了極大的恐慌隧熙,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幻林,死亡現(xiàn)場離奇詭異贞盯,居然都是意外死亡,警方通過查閱死者的電腦和手機沪饺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門躏敢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人整葡,你說我怎么就攤上這事件余。” “怎么了遭居?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵啼器,是天一觀的道長。 經(jīng)常有香客問我俱萍,道長端壳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任枪蘑,我火速辦了婚禮损谦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘岳颇。我一直安慰自己照捡,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布赦役。 她就那樣靜靜地躺著麻敌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪掂摔。 梳的紋絲不亂的頭發(fā)上术羔,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機與錄音乙漓,去河邊找鬼级历。 笑死,一個胖子當(dāng)著我的面吹牛叭披,可吹牛的內(nèi)容都是我干的寥殖。 我是一名探鬼主播玩讳,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嚼贡!你這毒婦竟也來了熏纯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤粤策,失蹤者是張志新(化名)和其女友劉穎樟澜,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叮盘,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡秩贰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了柔吼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毒费。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖愈魏,靈堂內(nèi)的尸體忽然破棺而出觅玻,到底是詐尸還是另有隱情,我是刑警寧澤蝌戒,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布串塑,位于F島的核電站,受9級特大地震影響北苟,放射性物質(zhì)發(fā)生泄漏桩匪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一友鼻、第九天 我趴在偏房一處隱蔽的房頂上張望傻昙。 院中可真熱鬧,春花似錦彩扔、人聲如沸妆档。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贾惦。三九已至,卻和暖如春敦捧,著一層夾襖步出監(jiān)牢的瞬間须板,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工兢卵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留习瑰,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓秽荤,卻偏偏與公主長得像甜奄,于是被迫代替她去往敵國和親柠横。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容