设计模式专栏(一) ——观察者模式

设计模式是被发现的,而非发明。

观察者模式(发布者-订阅者模式)

定义

观察者模式(又称发布者-订阅者模式)定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

设计原则

  • 找出程序中变化的方面,然后将其跟固定不变的方面相分离
  • 针对接口编程,而不是针对实现编程
  • 更多的使用组合,少用继承
  • 为了交互对象之间的松耦合设计努力,降低对象之间的互相依赖

使用场景举例

我们要实现一个气象站,当气象站的数据(气温、气压等)有所变动时,就通知所有的显示板更新显示的状态。
并且我们需要支持可以添加新的显示板。

分析

为了实现上述场景,我们需要将气象站设定为发布者,将显示板设定为订阅者(观察者),
发布者通过订阅者实现的订阅接口,可以随时向所有订阅者发布消息。

示例代码如下

       // 气象站(发布者)
    class WeatherData{
        constructor() {
            this.observerList=[]; //所有的观察者
            this.temp; //温度
            this.humidity;//湿度
            this.pressure;//气压
        }

        // 注册观察者
        registerObserver(observer){
            this.observerList.push(observer);
        }   
        // 删除观察者
        removeObserver(observer){
            var index = this.observerList.indexOf(observer);
            if(index>=0){
                this.observerList.splice(index,1);
            }
        }
        // 当状态改变时,调用此方法通知所有观察者   
        notifyObserver(){
            this.observerList.forEach(e=>{
                e.update(this.temp,this.humidity,this.pressure);
            })
        }
        // 修改气象站数据时,通知观察者
        setWeatherData(temp,humidity,pressure){
            this.temp = temp;
            this.humidity = humidity;
            this.pressure = pressure;
            this.notifyObserver();
        }
    }

    // 显示板(订阅者)
    class DispalyModul{
        constructor(weatherData) {
            this.temp; //温度
            this.humidity;//湿度
            this.pressure;//气压
            weatherData.registerObserver(this); //将订阅者注册到发布者上,当发布者进行发布信息时,即可实现订阅者收到信息
        }
        // 用来接收发布者消息
        update(temp,humidity,pressure){
            this.temp = temp;
            this.humidity = humidity;
            this.pressure = pressure;
            this.dispaly();
        }
        dispaly(){
            console.log(`显示板显示:温度-${this.temp},湿度-${this.humidity},气压-${this.pressure}`)
        }
    }
    // Temp显示板(订阅者)
    class DispalyTemp{
        constructor(weatherData) {
            this.temp; //温度
            this.humidity;//湿度
            this.pressure;//气压
            weatherData.registerObserver(this); //将订阅者注册到发布者上,当发布者进行发布信息时,即可实现订阅者收到信息
        }
        // 用来接收发布者消息
        update(temp,humidity,pressure){
            this.temp = temp;
            this.humidity = humidity;
            this.pressure = pressure;
            this.dispaly();
        }
        dispaly(){
            console.log(`温度显示板:温度-${this.temp}`)
        }
    }
    // Humidity显示板(订阅者)
    class DispalyHumidity{
        constructor(weatherData) {
            this.temp; //温度
            this.humidity;//湿度
            this.pressure;//气压
            weatherData.registerObserver(this); //将订阅者注册到发布者上,当发布者进行发布信息时,即可实现订阅者收到信息
        }
        // 用来接收发布者消息
        update(temp,humidity,pressure){
            this.temp = temp;
            this.humidity = humidity;
            this.pressure = pressure;
            this.dispaly();
        }
        dispaly(){
            console.log(`湿度显示板:湿度-${this.humidity}`)
        }
    }
    // Pressure显示板(订阅者)
    class DispalyPressure{
        constructor(weatherData) {
            this.temp; //温度
            this.humidity;//湿度
            this.pressure;//气压
            weatherData.registerObserver(this); //将订阅者注册到发布者上,当发布者进行发布信息时,即可实现订阅者收到信息
        }
        // 用来接收发布者消息
        update(temp,humidity,pressure){
            this.temp = temp;
            this.humidity = humidity;
            this.pressure = pressure;
            this.dispaly();
        }
        dispaly(){
            console.log(`气压显示板:气压-${this.pressure}`)
        }
    }

    let subject = new WeatherData();
    let observer = new DispalyModul(subject);
    let observerTemp = new DispalyTemp(subject);
    let observerHumidity = new DispalyHumidity(subject);
    let observerPressure = new DispalyPressure(subject);

    subject.setWeatherData(23.5,"45%",750);
    // 输出如下:
    // 显示板显示:温度-23.5,湿度-45%,气压-750
    // 温度显示板:温度-23.5
    // 湿度显示板:湿度-45%
    // 气压显示板:气压-750

    subject.removeObserver(observerHumidity); //删除湿度显示板

    subject.setWeatherData(31,"30%",950);
    // 输出如下:
    // 显示板显示:温度-31,湿度-30%,气压-950
    // 温度显示板:温度-31
    // 气压显示板:气压-950

我们使用JS来演示,其他语言思路相通,因JS中没有接口概念,所以此处违背了设计原则中 针对接口编程,而不是针对实现编程的思想
为了不违背针对接口编程的思想,此处应当将weatherData中的registerObserver、removeObserver、notifyObserver与dispalyModul中的update抽出写成接口实现。这样既可以保证订阅者必须实现update方法,又能同时保证代码具有松耦合的优点。

问题

可以看出,上述实现了观察者模式的代码中,有个明显的问题
发布者发布的消息,订阅者必须接受,不能做到订阅者根据自己的需求获取数据

这其实并不是问题,而是设计思路的不同。我们可以设计成当前这种向所有订阅者发布消息的模式,也可以设计成订阅者向发布者索取想要的数据,又或者也可以两者兼容。

例如,我们需要订阅者单独获取温度信息,我们只需要在发布者与订阅者中分别添加如下代码

    // 气象站(发布者)
    class WeatherData{
        //```
        getTemp(){
            return this.temp
        }
        //```
    }
    // Temp显示板(订阅者)
    class DispalyTemp{
        constructor(weatherData) {
            //```
            this.weatherData = weatherData; // 保存发布者
            weatherData.registerObserver(this); //将订阅者注册到发布者上,当发布者进行发布信息时,即可实现订阅者收到信息
        }
      // ```  
      getTemp(){
        return this.weatherData.getTemp();
      }
      // ```
    }
    let observerTemp = new DispalyTemp(subject);
    observerTemp.getTemp();
    // 输出 weatherData中的temp,未定义的时候 就是undefined

总结一下

观察者模式:发布者提供注册接口(registerObserver),所有人都可以通过此接口注册成为订阅者,同时需要实现统一的订阅函数(update),而后发布者通过调用订阅函数来给所有注册过的观察者发送消息。

最后

代码是死的,人是活的,写代码的过程中,我们会发现很多种设计模式的变种,依照设计思路的不同,需求的不同,我们要灵活的运用设计模式,我们最终的目的,是为了设计出弹性的、可复用的、可维护的项目。

共勉。

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

推荐阅读更多精彩内容