1、模式定义
观察者模式,又被称作是“发布-订阅者模式”或“消息机制”,它定义了一种依赖关系,解决了主体对象和观察者之间功能的耦合。
2、生活中的观察者模式
以卫星、中转站和飞机为例,简化成这样的一个模型:飞机飞行时,不断地向卫星发送位置信息,地面上的中转站从卫星上订阅飞机的信息,从而可以实时地监控到飞机的所在位置。在这个模型中,卫星就是观察者或者说是消息系统;飞机是被观察者。
卫星是观察者(也称为“消息系统”),对于观察者(消息系统)这个对象来说,它得包括三个方法,其一用于接收飞机发来的消息,其二用于向订阅了该飞机信息的中转站发送消息,其三用于取消中转站对飞机信息的订阅。除此之外,观察者(消息系统)对象还得提供一个消息容器,用于保存消息。
3、实现一个观察者对象(消息系统)
下面我们定义一个观察者(消息系统)对象,它包含三个方法和一个消息容器。基于这个观察者雏形,我们来实现它的三个方法。
首先来实现“注册订阅 regist”方法,该方法的作用是把订阅者注册的消息推入到消息队列中去。
其次来实现“消息发布 fire”方法,该方法的功能是当观察者发布一个消息时把所有订阅者订阅的消息一次性执行。
最后来实现“取消注册 remove”方法,该方法的功能是把订阅者注销的消息从消息队列中清除。
// 把观察者放在闭包中,当页面加载时就立即执行
var Observer = (function() {
// 防止消息队列暴露而被篡改,因此我们要把消息容器作为静态私有变量
var _messages = {};
return {
// 注册订阅
regist: function(type, fn) {
if (typeof _messages[type] === 'undefined') {
// 如果此消息不存在,则创建一个新的消息类型,并把消息放入到队列中去
_message[type] = [fn];
} else {
// 如果此消息存在,直接把消息追加到队列中去
_messages[type].push(fn);
}
},
// 消息发布
fire: function(type, args) {
if(!_messages[type]) return;
// 定义消息信息
var events = {
type: type,
args: args || {}
};
for (var i=0; i<_messages[type].length; i++) {
// 依次执行注册的消息对应的动作序列
_messages[type][i].call(this, events);
}
},
// 取消订阅
remove: function() {
if (_messages[type] instanceof Array) {
for (var i=_messages[type].length-1; i>=0; i--) {
// 如果存在该动作则从消息动作序列中移除相应动作
_messages[type][i] === fn && _messages[type].splice(i, 1);
}
}
}
}
})();
观察者对象(消息系统)创建成功之后,我们来小测一下:
// 订阅一条消息
Observer.regist('test', function(e) {
console.log(e.type, e.args.msg);
});
// 发布消息
Observer.fire('test', {msg: '传递参数'});
4、观察者模式 应用举例
// 外观模式:简化获取元素
function $(id) {
return document.getElementById(id);
}
// 工程师A
(function() {
// 追加一则消息
function addMsgItem(e) {
var text = e.args.text;
var ul = $('msg'); // 留言容器元素
var li = document.createElement('li'); // 创建内容容器元素
var span = document.createElement('span'); // 删除按钮
li.innerHTML = text;
span.onclick = function() {
ul.removeChild(li);
Observer.fire('removeCommentMessage', {num: -1});
}
li.appendChild(span);
ul.appendChild(li);
}
Observer.regist('addCommentMessage', addMsgItem);
})();
// 工程师B
(function() {
function changeMsgNum(e) {
var num = e.args.num;
$('msg-num').innerHTML = parseInt($('msg-num').innerHTML) + num;
}
Observer.regist('addCommentMessage', changeMsgNum).regist('removeCommentMessage', changeMsgNum);
})();
// 工程师C
(function() {
$('user-submit').onclick = function() {
var text = $('user-input');
if (text.value === '') return;
Observer.fire('addCommentMessage', {
text: text.value,
num: 1
});
text.value = '';
}
})();
图例
观察者模式就是这么神奇,各个模块之间耦合问题可以简单地被解决。属于哪个模块的功能方法只用写在原模块中,一点也不用担心其它模块是怎么实现的,只需要收发消息即可。
5、观察者模式 应用再举例
var Student = function(result) {
var that = this;
this.result = result;
this.say = function() {
console.log(that.result);
}
}
Student.prototype.answer = function(question) {
Observer.regist(question, this.say);
}
Student.prototype.sleep = function(question) {
console.log(this.result + ' ' + question + ' 已被注销');
Observer.remove(question, this.say);
}
var Teacher = function() {};
Teacher.prototype.ask = function(question) {
console.log('问题是: ' + question);
Observer.fire(question);
}
实现三个学生和一个老师之间的消息通信。小测一下:
var student1 = new Student('学生1回答问题');
var student2 = new Student('学生2回答问题');
var student3 = new Student('学生3回答问题');
student1.answer('什么是设计模式?');
student2.answer('简述观察者模式。');
student3.answer('简述外观模式。');
student2.sleep('休息了');
var teacher = new Teacher();
teacher.ask('什么是设计模式?');
6、小结
使用观察者模式,可以解决团队开发中的最重要的模块间通信问题,这是模块解耦的一种可行性方案,建立消息机制。
这样对于任意一个订阅者来讲,其它订阅者的变化不会影响到自身。对于每个订阅者来讲,其自身既可以是消息的发出者,也可以是消息的接受者,这都依赖于调用观察者对象的三个方法(订阅消息、注销消息、发布消息)。
2019-03-01
网友评论