設(shè)計(jì)模式之觀察者模式 2022-03-10

觀察者模式做什么

觀察者模式實(shí)現(xiàn)了對象之間的多對1依賴薪介,一旦新數(shù)據(jù)發(fā)布所有訂閱者都將收到通知并自動(dòng)更新

觀察者模式核心——interface

觀察者模式-核心就是interface接口

為什么要設(shè)計(jì)接口?看個(gè)案例
現(xiàn)在有一個(gè)主題發(fā)布者Subject s,被一個(gè)訂閱者Observer1 o1訂閱嚣崭。那么偽代碼如下:

class Subject:
    string topic
    string content
    string getTopic() { return topic}
    string getContent() {return content }
    void notifyAll() {
        string t = getTopic()
        string c = getContent()
        // 調(diào)用o1的updateMyself方法更新o1
        o1.updateMyself(t, c)
    }
    
class Observer1:
    void updateMyself(string topic, content) { ... }

某一天來了另外一個(gè)訂閱者Observer2 o2肥缔,新增代碼后變成:

class Subject:
    string topic
    string content
    string getTopic() { return topic}
    string getContent() {return content }
    void notifyAll() {
        string t = getTopic()
        string c = getContent()
        // 調(diào)用o1的updateMyself方法更新o1
        o1.updateMyself(t, c)
        // 調(diào)用o2的updateMe方法更新o2
        o2.updateMe(t, c)
    }
    
class Observer1:
    void updateMyself(string topic, content) { ... }

class Observer2:
    void updateMe(string topic, content) { ... }

由于Observer1和Observer2的更新方法不同芒划,每來一個(gè)訂閱者逐沙,Subject就不得不去適配該訂閱者的更新方法:

  • 假設(shè)不斷地有新類型的訂閱者過來,那么就要不斷地往Subject.notifyAll里面加代碼
  • 假設(shè)不斷地有訂閱者退出搏讶,那么就要不斷地往Subject.notifyAll里面刪代碼
  • 假設(shè)不斷地有訂閱者修改了它們的update方法佳鳖,那么就要不斷地往Subject.notifyAll里面改代碼
  • 不支持運(yùn)行時(shí)訂閱者的增減

這就是面向每個(gè)訂閱者的update()實(shí)現(xiàn)的編程,非常死板

沒有接口媒惕,就沒有協(xié)議系吩。各玩各的一套,只能是疲于適配妒蔚。因此穿挨,觀察者需統(tǒng)一實(shí)現(xiàn)update接口
只要所有觀察者將更新方法都統(tǒng)一一個(gè)函數(shù)簽名月弛,那對于Subject就非常友好了。Subject無需再關(guān)注每個(gè)訂閱者具體的更新方法科盛,因?yàn)橛嗛喺叩母路椒〒碛邢嗤暮瘮?shù)簽名帽衙。代碼如下:

class Subject:
    string topic
    string content
    string getTopic() { return topic}
    string getContent() {return content }
    void notifyAll() {
        string t = getTopic()
        string c = getContent()
        // 調(diào)用o1的update方法更新o1
        o1.update(t, c)
        // 調(diào)用o2的update方法更新o2
        o2.update(t, c)
    }

// 觀察者interface
interface Observer {
    void update(string topic, content)
}

class Observer1 implements Observer:
    void update(string topic, content) { ... }

class Observer2 implements Observer:
    void update(string topic, content) { ... }

但是,現(xiàn)在實(shí)現(xiàn)/取消訂閱贞绵,還是通過在Subject.notifyAll()里面增加/刪除代碼來實(shí)現(xiàn)厉萝,擴(kuò)展性很差
于是,Subject還需要一個(gè)注冊訂閱者的方法榨崩,需要一個(gè)刪除訂閱者的方法:

class Subject:
    string topic
    string content
    list observers

    string getTopic() { return topic}
    string getContent() {return content }

    void notifyAll() {
        string t = getTopic()
        string c = getContent()
        for each o in observers {
            o.update(t, c)
        }
    }

    void addObserver(Observer o) {
        observers.add(o)
    }

    void removeObserver(Observer o) {
        observers.remove(o)
    }

// 觀察者interface
interface Observer {
    void update(string topic, content)
}

class Observer1 implements Observer:
    void update(string topic, content) { ... }

class Observer2 implements Observer:
    void update(string topic, content) { ... }

好了谴垫,這個(gè)時(shí)候,如果這些訂閱者還想去訂閱其他subject呢母蛛?自然的思路是翩剪,其他subject的類也實(shí)現(xiàn)void addObserver(Observer o)void removeObserver(Observer o)void notifyAll()——這又回到接口上來了彩郊。主題類也可以抽象成接口以便于擴(kuò)展

// 主題interface
interface Subject{
    void notifyAll()
    void addObserver(Observer o) 
    void removeObserver(Observer o)
}

// 觀察者interface
interface Observer {
    void update(string topic, content)
}

