古调虽自爱,今人多不谈 —— 漫谈发布订阅模式
发布订阅模式又称观察者模式,定义对象间一种一对多的关系,当对象状态改变时,所有依赖他的对象都将得到通知。实现观察者模式的一般流程
- 首先指定发布者(代表一的对象)
- 给发布者添加缓存列表用于存放回调函数以便通知订阅者
- 发布消息是时发布者遍历缓存列表,依次触发里面存放的的订阅者回到函数
穷人版
let salesOffices = {}; // 售楼处 即 发布者
salesOffices.clientList = []; // 缓存列表 存放订阅者的回调函数
salesOffices.listen = (fn) => { // 增加订阅者
salesOffices.clientList.push(fn); // 订阅的消息加进缓存列表
}
salesOffices.trigger = (arguments) => {
salesOffices.clientList.forEach((fn) => { // arguments 是trigger函数接受的参数
fn.call(this, arguments)
})
}
salesOffices.listen((price) => {
console.log('小明的消息' + price)
})
salesOffices.listen((price) => {
console.log('小芳的消息' + price)
})
salesOffices.trigger(100)
salesOffices.trigger(200)
加强版
上面的代码中小明和消防会接收到所有的信息,接下来我们在订阅函数中添加一个标识,标志用户特定的信息。
/*
* 假设小明只想接受100的消息
* 那我们就要在添加订阅的时候加入标识
* */
let salesOffices = {}; // 售楼处 即 发布者
salesOffices.clientList = {}; // 缓存列表 存放订阅者的回调函数
salesOffices.listen = (key, fn) => { // 增加订阅者
if (!salesOffices.clientList[key]) {
salesOffices.clientList[key] = [];
}
salesOffices.clientList[key].push(fn); // 订阅的消息加进缓存列表
}
salesOffices.trigger = (arg1, ...arg2) => {
let key = arg1;
let fns = salesOffices.clientList[key];
if (!fns || fns.length === 0) { // 没有订阅该消息
return false;
}
fns.forEach((v) => {
v.call(this, arg2) // arguments 指的是回调函数的
})
}
salesOffices.listen(100,(price) => {
console.log('小明的消息' + price)
})
salesOffices.listen(200,(price) => {
console.log('小芳的消息' + price)
})
salesOffices.trigger(100, 2000) // 发布100
salesOffices.trigger(200, 3000) // 发布200
设计模式的首要原则便是不变的和可变的分开,所以我们可以将观察者对象提取为一个公共的类。
全能版
/*
* 观察者类
* */
class Oberver {
constructor () {
this.clientList = {};
this.stack = [];
}
listen (key, fn) { // key 事件名
if (!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn);
this.stack.forEach((v, index) => {
v(key, index)
})
};
trigger (arg1, ...arg2) {
let key = arg1;
let fns = this.clientList[key];
if (!fns || fns.length === 0) {
this.stack.push((key, index) => {
if (key == arg1) {
this.trigger(arg1, arg2);
this.stack.splice(index, 1);
}
});
return false;
}
fns.forEach(v=> {
v.call(this, arg2)
})
}
}
// 删除订阅
Oberver.prototype.remove = function (key, fn) {
var fns = this.clientList[key];
if (!fns) {
return false;
}
if (!fn) {
fns && (fns.length = 0); // 没有fn标识删除所有的订阅消息属于key的
} else {
fns = fns.filter(v => {
return v != fn
})
}
return fns;
}
/*
* 添加具体的订阅发布
* */
var salesOffices = new Oberver();
salesOffices.listen(100, function (prices) {
console.log('我是小明我订阅了100的信息' + prices)
})
salesOffices.listen(200, function (proces) {
console.log('我是小芳我订阅了200的信息' + proces)
})
适用场景
观察者模式适用于一对多的业务需求,比如说很多模块都需要登录信息,或者封装的多个模块之间通信。
优点
时间解耦,对象解耦
缺点
创建订阅者本山需要时间,订阅一个消息可能没有任何用处,但还是在内存中。