通常一个优秀的项目,会使用到很多的设计模式,这些设计模式在我们的解决方案中,会起到十分重要的作用,它的存在,能使项目的结构更加简洁、清晰、易于理解。
所以了解常用的设计模式,会让我们在日常开发更加得心应手,也会让团队沟通变得更加顺畅。
在《设计模式:可复用面向对象软件基础》一书中强调:当我们评估一个面向对象系统的质量时,所使用的方法之一就是判断系统的设计者是否强调了对象之间的公共系统关系。
那么在当前这个博文中,我介绍的就是 JavaScript 语言的 Observer设计模式。
初次接触 Observer设计模式,是在 Vue 的双向数据绑定中。其原理就是使用了 Observer-观察者 对数据进行劫持,然后让数据的读写全部处于监管之中。然后再使用发布-订阅模式的Publish-发布者通知到Subscribe-订阅者,对数据进行进行相应操作,比如双向数据绑定中的视图更新。
所以,实际上,Vue 数据的双向绑定,使用到了2个设计模式:Obsever模式和Publish–Subscribe模式。(有一些人认为,观察者模式和订阅观察)
就像发明一个新的东西一样,新的发明,必定会有其在现实世界的启发。
就好比雷达的发明,源自于蝙蝠的超声波回声定位。火箭的发明,源自于水母、墨鱼的反冲运动。
无独有偶,观察者模式也一样。在现实社会中,经常会出现观察者这个身份。比如书店,他们就是观察者。既然,有了观察者,那一定会有被观察者,他们就像是出版社。
书店“监视”各个出版社的新书,若出版社出现了新书(观察者在监视的行为),则书店获得了出版社的信息(被观察者的对象信息)和出版社的发布新书(被观察者的变化),然后书店将其发布的新书拿到,并告诉关注该类书的读者(观察者做出相应操作)
Observer模式的大致流程如上。话不多说,我们着手开始用 JavaScript 实现Observer模式。
Observer是一个监视被观察者的对象,它需要提供一个接口,对数据发生改变时,做出的行为进行定义。
在出现观察者关心的事件时,要同时通知多个观察者。
var Observer = function () {
};
// 观察者包含一个notify(通知)的回调方法
Observer.prototype.notify = function (info) {
// 在该方法内做出操作
console.log(`观察者发现 ${
info} 出现更改`);
};
var Subject = function (info) {
// 注册自己(被观察者)的信息
this.info = info;
// 已经注册的观察者的列表
this.observerList = [];
};
Subject.prototype = {
// 注册一个监视当前对象的观察者到观察者列表
register: function (observer) {
this.observerList.push(observer);
},
// 不在关注当前对象的观察者,注销观察者对象
remove: function (observer) {
// 获得当前观察者列表的长度
var listLength = this.observerList.length;
for (var i = 0; i < listLength; i++) {
if ((observer = this.observerList[i])) {
// 从观测者列表删除该观察者
this.observerList.splice(i, 1);
return true;
}
}
return false;
},
// 通知所有注册的观察者对象
update: function () {
var observer;
// 获得当前观察者列表的长度
var listLength = this.observerList.length;
for (var i = 0; i < listLength; i++) {
observer = this.observerList[i];
// 调用Observer对象的notify回调方法
observer.notify(this.info);
}
},
};
以上便是Observer模式的代码实现雏形,主要的流程和逻辑已经包括在其中了。但是在具体实现的时候,可以做出部分的调整。
比如:
observer.notify(this.info);
这一句代码可以不一定要传输对象自身的所有信息,放在出版社那,可能只需要传入一个新发布的书名。
那么接下来,我们按照之前的出版社-书店-读者的例子,进行一个具体实例的代码实现。
var BookStore = function () {
};
// 书店包含一个notify(通知)的回调方法
BookStore.prototype.notify = function (CompanyName,BookName) {
// 在该方法内做出操作
console.log(`读者快来,我这出了新书:${
CompanyName} 发版的 ${
BookName}`);
};
var Company = function (CompanyName) {
// 出版社名称
this.Name = CompanyName;
// 新的书名
this.NewBookName = "";
// 已经注册的书店的列表
this.bookStoreList = [];
};
Company.prototype = {
// 注册一个监视当前对象的书店到书店列表
register: function (bookStore) {
this.bookStoreList.push(bookStore);
},
// 不在关注当前对象的书店,注销书店对象
remove: function (bookStore) {
// 获得当前书店列表的长度
var listLength = this.bookStoreList.length;
for (var i = 0; i < listLength; i++) {
if ((bookStore == this.observerList[i])) {
// 从书店列表删除该观察者
this.bookStoreList.splice(i, 1);
return true;
}
}
return false;
},
// 通知所有注册的书店对象
publish: function (BookName) {
// 记录新发布书的名字
this.NewBookName = BookName;
var bookStore;
// 获得当前书店列表的长度
var listLength = this.bookStoreList.length;
for (var i = 0; i < listLength; i++) {
bookStore = this.bookStoreList[i];
// 调用BookStore对象的notify回调方法
bookStore.notify(this.Name,this.NewBookName);
}
},
};
// 初始化一个出版社——VicoHu出版社
var company = new Company("VicoHu出版社");
// 初始化三家商店
var bookStore1 = new BookStore("小胡书店");
var bookStore2 = new BookStore("二刘书店");
var bookStore3 = new BookStore("大黄书店");
// 重写bookStore3的notify方法实现发布新书时的不同操作
bookStore3.notify = function (CompanyName, BookName) {
// 在该方法内做出操作
console.log(
`${
this.Name}促销,新书上架:${
CompanyName} 发版的 ${
BookName},比其他的店都要便宜很多。`
);
};
// 将三家商店注册到出版社的商店列表里
company.register(bookStore1);
company.register(bookStore2);
company.register(bookStore3);
// 发布新书
company.publish("《挪威的森林》");
// 控制台输出如下内容
/**
* 小胡书店有促销活动!!!!! 读者快来,我这出了新书:VicoHu出版社 发版的 《挪威的森林》
* 二刘书店有促销活动!!!!! 读者快来,我这出了新书:VicoHu出版社 发版的 《挪威的森林》
* 大黄书店促销,新书上架:VicoHu出版社 发版的 《挪威的森林》,比其他的店都要便宜很多。
*/
在上面的具体实例的代码实现中,我们完成了书店的创建,和出版社的创建,并且将多个商店注册到出版社,并在出版社发布新书的时候,通知给所有的商店,让商店发出广告和通知给我们(读者)。
其实,从本质上而言,商店(观察者)是被动的,它需要依赖于出版社(被观察者)调用notify方法,来完成商店的广告发布。
但是,商店的广告的发布,可以通过重写 notify 方法来进行自定义的操作。
Observer 设计模式的优点和缺点都比较明显。
这世界上没有绝对通用的设计模式,都是根据任务的需求,选择合适的设计模式。
但是,倘若你连某个合适的设计模式都不知道,就会错过一些事半功倍的机会。
所以,只有不断的去学习,不断走出舒适圈,才能让自己走的更远,并成为一个编码更加高效、稳定的开发者。加油吧!各位!