设计模式作为解决软件设计中常见问题的可复用方案,为我们在软件开发中解决常见的问题提供了非常重要的参考,在前端开发中,了解并掌握常用设计模式将有助于我们日常的开发工作。
什么是Oberver(观察者)模式《设计模式:可复用面向对象软件基础》一书中对观察者模式做了如下描述:一个或多个观察者对目标的状态感兴趣,他们通过将自己依附在目标对象上一遍注册所有感兴趣的内容,目标状态发生改变并且观察者可能对这些兴趣变更的感兴趣,就会发送一个通知消息,调用每个观察者的更新方法。当观察者不对内容感兴趣,他可以简单的从中分离出去。
概念性的东西就是个磨人的小妖精,叭叭说了一大截,还是一脸懵的状态。。。好吧,总结一下:
简单说来就是:Observer(观察者)模式定义了对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新或删除。而这种依赖通知,本质上就是联动触发的结果。
嗯。。。也许你会说:“说的很有道理,虽然还是不知道说了什么...”。
好吧,talk is cheap, let me show you the code
,让我把这段话转成代码来看。。。
既然有观察者,那么就一定有被观察者,前面我们已经提到过,观察者与被观察者之间的消息广播机制,本质上是一种联动触发的结果,并且观察者与被观察者之间,是一种一对多的关系,为了实现这种关系,我们首先需要去了解几个名词:
-
Subject (被观察的目标集)
维护一系列的观察者,方便用来添加、删除或通知观察者更新
-
Observer (观察者)
为那些在目标状态改变是需要获得通知的对象提供一个更新的接口
-
ConcreateSubject (单个被观察的具体目标)
状态发生改变时,向Observer发出通知,并存储当前目标的状态
-
ConcreateObserver (具体的观察者)
存储一个指向ConcreateSubject的引用,具体的去实现observer的更新接口,以实现自身状态与目标状态的一致
首先,我么来实现Subject类,它提供了三个方法,分别是添加观察者、删除观察者和通知已经注册的观察者更新
var Subject = function () {
// 用来保存注册的观察者对象
this.observers = [];
};
Subject.prototype = {
// 注册观察者对象
add: function (observer) {
this.observers.push(observer);
},
// 删除观察者对象
remove: function (observer) {
for (var i = 0, len = this.observers.length; i < len; i++) {
if (observer = this.observers[i]) {
this.observers.splice(i, 1);
return true;
}
}
return false;
},
// 通知所有注册的观察者对象
update: function () {
var observer;
for (var i = 0, len = this.observers.length; i < len; i++) {
observer = this.observers[i];
observer.update(this);
}
}
};
接下来,实现observer类,它提供一个update更新的方法:
var Observer = function () {};
Observer.prototype.update = function (data) {
// 执行具体的更新逻辑
console.log(`observer update ${data}`)
};
这样,我们就从一个抽象的层面简单的实现了观察者模式的雏形,巴特,这有什么用呢,似乎我们依然不知道它在实际代码中如何体现的...
Observer (观察者)具体案例实现我们以一个订阅报纸的经典案例来实践观察者模式的代码应用:
在这个案例中,出版社相当于一个被观察者,属于目标对象,读者去订阅报纸,当报社报纸更新后,要自动通知订阅报纸的读者更新内容。
首先实现主题对象:
function Subject() {
this.readers = [];
}
Subject.prototype = {
// 报纸的读者需要向报社订阅,先要注册
add: function (reader) {
this.readers.push(reader);
},
remove: function (reader) {
for (var i = 0, len = this.readers.length; i < len; i++) {
if (reader === this.readers[i]) {
this.readers.splice(i, 1);
return;
}
}
},
update: function (content) {
for (var i = 0, len = this.readers.length; i < len; i++) {
this.readers[i].update(content);
}
}
};
// 报纸对象,具体的目标实现
function Paper() {
Paper.super.constructor.call(this);
this.content = '';
}
Object.assign(Paper, Subject);
// 获取报纸的具体内容
Paper.prototype.getContent = function () {
return this.content;
};
// 设置报纸的具体内容
Paper.prototype.setContent = function (content) {
this.content = content;
// 通知所有的读者
this.update(content);
};
接下来,实现observer相关:
// 观察者
function Observer() {}
Observer.prototype.update = function () {};
function Reader() {
this.name = '';
}
Object.assign(Reader, Observer);
Reader.prototype.update = function (content) {
// 以前需要到目标对象中获取自己需要的数据,现在是直接接受传入的数据
console.log(this.name + 'update:' + content);
};
最后,触发报纸更新:
// 使用
var subject = new Paper();
var reader1 = new Reader();
reader1.name = 'zhangsan';
var reader2 = new Reader();
reader2.name = 'lisi';
subject.add(reader1);
subject.add(reader2);
subject.setContent('内容更新');
在代码中,首先我们定义了Subject类,它提供了对读者(观察者)的注册、删除和更新事件,并且定义了具体主题ConcreateSubject模型Paper,它除了实现Subject的方法外,还定义了自己的setContent和getContent两个自定义方法,用来触发具体目标的更改。
然后,我们定义了观察者Observer和读者Reader类,并且Reaer实现了Observer的更新方法Update,并且暴露除了自己的name属性,这样方便读者定义自己的方法或者属性值
在代码执行阶段,我们首先实例化了paper对象和读者集,并分别将读者注册到subject中,标识读者订阅,这样,当具体的报纸内容更新后 subject.setContent('内容更新')
,也就是当报纸更新后,订阅报纸的两个读者会自动接收到更新的内容。
-
优点
-
解耦,观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体观察者列表,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。
-
由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。如果被观察者和观察者都被扔到一起,那么这个对象必然跨越抽象化和具体化层次。
- 观察者模式支持广播通讯。被观察者会向所有的登记过的观察者发出通知。
-
-
缺点
-
运行效率较低,一个被观察者,多个观察者时,开发代码和调试会比较复杂,若其中一个观察者卡壳,会影响到此观察者后面的观察者执行,影响整体的执行。
-
如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。
-
如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。
- 虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。
-
设计模式是解决软件设计中常见问题的可复用方案,它是由很多开发人员总结出的经验,我们可以借鉴这些模式解决开发中棘手的问题。
本章节我们着重从代码的角度来实现了一个简单的观察者模式,但这并不完全,实际上,观察者模式的模型细分可分为推、拉两种模式,而在本文中,我们只拿推模型来作为理解观察者模式的案例。
下一节,将着重介绍观察者的另一种“拉模型”实现。
感谢这个时代,让我们可以站在巨人的肩膀上,窥探程序世界的宏伟壮观,我愿以一颗赤子心,踏遍程序世界的千山万水!愿每一个行走在程序世界的同仁,都活成心中想要的样子,加油。