1. 程式人生 > 其它 >觀察者模式於釋出訂閱模式的關係

觀察者模式於釋出訂閱模式的關係

觀察者模式於釋出訂閱模式的關係

在《JavaScript設計模式》中,對觀察者模式的定義:又被稱作釋出-訂閱者模式或訊息機制,定義了一中依賴關係,解決了主題於觀察者之間功能的耦合。

這裡的定義將觀察者和釋出-訂閱者,兩種模式稱作同一中,但是實際上觀察者模式和釋出訂閱模式還是有一些區別的。

下面是一段書中作為觀察者模式的程式碼:

NoDatavar Observer = (function () {
   let _messages = {}
   return {
       regist: function(type, fn) {
           if (typeof _messages[type] === 'undefined') {
               _messages[type] = [fn]
           }else {
               _messages[type].push(fn)
           }
       },
       fire: function(type, args) {
           if (!_messages[type]) { 
               return 
           }
           let events = {
               type: type,
               args: args || {}
           }
           for (let i = 0; i < _messages[type].length; i++) {
               _messages[type][i].call(this, events)
           }
       },
   	   remove: function(type, fn) {
           if (_messages[type] instanceof Array) {
               for (let i = _messages[type].length - 1; i >= 0; i--) {
                   _messages[type][i] === fn && _messages[type].splice(i, 1)
               }
           }
       }
   }
})()

// 訂閱者
// 學生類
const Student = function(result) {
    let that = this
    that.result = result
    that.say = function() {
        console.log(that.result)
    }
}

Student.prototype.answer = function(question) {
    Observer.regist(question, this.say)
}

Student.prototype.sleep = function(question) {
    console.log(this.result + ':' + question + '已被登出')
    Observer.remove(question, this.say)
}

// 釋出者
// 教師類
const Teacher = function() {}
Teacher.prototype.ask = function(question) {
    console.log('問題是:' + question)
    Observer.fire(question)
}

const student1 = new Student('學生1回答問題')
const student2 = new Student('學生2回答問題')
const student3 = new Student('學生3回答問題')

student1.answer('什麼是設計模式')
student1.answer('什麼是觀察者模式')
student2.answer('什麼是設計模式')
student3.answer('什麼是設計模式')
student3.answer('什麼是觀察者模式')


const teacher = new Teacher()
teacher.ask('什麼是設計模式')
student3.sleep('什麼是觀察者模式')
teacher.ask('什麼是觀察者模式')

//問題是:什麼是設計模式
//學生1回答問題
//學生2回答問題
//學生3回答問題
//學生3回答問題:什麼是觀察者模式已被登出
//問題是:什麼是觀察者模式
//學生1回答問題

  但實際上這段程式碼使用的應該是釋出訂閱模式。接下來詳細瞭解一下二者的關係。

一、觀察者模式

  在物件之間定義一個一對多的依賴,當物件自身狀態改變的時候,會自動通知給關心該狀態的觀察者。解決了主體物件與觀察者之間功能的耦合,即一個物件的狀態改變給其他物件通知的問題。

下面是一個簡單的觀察者模式例子:

// 被觀察者
class Subject {
    constructor() {
        // 觀察者列表
        this.observers = []
    }
    // 新增
    add(observer) {
        this.observers.push(observer)
    }
    // 刪除
    remove(observer) {
        let index = this.observers.indexOf(observer)
        if (index > -1) {
            this.observers.splice(index, 1)
        }
    }
    //	通知
    notify() {
        for (let observer of this.observers) {
            observer.update()
        }
    }
}

// 觀察者類
class Observer {
    constructor(name) {
        this.name = name
    }
    // 觀察目標更新時
    update() {
        console.log(`${this.name},專案又有新bug了`)
    }
}

let subject = new Subject()

let obs1 = new Observer('前端開發')
let obs2 = new Observer('後端開發')

subject.add(obs1)
subject.add(obs2)

subject.notcify()

