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

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

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

定义

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

设计原则

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

使用场景举例

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

分析

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

示例代码如下

       // 气象站(发布者)
    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),而后发布者通过调用订阅函数来给所有注册过的观察者发送消息。

最后

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

共勉。

发布了139 篇原创文章 · 获赞 203 · 访问量 69万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章