class ConcreteSubject implements Subject:
    string topic
    string content
    list observers

    string getTopic() { return topic}
    string getContent() {return content }

    void notifyAll() {
        string t = getTopic()
        string c = getContent()
        for each o in observers {
            o.update(t, c)
        }
    }

    void addObserver(Observer o) {
        observers.add(o)
    }

    void removeObserver(Observer o) {
        observers.remove(o)
    }


class Observer1 implements Observer:
    void update(string topic, content) { ... }

class Observer2 implements Observer:
    void update(string topic, content) { ... }

還有一個(gè)問題前弯,上面的所有例子中,訂閱者需要被通知什么信息焦辅,是通過Observer.update()的參數(shù)約定的博杖,例子中通知的數(shù)據(jù)是topic和content
如果部分訂閱者只需要topic椿胯,部分只需要content筷登,部分還需要額外的數(shù)據(jù)呢?那void update(string topic, content)就不滿足需求了
如何既保持統(tǒng)一的接口哩盲,又擁有各自的更新邏輯呢前方?——把整個(gè)"數(shù)據(jù)源"作為Observer.update()的參數(shù)

// 主題interface
interface Subject{
    void notifyAll()
    void addObserver(Observer o) 
    void removeObserver(Observer o)
    string getTopic()
    string getContent()
}

// 觀察者interface
interface Observer {
    // 把整個(gè)"數(shù)據(jù)源"拉過來,把整個(gè)subject拿過來廉油,想要什么數(shù)據(jù)自己內(nèi)部取
    void update(Subject s)
}

class ConcreteSubject implements Subject:
    string topic
    string content
    list observers

    string getTopic() { return topic}
    string getContent() {return content }

    void notifyAll() {
        string t = getTopic()
        string c = getContent()
        for each o in observers {
            o.update(this)
        }
    }

    void addObserver(Observer o) {
        observers.add(o)
    }

    void removeObserver(Observer o) {
        observers.remove(o)
    }


class Observer1 implements Observer:
    void update(Subject s) { 
        string t = s.getTopic()
        // update t ...
    }

class Observer2 implements Observer:
    void update(Subject s) { 
        string c = s.getContent()
        // update c ...
    }

小結(jié)

  • 協(xié)商好接口(interface)惠险,所有實(shí)例都統(tǒng)一按照interface規(guī)定的標(biāo)準(zhǔn)去交互,避免了不同對象的method之間的差異抒线,便于擴(kuò)展
    不同的對象按照interface去做各自的內(nèi)部實(shí)現(xiàn)班巩,它們將擁有相同的函數(shù)簽名(method signature),內(nèi)部卻是不同的函數(shù)體(method bodies, aka implementation)——暴露出去的是公共的嘶炭、統(tǒng)一的抱慌,內(nèi)部邏輯是私有的、獨(dú)占的

  • notify無外乎兩種方式:推和拉
    推:需要事先約定好observer需要的數(shù)據(jù)眨猎,subject直接推送這些數(shù)據(jù)
    拉:當(dāng)不同的observer需要的數(shù)據(jù)在類型和數(shù)量上存在差異時(shí)抑进,可以通過observer拉的方式:observer調(diào)用subject暴露的方法拉取自己所需的數(shù)據(jù)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市睡陪,隨后出現(xiàn)的幾起案子寺渗,更是在濱河造成了極大的恐慌匿情,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件信殊,死亡現(xiàn)場離奇詭異炬称,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)涡拘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門转砖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鲸伴,你說我怎么就攤上這事府蔗。” “怎么了汞窗?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵姓赤,是天一觀的道長。 經(jīng)常有香客問我仲吏,道長不铆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任裹唆,我火速辦了婚禮誓斥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘许帐。我一直安慰自己劳坑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布成畦。 她就那樣靜靜地躺著距芬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪循帐。 梳的紋絲不亂的頭發(fā)上框仔,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天,我揣著相機(jī)與錄音拄养,去河邊找鬼离斩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛瘪匿,可吹牛的內(nèi)容都是我干的跛梗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼柿顶,長吁一口氣:“原來是場噩夢啊……” “哼茄袖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嘁锯,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤宪祥,失蹤者是張志新(化名)和其女友劉穎聂薪,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蝗羊,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡藏澳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了耀找。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翔悠。...
    茶點(diǎn)故事閱讀 39,992評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖野芒,靈堂內(nèi)的尸體忽然破棺而出蓄愁,到底是詐尸還是另有隱情,我是刑警寧澤狞悲,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布撮抓,位于F島的核電站,受9級特大地震影響摇锋,放射性物質(zhì)發(fā)生泄漏丹拯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一荸恕、第九天 我趴在偏房一處隱蔽的房頂上張望乖酬。 院中可真熱鬧,春花似錦融求、人聲如沸咬像。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽施掏。三九已至,卻和暖如春茅糜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背素挽。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工蔑赘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人预明。 一個(gè)月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓缩赛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親撰糠。 傳聞我的和親對象是個(gè)殘疾皇子酥馍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評論 2 355

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