原生實現的觀察者模式(Observer Model)

JingLMalan發表於2018-10-14

MVC Pattern

在我們的MVC模式中,controller作為調解人與view和model進行互動: view層觸發了動作通過controller修改了model,model的更新又通過controller進行了view的更新。

MVC pattern

那麼在觀察者模式中,我們可以把model變換為可觀察的物件,把view變為觀察者。當被觀察者發生改變的時候,它就通知觀察者它此時的狀態,隨後觀察者根據此狀態進行自我的更新。這使得資料流變成了單向資料流。在這裡,controller不在更新view,而是view根據model進行自我更新。

原生實現的觀察者模式(Observer Model)

在這裡我們把model作為我們的被觀察者介面,把view作為觀察者介面。與事件監聽介面通過DOM API來實現不同的是,此時的事件介面更加的抽象。所有方法的命名不是特別的重要,重要的是他們怎麼進行互動。

原生實現的觀察者模式(Observer Model)

我們給model類一個notifyAll方法,給view一個update方法。我們也給model一個用來註冊觀察者的方法叫做registerOberver,在model類中有一個observers的陣列,用來存放註冊的觀察者。一旦model和view例項化之後,我們使用registerObserver來註冊view,這樣的話,無論什麼時候當controller改變了model,我們就可以再model上呼叫notifyAll()方法,該方法此時就迭代所有的observers,在每一個obsever上呼叫update()方法,然後把model的狀態作為引數傳給view。

Model

function Model() {
    const self = this;
    this.heading = "hello";
    // 觀察者集合
    this.observers = [];
    // 新增觀察者到觀察者集合
    this.registerObserver = function(observer) {
        self.observers.push(observer);
    }
    // 迭代觀察者,在每一個觀察者上呼叫update()方法
    this.notifyAll = function() {
        self.observers.forEach(function(observer) {
        observer.update(self);
    }
}
複製程式碼

View

function View(controller) {
    this.controller = controller;
    this.heading = document.querySelector("#heading");
    this.heading.addEventListener('click',controller);
    this.update = function(data) {
        this.heading.innerText = data.heading;
    }
    this.controller.model.registerObserver(this);
}
複製程式碼

現在與MVC中通過controller來改變文字不同的是,view是在model更新之後進行的自我更新。我們的main函式仍然是像前面一篇文章一樣是可以工作的。

提升 在我們以上的討論中還是有幾點提升或者是更簡潔的實現:

  • GetterSetter邏輯是可以包含在model中的,這樣的話我們就在model中的值更新的時候通過這些邏輯來觸發通知。而不是在controller中手工實現。
  • 我們可以使用State模式來讓heading值抽象出來。這樣的話,我們就可以雙向觸發"hello"和"world"。而不是僅僅單向的觸發。

Object.defineProperties() 與簡單的生命getHeading()setHeading()方法不同的是,我們可以使用Object.defineProperties()方法來做一些後設資料程式設計。通過這種靜態物件方法,我們可以我們一同通過物件屬性的"dot"或者"bracket"方法(也就是object.property或者object["property"])或者賦值。你可以使用以上的方法來完成很多有意義的事情,但是這些超出了文章的範圍。

實際上我們使用的是Object.defineProperty()而不是Object.defineProperties(),因為我們僅僅關心的是一個屬性,但是兩者(Object.defineProperty()Object.defineProperties())原理上是相同的。現在我們在物件建立中這樣寫:

function Model() {
    const self = this;
    // heading不再是一個屬性,而是一個區域性環境變數
    let heading = "hello";
    // 觀察者集合
    this.observers = [];
    // 新增觀察者到觀察者集合
    this.registerObserver = function(observer) {
        self.observers.push(observer);
    }
    // 迭代觀察者,在每一個觀察者上呼叫update()方法
    this.notifyAll = function() {
        self.observers.forEach(function(observer) {
        observer.update(self);
    }
    // 通過this,這是我們想影響的object.
    Object.defineProperty(this,"heading",{
    get: function() {return heading;},
    set: function(value) {
        heading = value;
        // 在賦值函式上呼叫notifyAll
        this.notifyAll();
    }
    })
}
複製程式碼

我們在構造器中呼叫,在目標中傳入this,'heading'作為物件的值/鍵的名字,我們使用這個獲取或者賦值,定義get()set()把物件變為了可配置的物件。注意到與直接在this上直接定義heading屬性不同的是,我們定於了一個範圍環境變數,使得可配置的物件通過獲取。現在我們可以用setter呼叫notifyAll,所以無論什麼時候我們給heading賦值,View都是可以更新的。

(如果你對Python熟悉的話,這和在類定義中的_getitem__setitem_相似)

這樣也是可以工作的,我們需要移除self.model.notifyAll()

原文連結: The Observer Pattern with Vanilla JavaScript

相關文章