MVC Pattern
在我們的MVC模式中,controller作為調解人與view和model進行互動: view層觸發了動作通過controller修改了model,model的更新又通過controller進行了view的更新。
那麼在觀察者模式中,我們可以把model變換為可觀察的物件,把view變為觀察者。當被觀察者發生改變的時候,它就通知觀察者它此時的狀態,隨後觀察者根據此狀態進行自我的更新。這使得資料流變成了單向資料流。在這裡,controller不在更新view,而是view根據model進行自我更新。
在這裡我們把model作為我們的被觀察者介面,把view作為觀察者介面。與事件監聽介面通過DOM API來實現不同的是,此時的事件介面更加的抽象。所有方法的命名不是特別的重要,重要的是他們怎麼進行互動。
我們給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
函式仍然是像前面一篇文章一樣是可以工作的。
提升 在我們以上的討論中還是有幾點提升或者是更簡潔的實現:
Getter
和Setter
邏輯是可以包含在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()
。