观察者模式

98 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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,三个数字都会更新。

image.png

现在来把代码优化一下,首先是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();
}