观察者模式是前端应用最多的一种设计模式
介绍:
- 当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。
- 发布&订阅
例如:售楼处和购房者,一般情况,售楼中心有1个,购房者有n多个,符合一对多的关系,当房价下降时,售楼处会通知所有购房者。(前提是购房者在售楼处进行信息的登记,然后售楼处遍历这张登记表)。 - 在JavaScript开发中,我们一般用事件模型来替代传统的发布-订阅模式。
DOM事件:
document.body.addEventListener('click', () => {
alert(1);
});
document.body.addEventListener('click', () => {
alert(2);
});
document.body.addEventListener('click', () => {
alert(3);
});
如上,我们需要监控点击document.body的动作,但是我们没办法预知用户将在什么时候点击,所以我们订阅document.body上的click事件,当body节点被点击时,body节点变会向订阅者发布这个消息。
其中,() => {alert(1)}, () => {alert(2)}等就是一个个订阅者,他们都订阅了click事件。
这很像购房的例子,购房者不知道房子什么时候开售,于是他们在订阅消息后等待售楼处发布消息。
JavaScript代码实现发布订阅:
<script>
const saleCenter = {};
saleCenter.clientList = [];
saleCenter.listen = function (fn) {
this.clientList.push(fn);
};
saleCenter.attach = function () {
for (let i = 0, fn; (fn = this.clientList[i++]); ) {
fn.apply(this, arguments);
}
};
// 测试
saleCenter.listen((info) => {
console.log(info)
})
saleCenter.listen((info) => {
console.log(info);
})
saleCenter.attach('房子降价了')
</script>
上面是一个最简单的发布-订阅模式,对于不同收入的人,想要购买的房源是不一样的。
<script>
const saleCenter = {};
saleCenter.clientList = {};
saleCenter.listen = function (type, fn) {
if (!this.clientList[type]) { // 如果还没有订阅过此类型的消息,则给该类型创建一个缓存列表
this.clientList[type] = [];
}
this.clientList[type].push(fn);
};
saleCenter.attach = function () {
const type = Array.prototype.shift.apply(arguments),
fns = this.clientList[type]; // 取出该消息类型对应的回调函数集合
if (!fns || fns.length === 0) {
return false;
}
for (let i = 0, fn; (fn = fns[i++]); ) {
fn.apply(this, arguments);
}
};
// 测试
saleCenter.listen('海景房', (info) => {
console.log(info);
});
saleCenter.listen('小平房', (info) => {
console.log(info);
});
saleCenter.attach('海景房', '房子降价了');
</script>
现在,订阅者可以只订阅自己感兴趣的房源了。
参考《JavaScript设计模式与开发实战》第8章。
// 没事手写一写记录
<script>
// 发布订阅模式
function SellHome() {
this.userList = [];
}
SellHome.prototype.attach = function (msg) {
console.log('11', this);
this.userList.forEach(u => {
console.log('hh', u.name+''+msg);
});
};
SellHome.prototype.listen = function (user) {
console.log('22', this);
this.userList.push(user);
};
const u1 = {
name: '小明',
age: 20
};
const u2 = {
name: '小红',
age: 18
};
const s = new SellHome();
s.listen(u1);
s.listen(u2);
s.attach('吃饭啦');
</script>
UML类图:
观察者模式的实现思路:把多个观察者放入主题中,由主题来控制消息的变化进行消息的分发。
代码实现:
// 主题,接收状态变化,触发每个观察者
class Subject {
constructor() {
this.state = 0
this.observers = []
}
getState() {
return this.state
}
setState(state) {
this.state = state
this.notifyAllObservers()
}
attach(observer) {
this.observers.push(observer)
}
notifyAllObservers() {
this.observers.forEach(observer => {
observer.update()
})
}
}
// 观察者,等待被触发
class Observer {
constructor(name, subject) {
this.name = name
this.subject = subject
this.subject.attach(this)
}
update() {
console.log(`${this.name} update, state: ${this.subject.getState()}`)
}
}
// 测试代码
let s = new Subject()
let o1 = new Observer('o1', s)
let o2 = new Observer('o2', s)
let o3 = new Observer('o3', s)
s.setState(1)
s.setState(2)
s.setState(3)
应用场景
- 网页事件绑定
- Promise的then监听
- jQuery的callbacks
- nodejs自定义事件
- Vue里的watch监听
- nodejs中处理http请求
设计原则验证:
- 主题和观察者分离,不是主动触发而是被动监听,两者解耦。
- 符合开放封闭原则。
es5方式实现:
// 观察者
// name: string
// subject: Subject
// update: () => {}
function Observer(name, subject) {
this.name = name;
this.subject = subject;
this.subject.attach(this);
this.update = function() {
console.log(`${this.name}更新了${this.subject.getState()}`)
}
}
// 主题
// observers: []
// state: init
// setState: () => {}
// getState: () => {}
// attach: () => {}
// notifyAllObservers:
function Subject() {
this.observers = [];
this.state = 1;
this.getState = () =>{
return this.state;
};
this.setState = (state) => {
this.state = state;
this.notifyAllObservers();
}
this.notifyAllObservers = () => {
this.observers.forEach(o => {
o.update()
})
}
this.attach = (observer) => {
this.observers.push(observer)
}
}
const s = new Subject();
const O1 = new Observer('o1', s);
const O2 = new Observer('o2', s);
s.setState(4)