// 前端開發,專案又有新bug了
// 後端開發,專案又有新bug了

  在以上程式碼中可以很明顯的看出有兩個類,一個是Subject是被觀察者類,建構函式中有一個用來存放觀察者的列表,當被觀察者發生狀態改變時,通過遍歷被觀察者列表來通知觀察者。新增和刪除方法用來新增和刪除觀察者,notify用來通知觀察者。通過建立例項,呼叫被觀察者的notify方法來模擬被觀察者狀態改變時的事件呼叫。實現了簡單的觀察者模式。

二、釋出訂閱模式

  也是定義一對多的依賴關係,物件狀態改變後,通知給所有關心這個狀態的訂閱者。釋出訂閱模式有訂閱的動作,可以不和觀察者直接產生聯絡,只要能訂閱上關心的狀態即可,通常可用第三媒介來做,而釋出者也會利用第三媒介來通知訂閱者。

以下是一個釋出訂閱者示例:

class PubSub {
    constructor() {
        this.list = {}
    }
    subscribe(key, fn) {
        if (!this.list[key]) {
            this.list[key] = []
        }
        this.list[key].push(fn)
    }
    publish(key, ...arg) {
        for (let fn of this.list[key]) {
            fn.call(this, ...arg)
        }
    }
    unSubscribe(key, fn) {
        let fnList = this.list[key]
        if (!fnList) {
            return false
        }
        if (!fn) {
            fnList && (fnList.length = 0)
        } else {
            let index = fnList.indexOf(fn)
            fnList.splice(index, 1)
        }
    }
}

const pubSub = new PubSub()

pubSub.subscribe('onwork', time => {
    console.log(`上班時間:${time}`)
})
pubSub.subscribe('offwork', time => {
    console.log(`下班時間:${time}`)
})
pubSub.subscribe('launch', time => {
    console.log(`午飯時間:${time}`)
})

pubSub.publish('onwork', '8:30')
pubSub.publish('launch', '12:00')
pubSub.publish('offwork', '17:30')

// 上班時間:8:30
// 午飯時間:12:00
// 下班時間:17:30

  上面的程式碼就是釋出訂閱最重要的部分,雖然沒有明顯的實現觀察者類和被觀察者類,但觀察者和被觀察者在釋出訂閱模式中本來就不會有直接關聯,都只需呼叫以上程式碼(事件匯流排)中的釋出和訂閱方法即可。這樣的程式碼可以被提取出來,可以被重複使用,可以被多個觀察者和被觀察者使用。

三、兩者之間的異同

1、從概念上沒什麼不同,都是在解決物件之間解耦的問題,通過事件的方式在某個時間點進行觸發,監聽這個事件的訂閱者可以進行相應的操作。

2、從實現上來說,觀察者模式對訂閱事件的訂閱者通過釋出自身來維護,後續的一系列操作都需要通過釋出者完成;釋出訂閱模式是訂閱者和釋出者有一個事件匯流排,操作都要經過事件匯流排完成。

3、在觀察者模式中,觀察者知道被觀察的主體,主體也維護觀察者的記錄。在釋出訂閱模式中,釋出者和訂閱者不需要知道彼此,他們通過事件匯流排來實現通訊。觀察者模式進一步抽象,能抽出的公共程式碼就是事件匯流排。

4、觀察者模式是鬆耦合的,釋出訂閱模式,完全不耦合。

5、觀察者大多數主要以同步方式實現,釋出訂閱模式大多數是非同步模式。

6、觀察者模式基本用於單個應用內部,釋出訂閱模式更多的是跨應用的模式

Vue2中通過攔截資料進行依賴收集,等待資料變更時,通知依賴的Watcher進行元件更新。

  通過使用Object.defineReactive()方法,對資料物件新增get()方法進行攔截,收集依賴,在通過新增set()方法,對修改資料進行攔截,通知依賴更新,從而達到資料的響應式。在此過程中也使用了釋出訂閱的模式,事件匯流排就是用來收集和通知更新依賴的Dep()函式。

// src/observer/dep.js

export default class Dep{
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;
  constructor () {
    this.id = uid++
    this.subs = []
  }

  addSub(sub) {
    this.subs.push(sub)
  }

  removeSub(sub) {
    romove(this.subs, sub)
  }

  depend() {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify() {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}