浅析 观察者模式(Observer) 和 发布(Publish)/订阅(Subscribe)模式
对于观察者模式 Observer 和 发布 Publish / 订阅 Subscribe 模式,这里简单做个记录, 最大的区别就是 调度中心的不同。
观察者模式 Observer
这里就简单做一个情景分析: 小明很喜欢隔壁 的小美,小美长得老漂漂亮了, 但是小明有点⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄(害羞),并且有点孤傲,不想刻意去打招呼,就想着能不能 “不经意”的去 认识一下,这点让它有点烦恼。这天小明在家思考🤔这个不经意怎么制造的时候,忽然,有急促的敲门声,烦得很ε=ε=ε=(#>д<)ノ, 于是想去battle一下,打开门发现是小美的快递,于是 一个 “不经意”的想法出现了, 小明想让 快递员老张 每次去给小美送快递的时候 给他报个信, (^▽^)
首先来个简单的快递员类
// 五星级快递员
class DeliverGuy {
delivery(who) {
console.log(`${new Date().toLocaleString()} 嘿咻嘿咻 给 ${who} 送货`)
}
}
- 小明:哎,老张 我有个小事情,help一下。
- 老张:阿巴阿巴阿巴
- 小明:两瓶快乐水
- 老张:好嘞ヽ( ̄▽ ̄)و
就这样老张 做起了副业,并秉承这做大做强的 心理,觉得以后可以好好发展的 态度, 扩宽了自己的副业
class Person {
constructor(name) {
this.name = name
}
log(str) {
console.log(`${new Date().toLocaleString()} ${this.name}: ${str}`)
}
}
class DeliverGuy extends Person {
constructor(props) {
super(props)
}
delivery(who) {
// 先去跑一遍贯彻着注册的逻辑
this.observerList.forEach(ob => {
this.log(`${ob.p.name},麻溜的,我要给 ${who} 送货了,一天天的, 挂了嗷, do...`)
ob.cb && ob.cb()
this.log(`下次得加钱 -。-, 先去送货`)
})
this.log(`嘿咻嘿咻 给 ${who} 送货`)
}
observerList = []
addExtraObserver(p, cb) {
this.observerList.push({
p,
cb
})
}
}
class XiaoMing extends Person {
constructor(props) {
super(props)
}
OutForXiaoMei = () => {
this.log('I\' ready for beautiful day now')
}
}
let LaoZhang = new DeliverGuy('老张')
let XiaoMing = new XiaoMing('小明')
LaoZhang.addExtraObserver(XiaoMing, XiaoMing.OutForXiaoMei)
LaoZhang.delivery('小美')
2022/1/12 上午11:37:01 老张: 小明,麻溜的,我要给 小美 送货了,一天天的, 挂了嗷, do...
2022/1/12 上午11:37:01 小明: I'm ready for beautiful day now
2022/1/12 上午11:37:01 老张: 下次得加钱 -。-, 先去送货
2022/1/12 上午11:37:01 老张: 嘿咻嘿咻 给 小美 送货
以上 老张快递员 后面扩宽的业务方法 addExtraObserver
就是 典型的观察者模式处理方式, 把观察者注册方法都注册到了 自己的处理逻辑中, 耦合
到了一起。
发布订阅者模式
发布订阅者模式和观察者模式最大区别就是 调度不同, 发布订阅这模式 是对 观察者模式的 解耦。 下面就简单写一个 类vue bus的 发布订阅 class
/**
* Vue 的on off emit 事件 做消息中心
*/
class VueBus {
static ins;
constructor() {
// 事件通道调度中心
this._events = Object.create(null);
}
$on(event, fn) {
if (Array.isArray(event)) {
event.map(item => {
this.$on(item, fn);
});
} else {
(this._events[event] || (this._events[event] = [])).push(fn);
}
return this;
}
$once(event, fn) {
function on() {
this.$off(event, on);
fn.apply(this, arguments);
}
on.fn = fn;
this.$on(event, on);
return this;
}
$off(event, fn) {
if (!arguments.length) {
this._events = Object.create(null);
return this;
}
if (Array.isArray(event)) {
event.map(item => {
this.$off(item, fn);
});
return this;
}
const cbs = this._events[event];
if (!cbs) {
return this;
}
if (!fn) {
this._events[event] = null;
return this;
}
let cb;
let i = cbs.length;
while (i--) {
cb = cbs[i];
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1);
break;
}
}
return this;
}
$emit(event) {
let cbs = this._events[event];
if (cbs) {
const args = [].slice.call(arguments, 1);
cbs.map(item => {
args ? item.apply(this, args) : item.call(this);
});
}
return this;
}
}
const vueBus = new VueBus()
vueBus.$on('test', function(s) {
console.log('test:', s)
})
vueBus.$emit('test', '-.-')
以上就是简单的 vueBus events的简单实现, 在我看来和观察者 有着明显的区别就是, 发布者和订阅者 是互不关心的 整体, 有着自己的独立的逻辑, 通知订阅者 是依靠 调度中心。 下面 关系图
anyWhy 下面总结下
耦合
耦合
, 我个人觉得观察者模式和 发布订阅模式的最大区别就是 耦合, 具体目标对象中会耦合 注册进入 观察者的 附带逻辑。而 发布订阅者不会, 二者完全解耦。
观察者比较概念的解释是,目标和观察者都是基类,目标维护观察者的一系列的方法,观察者只提供接口。 具体的观察者和具体的目标 即成各自的基类,然后具体观察者把自己注册到具体目标里,在具体目标发生变化后,注意,是具体目标去调度具体观察者的逻辑哦
简单整理下,把一些 观察者模式里面 具象化的 逻辑提取出来
/**
* 简单的 观察者类
*/
class Observer {
constructor() {
this.observerList = new Array()
}
addObserver(cb) {
this.observerList.push(cb);
}
countObserver() {
return this.observerList.length
}
getObserver(index) {
if(index > -1 && index < this.observerList.length){
return this.observerList[index];
}
}
indexOfObserver(cb, startIndex){
var i = startIndex ? startIndex : 0;
while(i < this.observerList.length){
if(this.observerList[i] === cb){
return i
}
i++;
}
return -1
}
removeAtObserver(index){
this.observerList.splice(index, 1)
}
emptyObserver() {
this.observerList.splice(0)
}
/**
* 通知观察者
*/
notifyObersver() {
console.log(this)
for (let i = 0; i < this.countObserver(); i++) {
const observer = this.getObserver(i);
// 这里可以改进下, this的指向问题
observer()
}
}
}
// 目标
class Subject extends Observer {
constructor() {
super()
}
}
var s = new Subject()
s.addObserver(function() {
console.log(this)
console.log(1)
})
另外 mobx 里面的 makeObserver 也是一种 观察者模式, 下面是一个简单的 利用ES6 Proxy和Reflct + 闭包 的简单实现
// 用symbol 做一个唯一变量
let _observer_ = Symbol('[[observer]]');
function makeObservable(target) {
// 初始化 target 上自己的 _observer_ list
target[_observer_] = [];
// 将 handler push 到 唯一数组 target[_observer_]
target.observe = function(cb) {
this[_observer_].push(cb);
};
// 用Proxy做代理
return new Proxy(target, {
set(target, property, value, receiver) {
// 用Reflect 将操作转发给对象
let success = Reflect.set(...arguments);
if (success) {
// 调用所有 handler
target[_observer_].forEach(cb => cb(property, value));
}
return success;
}
});
}
var ob = makeObservable({})
ob.observe((k, v) => {
console.log(`key:${k}, updated ${v}`)
})
ob.a = 233
ouput:
key:a, updated 233
233
ok,sir 本篇文章就是一个简单的记录文章,阐述了 观察者和发布订阅模式之前比较 浅显的区别,耦合和调度 不同。(应该说了好多遍,应该懂了,是的-。-), 另外记录了 发布订阅(vuebus) 的简单实现, proxy + reflect实现 类似mobx的 makeObservable. Have a Good Day