开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情
观察者模式(发布-订阅模式)
GOF 定义
定义对象间的一种一对多的依赖关系 ,当一个对象的状态发生改变时 , 所有依赖于它的对象
都得到通知并被自动更新。
举一个简单的例子。excel里可以将数据描述成图像,当表格中的数据改变时,图像也应该相应改变,而不需要重新生成图像。数据可以被描述成柱状图,饼图,条形图等等,这里数据就是就是发布者,各种图就是订阅者
观察者模式要做的就是 当发布者的状态发生改变时,通知各个订阅者。
实现
先来实现上面的例子, 当数据改变时各个图像也会更新
<div>
<div id="data" ></div>
<div id="img1"></div>
<div id="img2"></div>
<button id="btn">click</button>
<div/>
let dataEl = document.getElementById('data')
let img1El = document.getElementById('img1')
let img2El = document.getElementById('img2')
let btnEl = document.getElementById('btn')
let img1={
data: 0,
update: function(data){
this.data = data+1
img1El.innerHTML = `img1:${this.data}`
}
}
let img2={
data: 0,
update: function(data){
this.data = data+2
img2El.innerHTML = `img2:${this.data}`
}
}
// 数据
let dataObj = {
data: 1,
observers:[img1,img2],
notify: function(){ // 通知图像更新
this.observers.forEach((observer)=>{
if(observer.update){
observer.update(this.data)
}
})
},
setSate: function(){
this.data ++
dataEl.innerHTML = this.data
// 数据发生改变,通知依赖数据的图
this.notify();
}
}
btnEl.addEventListener('click',()=>{
dataObj.setSate()
})
通过上面的方法,我们可以实现,当点击一次click,三个数字都会更新。
现在来把代码优化一下,首先是img1 和 img2 这两个对象很明显可以通过同一个构造函数构造出来
function Img (data,work) {
this.data = data
// 这里只是更新数据,具体的对象工作不相同,不适合写在update中
this.update = (dataObj)=>{
this.data = dataObj.data
work(this.data) // 更新数据后的工作
}
}
let img1 = new Img(1,(data)=>{
img1El.innerHTML = `img1:${data}`
})
let img2 = new Img(2,(data)=>{
img2El.innerHTML = `img2:${data}`
})
发布者一般只有一个,这里就不抽象成类了。
进一步抽象,我们不关心发布者和订阅者到底是谁,我们只关心发布者和订阅者要做些什么,把它们抽象成类
首先是发布者,通过上面的例子我们可以知道,发布者都需要有通知订阅者的能力,所以必须要有observers 属性和 notify方法,同时也要有修改observers的能力,于是得到
class Publisher {
constructor(){
this.observers =[]
},
addObserver(observer){
this.observers.push(observer)
},
removeObserver(observer){
this.observers.forEach((item, i) => {
if (item === observer) {
this.observers.splice(i, 1)
}
})
},
notify(){
this.observers.forEach((observer)=>{
if(observer.update){
observer.update(this)
}
})
}
}
作为订阅者,就是要接收发布者发送的消息 也就是update
class Observer{
constructor(){},
update(){
}
}
通过继承来实现上述例子
class Img extends Observer {
constructor (data){
super()
this.data = data
}
// 这里只是更新数据,具体的对象工作不相同,不适合写在update中
update(dataObj){
this.data = dataObj.data
this.work(this.data) // 更新数据后的工作
}
work(){
}
}
let img1 = new Img(1)
let img2 = new Img(2)
img1.work = function(data){
this.data = data+1
img1El.innerHTML = `img1:${this.data}`
}
img2.work = function(data){
this.data = data+2
img2El.innerHTML = `img2:${this.data}`
}
let dataObj = new Publisher()
dataObj.addObserver(img1)
dataObj.addObserver(img2)
dataObj.data = 0;
dataObj.setSate = function(){
this.data++
dataEl.innerHTML = this.data
this.notify();
}