设计模式之观察者模式 2022-03-10

观察者模式做什么

观察者模式实现了对象之间的多对1依赖,一旦新数据发布所有订阅者都将收到通知并自动更新

观察者模式核心——interface

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

为什么要设计接口?看个案例
现在有一个主题发布者Subject s,被一个订阅者Observer1 o1订阅。那么伪代码如下:

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

某一天来了另外一个订阅者Observer2 o2,新增代码后变成:

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

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

由于Observer1和Observer2的更新方法不同,每来一个订阅者,Subject就不得不去适配该订阅者的更新方法:

  • 假设不断地有新类型的订阅者过来,那么就要不断地往Subject.notifyAll里面加代码
  • 假设不断地有订阅者退出,那么就要不断地往Subject.notifyAll里面删代码
  • 假设不断地有订阅者修改了它们的update方法,那么就要不断地往Subject.notifyAll里面改代码
  • 不支持运行时订阅者的增减

这就是面向每个订阅者的update()实现的编程,非常死板

没有接口,就没有协议。各玩各的一套,只能是疲于适配。因此,观察者需统一实现update接口
只要所有观察者将更新方法都统一一个函数签名,那对于Subject就非常友好了。Subject无需再关注每个订阅者具体的更新方法,因为订阅者的更新方法拥有相同的函数签名。代码如下:

class Subject:
    string topic
    string content
    string getTopic() { return topic}
    string getContent() {return content }
    void notifyAll() {
        string t = getTopic()
        string c = getContent()
        // 调用o1的update方法更新o1
        o1.update(t, c)
        // 调用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) { ... }

但是,现在实现/取消订阅,还是通过在Subject.notifyAll()里面增加/删除代码来实现,扩展性很差
于是,Subject还需要一个注册订阅者的方法,需要一个删除订阅者的方法:

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) { ... }

好了,这个时候,如果这些订阅者还想去订阅其他subject呢?自然的思路是,其他subject的类也实现void addObserver(Observer o)void removeObserver(Observer o)void notifyAll()——这又回到接口上来了。主题类也可以抽象成接口以便于扩展

// 主题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) { ... }

还有一个问题,上面的所有例子中,订阅者需要被通知什么信息,是通过Observer.update()的参数约定的,例子中通知的数据是topic和content
如果部分订阅者只需要topic,部分只需要content,部分还需要额外的数据呢?那void update(string topic, content)就不满足需求了
如何既保持统一的接口,又拥有各自的更新逻辑呢?——把整个"数据源"作为Observer.update()的参数

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

// 观察者interface
interface Observer {
    // 把整个"数据源"拉过来,把整个subject拿过来,想要什么数据自己内部取
    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 ...
    }

小结

  • 协商好接口(interface),所有实例都统一按照interface规定的标准去交互,避免了不同对象的method之间的差异,便于扩展
    不同的对象按照interface去做各自的内部实现,它们将拥有相同的函数签名(method signature),内部却是不同的函数体(method bodies, aka implementation)——暴露出去的是公共的、统一的,内部逻辑是私有的、独占的

  • notify无外乎两种方式:推和拉
    推:需要事先约定好observer需要的数据,subject直接推送这些数据
    拉:当不同的observer需要的数据在类型和数量上存在差异时,可以通过observer拉的方式:observer调用subject暴露的方法拉取自己所需的数据

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容