Observer(观察者)模式
观察者是一种设计模式。其中,一个对象(subject)维持一系列依赖它(观察者)的对象,将有关状态的任何变更通知给它们。
观察者模式一般使用一个被称为发布/订阅(Publish/Subscribe)模式的变量来实现。虽然他们很相似,但是还是有几点区别的。
- 观察者模式要求希望接收到主题通知的观察者必须订阅内容改变的事件。
- 发布/订阅模式使用一个主题/事件通道,这个通道位于希望接收到通知(发布者)的对象和激活事件(订阅者)的对象之间。
- 发布/订阅模式允许任何订阅者执行适当的事件处理程序来注册和接收发布者的通知。
简单模板实现:
/**
* Created by Zang on 2017/3/13.
* 通过 extend(new Subject(), concreteSubject) 创建具体目标
* 通过 AddNewObserver() 创建新的观察者,并将观察者加入具体目标
*
* 目标是一个 checkbox,观察者也是一个 checkout
* 当目标 checkbox.checked 发生变化的时候,通知所有的观察者做出相应的改变
*/
// 观察者
function Observer() {
this.observerList = [];
}
Observer.prototype.Add = function (obj) {
this.observerList.push(obj);
};
Observer.prototype.Count = function () {
return this.observerList.length;
};
Observer.prototype.Get = function (index) {
if (index > -1 && index < this.Count()) {
return this.observerList[index];
}
};
// 目标
function Subject() {
this.observer = new Observer();
}
Subject.prototype.AddObserver = function (obj) {
this.observer.Add(obj);
};
Subject.prototype.Notify = function (context) {
for (var i = 0; i < this.observer.Count(); i++) {
this.observer.Get(i).Update(context);
}
};
// 属性扩展
function extend(obj, extension) {
for (var key in obj) {
extension[key] = obj[key];
}
}
var concreteSubject;
window.onload = function () {
var addNewObserver = document.getElementById('addNewObserver');
concreteSubject = document.getElementById('concreteSubject');
var concreteObserver = document.getElementById('concreteObserver');
addNewObserver['onclick'] = AddNewObserver;
// 创建具体目标 concreteSubject
extend(new Subject(), concreteSubject);
concreteSubject['onclick'] = new Function("concreteSubject.Notify(concreteSubject.checked)");
// 添加新的观察者
function AddNewObserver() {
var check = document.createElement('input');
check.type = 'checkbox';
// 创建具体的观察者 check
extend(new Observer(), check);
check.Update = function (value) {
this.checked = value;
};
concreteSubject.AddObserver(check); // 加入具体目标
concreteObserver.appendChild(check);
}
};
<button id="addNewObserver">添加新的观察者</button>
<br><br><br>
<input type="checkbox" id="concreteSubject">具体目标
<br><br><br>
<div id="concreteObserver">
具体观察者<br><br>
</div>
Publish/Subscribe(发布/订阅)模式
/**
* Created by Zang on 2017/3/13.
*/
var pubSub = {};
(function (q) {
var topics = {}, subUid = -1;
// 发布者
q.publish = function (topic, args) {
if (!topics[topic]) {
return false;
}
var subscribers = topics[topic];
subscribers.forEach(function (item) {
item.Event(item, args);
});
};
// 订阅者
q.subscribe = function (topic, Event, name) {
if (!topics[topic]) {
topics[topic] = [];
}
var token = (++subUid).toString();
var obj = {
name: name,
topic: topic,
token: token,
Event: Event
};
topics[topic].push(obj);
return obj;
};
// 取消订阅
q.unSubscribe = function (obj) {
for (var i = 0; i < topics[obj.topic].length; i++) {
if (topics[obj.topic][i] === obj) {
topics[obj.topic].splice(i, 1);
return;
}
}
};
})(pubSub);
var message = function (item, data) {
console.log(item.name + ':' + item.topic + ':' + item.token + ':' + data);
};
var subscribeA1 = pubSub.subscribe('A', message, 'subscribeA1');
var subscribeA2 = pubSub.subscribe('A', message, 'subscribeA2');
var subscribeB = pubSub.subscribe('B', message, 'subscribeB');
// subscribeA1:A:0:hello
// subscribeA2:A:1:hello
pubSub.publish('A', 'hello');
// subscribeB:B:2:hello
pubSub.publish('B', 'hello');
// subscribeA1:A:0:hi
// subscribeA2:A:1:hi
// subscribeA3:A:3:hi
var subscribeA3 = pubSub.subscribe('A', message, 'subscribeA3');
pubSub.publish('A', 'hi');
// subscribeA1:A:0:hi,helo
// subscribeA3:A:3:hi,helo
pubSub.unSubscribe(subscribeA2);
pubSub.publish('A', 'hi,helo');
优缺点
优点:
- 帮助我们识别包含直接关系的层,并且可以用目标集和观察者进行替换。
- 将应用程序分解为更小、更松散的块,以改进代码的管理和潜在的复用。
缺点:
- 很难保证应用程序的特定部分按照我们期望的运行。
- 订阅者非常无视彼此的存在,并对变换发布者产生的成本视而不见。很难跟踪依赖更新。