javascript设计模式之观察者模式

204 阅读3分钟

什么是观察者模式呢?

它定义对象间的一种一对多的依赖关系,当一个对象的状 态发生改变时,所有依赖于它的对象都将得到通知 。

举个例子来说:

当用户在一个网站是订阅某个主题时:

  1. 多个用户(观察者,Observer)都可以订阅某个网站上的主题(Subject)
  2. 当主题内容更新时订阅该主题的用户都能收到通知

也就是说,当用户订阅了一个主题之后,不需要时时刻刻都去关注,主题的实时情况,因为这样太浪费用户的时间。那么,如何才能让用户知道我订阅的主题是不是发生了改变呢?我们只要在主题发生改变的时候,去通知用户即可。

那么我们怎么用代码来描述这个场景呢?

//创建一个主题对象,内部维护订阅它的用户
function Subject() {
  this.observers = []
}
//添加订阅该主题的用户
Subject.prototype.addObserver = function(observer) {
  this.observers.push(observer)
}


Subject.prototype.removeObserver = function(observer) {
  var index = this.observers.indexOf(observer)
  if(index > -1){
    this.observers.splice(index, 1)
  }
}

// 通知用户,主题发生了变化
Subject.prototype.notify = function() {
  this.observers.forEach(function(observer){
    observer.update()
  })
}

//观察者(用户),需要定义一个update方法,在主题变化的时候触发
function Observer(name) {
  this.name = name
  this.update = function(){
    console.log(name + ' update...')
  }
}  


// 创建主题
var subject = new Subject()

//创建观察者1
var observer1 = new Observer('hunger')
//主题添加观察者1
subject.addObserver(observer1)
//创建观察者2
var observer2 = new Observer('valley')
//主题添加观察者2
subject.addObserver(observer2)

//主题通知所有的观察者更新
subject.notify()

接下来,我们用es6代码来改造一下:

class Subject {
  constructor() {
    this.observers = []
  }

  addObserver(observer) {
    this.observers.push(observer)
  }

  removeObserver(observer) {
    var index = this.observers.indexOf(observer)
    if(index > -1){
      this.observers.splice(index, 1)
    }
  }

  notify() {
    this.observers.forEach(observer=> {
      observer.update()
    })
  }
}


class Observer{
  constructor() {
    this.update = function() {}
  }
}  


let subject = new Subject()

let observer1 = new Observer()

observer1.update = function() {
  console.log('observer1 update')
}
subject.addObserver(observer1)

let observer2 = new Observer('valley')
observer2.update = function() {
  console.log('observer2 update')
}
subject.addObserver(observer2)
subject.notify()

当然,上面用户订阅的时候,表达的也不是很直观,因为我们知道,是用户去主动订阅主题,而不是主题将每个用户依次放入。所以我们可以在观察者上面添加一个主动订阅的方法

class Observer{
  constructor() {
    this.update = function() {}
  }

  //主动订阅主题
  subscribeTo(subject) {
    subject.addObserver(this)
  }
} 


let subject = new Subject()
let observer = new Observer()
observer.update = function() {
  console.log('observer update')
}
observer.subscribeTo(subject)  //观察者订阅主题

subject.notify()

看过vue源码的同学,看这里是不是感到很熟悉,没错,vue中的MVVM的实现就用到了观察者模式。


2.发布订阅模式

可是,更多的时候,我们是这样的一个需求,用户并不关心这个主题是来自哪个具体的网站,或者更具体的说,用户并不关心消息到底是来自于哪个发布者,用户关心的是,我能否顺利的收到消息。那么发布者也不需要了解具体会推送到哪些订阅者,发布者在意的是,我只需要把消息推送到某个“中介”,然后由“中介”去和订阅者联系。

所以,在程序中,可以用一个全局的Event对象来实现“中介”的功能,订阅者不需要了解消 息来自哪个发布者,发布者也不知道消息会推送给哪些订阅者,Event 作为一个类似“中介者” 的角色,把订阅者和发布者联系起来。

const Event = (function(){
  var eventList = {}
  function on(event, handler) {
    if(!eventList[event]) {
      eventList[event] = [handler]
    }else {
      eventList[event].push(handler)
    }
    offLineStack = null
  }

  function emit() {
    let key = Array.prototype.shift.call(arguments) 
    if(eventList[key]) {
      eventList[key].forEach(handler => handler.apply(this,arguments))
    }
  }

  function remove(event, handler) {
    if(eventList[event]) {
      if(!handler) {
        delete eventList[event]
      }else {
        let index = eventList[event].indexOf(handler)
        eventList[event].splice(index, 1)
      }
    }
  }
  return {
    on: on,
    emit: emit,
    remove: remove
  }
})()


Event.on('sayHello', function(data) {
  console.log('hello ' + data)
})
Event.emit('sayHello', 'smile')

 这样,我们便实现了我们所描述的场景。


总结:

其实所谓的观察者模式和发布订阅模式,实现都是大同小异,无非是通知订阅者改变的对象不同。他们的优点非常清除:一为时间上的解耦,二为对象之间的解耦。从架构上来看,MVVM架构模型,也有观察者模式的参与。