JS 设计模式|观察者模式

608 阅读2分钟

基础模式

图解

image.png

思路解读

  • 被观察对象

    • 可以输入信息、更新信息
    • 接受观察者的注册
    • 更新消息推送给已注册的观察者集合
  • 观察对象

    • 接收被观察者推送的更新消息

例子分析

被观察对象

/**
 * 信息输入类
 * 被观察对象
 */
class LoginBean {
  messageObj
  loginEventListener
  // 注册
  addLoginEventListener = function (loginEventListener) {
    this.loginEventListener = loginEventListener
  }
    // 通知
  fireLoginEvent = function () {
    this.loginEventListener.validateLogin(this.messageObj)
  }
    // 信息输入
  inputMessage = function (userName, password) {
    this.messageObj = { userName, password }
    this.fireLoginEvent()
  }
}

观察对象

/**
 * 信息校验类
 * 观察对象
 */
class LoginValidatorA {
  // 接受信息并进行校验
  validateLogin = function(event) {
    if(!event.userName){
      console.log('请输入名字')
    }
    else if(!event.password) {
      console.log('请输入密码')
    }
    else {
      console.log('输入成功', event.password, event.userName)
    }
  }
}

Main

// 实例化被观察对象
const loginBean = new LoginBean()
// 实例化观察对象
const loginValidator = new LoginValidatorA()
// 观察对象注册到被观察者对象中
loginBean.addLoginEventListener(loginValidator)
// 被观察对象更新数据
loginBean.inputMessage('你的名字', '123456')

全部代码

/**
 * 信息输入类
 * 被观察对象
 */
class LoginBean {
  messageObj
  loginEventListener
  addLoginEventListener = function (loginEventListener) {
    this.loginEventListener = loginEventListener
  }
  fireLoginEvent = function () {
    this.loginEventListener.validateLogin(this.messageObj)
  }
  inputMessage = function (userName, password) {
    this.messageObj = { userName, password }
    this.fireLoginEvent()
  }
}

/**
 * 信息校验类
 * 观察对象
 */
class LoginValidatorA {
  validateLogin = function(event) {
    if(!event.userName){
      console.log('请输入名字')
    }
    else if(!event.password) {
      console.log('请输入密码')
    }
    else {
      console.log('输入成功', event.password, event.userName)
    }
  }
}

const loginBean = new LoginBean()
const loginValidator = new LoginValidatorA()
loginBean.addLoginEventListener(loginValidator)
loginBean.inputMessage('你的名字', '123456')

优化模式

图解

image.png

思路解读

  • 观察者与被观察者之间增加调度中心,细化消息通知的粒度

例子分析

发布者

class Publisher {
  messageObj
  list = {}
  // 注册回掉方法
  subscribe = function (key, fun) {
    if(!this.list[key]){
      this.list[key] = []
    }
    this.list[key].push(fun)
  }
  // 推送消息
  publish = function (key, message) {
    this.list[key].forEach(funItem => funItem(message))
  }
}

监听者

class Observer1 {
  listen = function(message) {
    console.log('第一个监听者', message)
  }
}

class Observer2 {
  handle = function(message) {
    console.log('第二个监听者', message)
  }
}
  • 用此种优化可以解除监听者与被监听者之间方法名字的耦合

Main

const pub = new Publisher()
const obs1 = new Observer1()
const obs2 = new Observer2()
pub.subscribe('sing', obs1.listen)
pub.subscribe('todo', obs2.handle)
pub.publish('sing', { value: '你好世界' })
pub.publish('todo', { name: 'hello world' })

全部代码

/**
 * 信息输入类
 * 被观察对象
 */
class Publisher {
  messageObj
  list = {}
  // 注册
  subscribe = function (key, fun) {
    if(!this.list[key]){
      this.list[key] = []
    }
    this.list[key].push(fun)
  }
  // 推送消息
  publish = function (key, message) {
    this.list[key].forEach(funItem => funItem(message))
  }
}

class Observer1 {
  listen = function(message) {
    console.log('第一个监听者', message)
  }
}

class Observer2 {
  handle = function(message) {
    console.log('第二个监听者', message)
  }
}

const pub = new Publisher()
const obs1 = new Observer1()
const obs2 = new Observer2()
pub.subscribe('sing', obs1.listen)
pub.subscribe('todo', obs2.handle)
pub.publish('sing', { value: '你好世界' })
pub.publish('todo', { name: 'hello world' })

模式优缺点

优点

  • 解耦了表示层和数据逻辑层
  • 简化一对多系统的设计
  • 符合开闭原则,增加观察者无需更改被观察者代码

缺点

  • 观察者增多会增加通知成本
  • 观察者如果与被观察者相互依赖,则可能触发死循环
  • 观察者不知道被观察者数据是如何变化的

参考资料

《设计模式--刘伟》

《JavaScript设计模式——观察者模式 vs 发布订阅模式》