观察者模式于发布订阅模式的关系
观察者模式于发布订阅模式的关系
在《JavaScript设计模式》中,对观察者模式的定义:又被称作发布-订阅者模式或消息机制,定义了一中依赖关系,解决了主题于观察者之间功能的耦合。
这里的定义将观察者和发布-订阅者,两种模式称作同一中,但是实际上观察者模式和发布订阅模式还是有一些区别的。
下面是一段书中作为观察者模式的代码:
NoDatavar Observer = (function () {
let _messages = {}
return {
regist: function(type, fn) {
if (typeof _messages[type] === 'undefined') {
_messages[type] = [fn]
}else {
_messages[type].push(fn)
}
},
fire: function(type, args) {
if (!_messages[type]) {
return
}
let events = {
type: type,
args: args || {}
}
for (let i = 0; i < _messages[type].length; i++) {
_messages[type][i].call(this, events)
}
},
remove: function(type, fn) {
if (_messages[type] instanceof Array) {
for (let i = _messages[type].length - 1; i >= 0; i--) {
_messages[type][i] === fn && _messages[type].splice(i, 1)
}
}
}
}
})()
// 订阅者
// 学生类
const Student = function(result) {
let that = this
that.result = result
that.say = function() {
console.log(that.result)
}
}
Student.prototype.answer = function(question) {
Observer.regist(question, this.say)
}
Student.prototype.sleep = function(question) {
console.log(this.result + ':' + question + '已被注销')
Observer.remove(question, this.say)
}
// 发布者
// 教师类
const Teacher = function() {}
Teacher.prototype.ask = function(question) {
console.log('问题是:' + question)
Observer.fire(question)
}
const student1 = new Student('学生1回答问题')
const student2 = new Student('学生2回答问题')
const student3 = new Student('学生3回答问题')
student1.answer('什么是设计模式')
student1.answer('什么是观察者模式')
student2.answer('什么是设计模式')
student3.answer('什么是设计模式')
student3.answer('什么是观察者模式')
const teacher = new Teacher()
teacher.ask('什么是设计模式')
student3.sleep('什么是观察者模式')
teacher.ask('什么是观察者模式')
//问题是:什么是设计模式
//学生1回答问题
//学生2回答问题
//学生3回答问题
//学生3回答问题:什么是观察者模式已被注销
//问题是:什么是观察者模式
//学生1回答问题
但实际上这段代码使用的应该是发布订阅模式。接下来详细了解一下二者的关系。
一、观察者模式
在对象之间定义一个一对多的依赖,当对象自身状态改变的时候,会自动通知给关心该状态的观察者。解决了主体对象与观察者之间功能的耦合,即一个对象的状态改变给其他对象通知的问题。
下面是一个简单的观察者模式例子:
// 被观察者
class Subject {
constructor() {
// 观察者列表
this.observers = []
}
// 添加
add(observer) {
this.observers.push(observer)
}
// 删除
remove(observer) {
let index = this.observers.indexOf(observer)
if (index > -1) {
this.observers.splice(index, 1)
}
}
// 通知
notify() {
for (let observer of this.observers) {
observer.update()
}
}
}
// 观察者类
class Observer {
constructor(name) {
this.name = name
}
// 观察目标更新时
update() {
console.log(`${this.name},项目又有新bug了`)
}
}
let subject = new Subject()
let obs1 = new Observer('前端开发')
let obs2 = new Observer('后端开发')
subject.add(obs1)
subject.add(obs2)
subject.notcify()
// 前端开发,项目又有新bug了
// 后端开发,项目又有新bug了
在以上代码中可以很明显的看出有两个类,一个是Subject是被观察者类,构造函数中有一个用来存放观察者的列表,当被观察者发生状态改变时,通过遍历被观察者列表来通知观察者。添加和删除方法用来添加和删除观察者,notify用来通知观察者。通过创建实例,调用被观察者的notify方法来模拟被观察者状态改变时的事件调用。实现了简单的观察者模式。
二、发布订阅模式
也是定义一对多的依赖关系,对象状态改变后,通知给所有关心这个状态的订阅者。发布订阅模式有订阅的动作,可以不和观察者直接产生联系,只要能订阅上关心的状态即可,通常可用第三媒介来做,而发布者也会利用第三媒介来通知订阅者。
以下是一个发布订阅者示例:
class PubSub {
constructor() {
this.list = {}
}
subscribe(key, fn) {
if (!this.list[key]) {
this.list[key] = []
}
this.list[key].push(fn)
}
publish(key, ...arg) {
for (let fn of this.list[key]) {
fn.call(this, ...arg)
}
}
unSubscribe(key, fn) {
let fnList = this.list[key]
if (!fnList) {
return false
}
if (!fn) {
fnList && (fnList.length = 0)
} else {
let index = fnList.indexOf(fn)
fnList.splice(index, 1)
}
}
}
const pubSub = new PubSub()
pubSub.subscribe('onwork', time => {
console.log(`上班时间:${time}`)
})
pubSub.subscribe('offwork', time => {
console.log(`下班时间:${time}`)
})
pubSub.subscribe('launch', time => {
console.log(`午饭时间:${time}`)
})
pubSub.publish('onwork', '8:30')
pubSub.publish('launch', '12:00')
pubSub.publish('offwork', '17:30')
// 上班时间:8:30
// 午饭时间:12:00
// 下班时间:17:30
上面的代码就是发布订阅最重要的部分,虽然没有明显的实现观察者类和被观察者类,但观察者和被观察者在发布订阅模式中本来就不会有直接关联,都只需调用以上代码(事件总线)中的发布和订阅方法即可。这样的代码可以被提取出来,可以被重复使用,可以被多个观察者和被观察者使用。
三、两者之间的异同
1、从概念上没什么不同,都是在解决对象之间解耦的问题,通过事件的方式在某个时间点进行触发,监听这个事件的订阅者可以进行相应的操作。
2、从实现上来说,观察者模式对订阅事件的订阅者通过发布自身来维护,后续的一系列操作都需要通过发布者完成;发布订阅模式是订阅者和发布者有一个事件总线,操作都要经过事件总线完成。
3、在观察者模式中,观察者知道被观察的主体,主体也维护观察者的记录。在发布订阅模式中,发布者和订阅者不需要知道彼此,他们通过事件总线来实现通信。观察者模式进一步抽象,能抽出的公共代码就是事件总线。
4、观察者模式是松耦合的,发布订阅模式,完全不耦合。
5、观察者大多数主要以同步方式实现,发布订阅模式大多数是异步模式。
6、观察者模式基本用于单个应用内部,发布订阅模式更多的是跨应用的模式
Vue2中通过拦截数据进行依赖收集,等待数据变更时,通知依赖的Watcher进行组件更新。
通过使用Object.defineReactive()方法,对数据对象添加get()方法进行拦截,收集依赖,在通过添加set()方法,对修改数据进行拦截,通知依赖更新,从而达到数据的响应式。在此过程中也使用了发布订阅的模式,事件总线就是用来收集和通知更新依赖的Dep()函数。
// src/observer/dep.js
export default class Dep{
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
removeSub(sub) {
romove(this.subs, sub)
}
depend() {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify() {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}

浙公网安备 33010602011771号