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()。