- 参考:https://addyosmani.com/resources/essentialjsdesignpatterns/book/#prototypepatternjavascript
- 参考:JavaScript编程精粹
1. 观察者模式
- GOF的《设计模式》:
“One or more observers are interested in the state of a subject and register their interest with the subject by attaching themselves. When something changes in our subject that the observer may be interested in, a notify message is sent which calls the update method in each observer. When the observer is no longer interested in the subject’s state, they can simply detach themselves.”
- JavaScript编程精粹对这段话的翻译是
对目标状态感兴趣的一个或者多个观察者,通过将自身与目标关联在一起的形式进行注册。当目标出现观察者可能感兴趣的变化时,发出提醒消息,进而调用每个贯彻者的更新方法。如果观察者对目标不再感兴趣,只需要解除关联即可。
观察者模式,简单来说就是多个观察者观察一个目标,目标内容发生变化时,目标可以主动通知观察者,观察者可以在收到通知时乘机作出反应。
2. 实现
首先,明确两个概念:
- Subject(目标)
- Observer(观察者)
那么,被观察的目标如何在变化的时候通知观察者呢?
需要让被观察的目标保留对观察者的引用。
这样在目标内部发生变化的时候,目标可以通过保留的观察者的引用,来调用观察者。
首先,定义一个目标。
然后,利用目标保存观察者。
当目标发生变化的时候,通知观察者。
class Subject {
constructor() {
this.observer_list = [];
}
// 添加观察者
add_observer = function( observer ) {
this.observer_list.push( observer );
}
// 移除观察者
remove_observer = function( observer ) {
let observer_index = this.observer_list.findIndex((item)=>item === observer);
this.observer_list.splice(observer_index, 1);
console.log("移除观察者")
}
// 通知观察者
notify = function() {
for (let observer of this.observer_list) {
observer.update(); // 调用观察者的update函数
}
}
}
// 观察者1号
let Observer1 = {
update: function() {
console.log('观察者1号 被通知了')
}
}
// 观察者2号
let Observer2 = {
update: function() {
console.log('观察者2号 被通知了')
}
}
let mySubject = new Subject();
mySubject.add_observer(Observer1); // 添加观察者1号
mySubject.add_observer(Observer2); // 添加观察者2号
mySubject.notify();
mySubject.remove_observer(Observer1); // 移除观察者1号
mySubject.remove_observer(Observer2); // 移除观察者2号
结果:
观察者1号 被通知了
观察者2号 被通知了
移除观察者
移除观察者
具体通知内容,利用update传递参数即可。
class Subject {
constructor() {
this.observer_list = [];
}
// 添加观察者
add_observer = function( observer ) {
this.observer_list.push( observer );
}
// 移除观察者
remove_observer = function( observer ) {
let observer_index = this.observer_list.findIndex((item)=>item === observer);
this.observer_list.splice(observer_index, 1);
console.log("移除观察者")
}
// 通知观察者
notify = function() {
let args = arguments || [];
for (let observer of this.observer_list) {
observer.update(...args);
// observer.update();
}
}
}
let Observer1 = {
update: function() {
let args = arguments || [];
console.log('观察者1号 被通知了, 通知内容为 - ' , ...args)
}
}
let Observer2 = {
update: function() {
let args = arguments || [];
console.log('观察者2号 被通知了, 通知内容为 - ' , ...args)
}
}
let mySubject = new Subject();
mySubject.add_observer(Observer1);
mySubject.add_observer(Observer2);
mySubject.notify("...这里是目标...");
mySubject.remove_observer(Observer1);
mySubject.remove_observer(Observer2);
结果:
观察者1号 被通知了, 通知内容为 - ...这里是目标...
观察者2号 被通知了, 通知内容为 - ...这里是目标...
移除观察者
移除观察者
3. 参考一下
JavaScript编程精粹的大致示例:
let Subject = (function() {
function Subject() {
this.observer_list = [];
}
// 向内部列表添加观察者
Subject.prototype.add_observer = function ( obj ) {
this.observer_list.push( obj );
};
// 向内部列表删除观察者
Subject.prototype.remove_observer = function ( obj ) {
this.observer_list.splice( this.observer_list.findIndex( (item)=>item === obj ) , 1 );
}
Subject.prototype.notify = function () {
let args = arguments || [];
for (observer of this.observer_list) {
observer.update(...args); // 调用观察者的update方法
}
}
return Subject;
})()
function Tweeter() {
this.subject = new Subject();
this.add_observer = function ( observer ) {
this.subject.add_observer( observer );
}
this.remove_observer = function ( observer ) {
this.subject.remove_observer( observer );
console.log("Remove Observer");
}
this.fetchTweeter = function() {
let tweet = {
tweet: "This is one apple."
}
this.subject.notify(tweet);
}
}
// 添加观察者
let TweetUpdater = {
update: function() {
console.log( 'Updated Tweet - ' + arguments );
}
};
let TweetFollower = {
update: function() {
console.log( 'Following this tweet - ' + arguments );
}
};
let TweetApp = new Tweeter();
TweetApp.add_observer(TweetUpdater);
TweetApp.add_observer(TweetFollower);
TweetApp.fetchTweeter();
TweetApp.remove_observer(TweetUpdater);
TweetApp.remove_observer(TweetFollower);
结果:
Updated Tweet - [object Arguments]
Following this tweet - [object Arguments]
Remove Observer
Remove Observer
书上的"栗子",首先是定义了一个Subject函数,该函数内部包括一个观察者列表(observer_list),一个添加函数(add_observer_list),一个删除函数以及一个通知函数。
然后,定义了一个Tweeter函数,该函数有一个成员变量,为Subject类型。
再然后,定义了两个观察者,观测目标的变化。
TweetApp去fetchTweeter的时候,TweetUpdater、TweetFollower会得到通知。
栗子
利用一个按钮添加复选框,每个复选框都观测按钮。当,原有的复选框发生变化时,会通知按钮,然后按钮会通知新添加的所有复选框。(案例来自,《Learning JavaScript Design Patterns》,略作修改)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div>
<button id="addNewObserver">添加复选框</button>
<input id="mainCheckBox" type="checkbox">
<div id="observersContainer"></div>
</div>
<script>
var Subject = (function() {
function Subject() {
this.observer_list = [];
}
// 向内部列表添加观察者
Subject.prototype.add_observer = function ( obj ) {
this.observer_list.push( obj );
};
// 向内部列表删除观察者
Subject.prototype.remove_observer = function ( obj ) {
this.observer_list.splice( this.observer_list.findIndex( (item)=>item === obj ) , 1 );
}
Subject.prototype.notify = function () {
let args = arguments || [];
for (observer of this.observer_list) {
observer.update(...args); // 调用观察者的update方法
}
}
return Subject;
})()
// 拓展属性
function extend( obj, extension ) {
for (let key in extension) {
obj[key] = extension[key];
}
}
let controlCheckBox = document.getElementById('mainCheckBox');
let addBtn = document.getElementById('addNewObserver');
let container = document.getElementById('observersContainer');
// 用目标类去拓展controlCheckBox
extend( controlCheckBox, new Subject() );
addBtn.onclick = addNewObserver;
function addNewObserver() {
let check = document.createElement('input');
check.type = 'checkbox';
// 重写更新函数
check.update = function ( value ) {
this.checked = value;
}
// 目标添加观察者
controlCheckBox.add_observer( check );
// 添加到容器
container.appendChild( check );
}
mainCheckBox.onchange = function(e) {
console.log(e.target.checked)
controlCheckBox.notify(e.target.checked)
}
</script>
</body>
</html>
4. 观察者模式的优缺点
5. 观察者模式和订阅/发布模式的区别
- 区别
观察者模式,观察者
和被观察的目标
直接接触。
订阅/发布模式,发布者
和订阅者
不直接接触,两者通过中间的管道进行通信。