設計模式專欄(一) ——觀察者模式

設計模式是被發(fā)現(xiàn)的,而非發(fā)明。

觀察者模式(發(fā)布者-訂閱者模式)

定義

觀察者模式(又稱發(fā)布者-訂閱者模式)定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態(tài)時,它的所有依賴者都會收到通知并自動更新。

設計原則

  • 找出程序中變化的方面,然后將其跟固定不變的方面相分離
  • 針對接口編程,而不是針對實現(xiàn)編程
  • 更多的使用組合,少用繼承
  • 為了交互對象之間的松耦合設計努力,降低對象之間的互相依賴

使用場景舉例

我們要實現(xiàn)一個氣象站,當氣象站的數(shù)據(jù)(氣溫、氣壓等)有所變動時,就通知所有的顯示板更新顯示的狀態(tài)。
并且我們需要支持可以添加新的顯示板。

分析

為了實現(xiàn)上述場景,我們需要將氣象站設定為發(fā)布者,將顯示板設定為訂閱者(觀察者),
發(fā)布者通過訂閱者實現(xiàn)的訂閱接口,可以隨時向所有訂閱者發(fā)布消息。

示例代碼如下

       // 氣象站(發(fā)布者)
    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);
            }
        }
        // 當狀態(tài)改變時,調(diào)用此方法通知所有觀察者   
        notifyObserver(){
            this.observerList.forEach(e=>{
                e.update(this.temp,this.humidity,this.pressure);
            })
        }
        // 修改氣象站數(shù)據(jù)時,通知觀察者
        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); //將訂閱者注冊到發(fā)布者上,當發(fā)布者進行發(fā)布信息時,即可實現(xiàn)訂閱者收到信息
        }
        // 用來接收發(fā)布者消息
        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); //將訂閱者注冊到發(fā)布者上,當發(fā)布者進行發(fā)布信息時,即可實現(xiàn)訂閱者收到信息
        }
        // 用來接收發(fā)布者消息
        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); //將訂閱者注冊到發(fā)布者上,當發(fā)布者進行發(fā)布信息時,即可實現(xiàn)訂閱者收到信息
        }
        // 用來接收發(fā)布者消息
        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); //將訂閱者注冊到發(fā)布者上,當發(fā)布者進行發(fā)布信息時,即可實現(xiàn)訂閱者收到信息
        }
        // 用來接收發(fā)布者消息
        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中沒有接口概念,所以此處違背了設計原則中 針對接口編程,而不是針對實現(xiàn)編程的思想
為了不違背針對接口編程的思想,此處應當將weatherData中的registerObserver、removeObserver、notifyObserver與dispalyModul中的update抽出寫成接口實現(xiàn)。這樣既可以保證訂閱者必須實現(xiàn)update方法,又能同時保證代碼具有松耦合的優(yōu)點。

問題

可以看出,上述實現(xiàn)了觀察者模式的代碼中,有個明顯的問題
發(fā)布者發(fā)布的消息,訂閱者必須接受,不能做到訂閱者根據(jù)自己的需求獲取數(shù)據(jù)

這其實并不是問題,而是設計思路的不同。我們可以設計成當前這種向所有訂閱者發(fā)布消息的模式,也可以設計成訂閱者向發(fā)布者索取想要的數(shù)據(jù),又或者也可以兩者兼容。

例如,我們需要訂閱者單獨獲取溫度信息,我們只需要在發(fā)布者與訂閱者中分別添加如下代碼

    // 氣象站(發(fā)布者)
    class WeatherData{
        //```
        getTemp(){
            return this.temp
        }
        //```
    }
    // Temp顯示板(訂閱者)
    class DispalyTemp{
        constructor(weatherData) {
            //```
            this.weatherData = weatherData; // 保存發(fā)布者
            weatherData.registerObserver(this); //將訂閱者注冊到發(fā)布者上,當發(fā)布者進行發(fā)布信息時,即可實現(xiàn)訂閱者收到信息
        }
      // ```  
      getTemp(){
        return this.weatherData.getTemp();
      }
      // ```
    }
    let observerTemp = new DispalyTemp(subject);
    observerTemp.getTemp();
    // 輸出 weatherData中的temp,未定義的時候 就是undefined

總結一下

觀察者模式:發(fā)布者提供注冊接口(registerObserver),所有人都可以通過此接口注冊成為訂閱者,同時需要實現(xiàn)統(tǒng)一的訂閱函數(shù)(update),而后發(fā)布者通過調(diào)用訂閱函數(shù)來給所有注冊過的觀察者發(fā)送消息。

最后

代碼是死的,人是活的,寫代碼的過程中,我們會發(fā)現(xiàn)很多種設計模式的變種,依照設計思路的不同,需求的不同,我們要靈活的運用設計模式,我們最終的目的,是為了設計出彈性的、可復用的、可維護的項目。

共勉。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容