设计模式之 观察者模式详解

一 观察者模式

1 含义

1 主体/被观察者- Subject

  • addObs(ob): 添加ob
  • removeObs(ob): 移除ob
  • setData(): 更新数据,通常是触发notify的契机
  • notifyObs(): 通知ob

2 观察者- Observer

  • update()/ doSomething(): 回调函数

示意图见下:

图1-1

2 实现流程/伪代码

1 流程

  • S1 new Subject(), 记做s1 + new Observer(), 记做ob1 ==>
  • S2 s1.addObs(ob1) ==>
  • S3 s1.setData() ==> s1.notifyObs() ==>
  • S4 ob1.doSomething()/ ob2.doSomething()……

2 伪代码- 观察者模式

// 被观察主体
class Subject {
  constructor() {

    this.obs = []
  }

  addObs(ob) {
    this.obs.push(ob)
  }
  removeObs(ob) {
    const index = this.obs.indexOf(ob)
    index > -1 && this.obs.splice(index, 1)
  }

  notifyObs() {
    this.obs.forEach(ob => ob.update(this))
  }
}

// 观察者
class Observer {
  constructor() {}
  // 或者称为updata()
  update(subject) {
    console.log('去做点什么吧')
  }
}

二 发布-订阅模式

1 含义

1 发布订阅中心

  • typeTocbsMap: { type: [cb1, cb2 ….]}: 消息类型与回调队列 映射关系
  • typeToMsgMap: { type: [content1, content2 ….]}: 消息类型与内容 映射关系
  • subscribe(type, cb): 建立 1个消息类型与回调函数的 绑定关系
  • publish(type, content): 建立 1个消息类型与消息内容的 绑定关系
  • notify(type): 把 某个消息类型对应的 消息内容,下发给所有的 消息回调

2 发布者

  • publish(type, content): 中转调用 sub.publish()

3 订阅者

  • subscribe(type, cb): 中转调用 sub.subscribe(type, cb)

示意图见下:

图2-1

2 实现流程/伪代码

1 流程

  • S1 sub.subscribe(type, cb)==> PubSub.subscribe(type, cb)
  • S2 pub.publish(type, content)==> PubSub.publish(type, cb)
  • S3 更新数据后==> PubSub.notify(type)==> cbX(contents)

2 伪代码-发布订阅模式

// 发布订阅中心
class PubSub {
  constructor() {

    this.messages = {};
    this.listeners = {};
  }
  publish(type, content) {
    const existContent = this.messages[type];
    if (!existContent) {
      this.messages[type] = [];
    }
    this.messages[type].push(content);
  }
  subscribe(type, cb) {
    const existListener = this.listeners[type];
    if (!existListener) {
      this.listeners[type] = [];
    }
    this.listeners[type].push(cb);
  }

  notify(type) {
    const messages = this.messages[type];
    const subscribers = this.listeners[type] || [];
    subscribers.forEach((cb, index) => cb(messages));
  }
}

// 发布者
class Publisher {
  // ctx指向是其所属的 发布订阅中心实例对象
  constructor(name, ctx) {
    this.name = name;
    this.ctx = ctx;
  }
  publish(type, content) {
    this.ctx.publish(type, content);
  }
}

// 订阅者
class Subscriber {
  // ctx指向是其所属的 发布订阅中心实例对象
  constructor(name, ctx) {
    this.name = name;
    this.ctx = ctx;
  }
  subscribe(type, cb) {
    this.ctx.subscribe(type, cb);
  }
}

三 观察者模式 和 发布-订阅模式的 区别

  1. 是否存在第三方角色/ 发布者是否可以直接与 订阅者通信:
  • 观察者模式: 只有[观察者]和[被观察者] 2个角色;
  • 发布-订阅模式: 有 [发布者]、[订阅者]、[调度中心/发布订阅中心] 3个角色
  1. 观察者模式是松耦合的,因为被观察者 内部包含一部分处理观察者关系的逻辑;
    而 发布者和订阅者由于[发布订阅中心]的出现 而完全解耦了

  2. 观察者模式大多数是同步的,而发布-订阅模式大多数是异步的(使用消息队列)

四 其他相关内容

1 EventBus/ EventEmitter实现

1 伪代码- EventBus/EventEmitter

class EventEmiter {
  constructor() {
    // events是一个map,用于存储事件与回调之间的对应关系
    this.events = {}
  }

  // 监听
  on(eventName, cb) {
    if (!this.events[eventName]) {
      this.events[eventName] = []
    }
    this.events[eventName].push(cb)
  }

  // 移除某个事件回调队列里的指定回调函数
  off(eventName, cb) {
    const cbs = this.events[eventName]
    const index = cbs.indexOf(cb)
    if (index !== -1) {
      cbs.splice(index, 1)
    }
  }

  // 派发事件
  emit(eventName, ...args) {
    const cbs = this.events[eventName]
    if (cbs) {
      // 对cbs做一次浅拷贝
      // 主要是为了 避免通过once安装的监听器在移除的过程中 出现顺序问题
      const handlers = cbs.slice()
      handlers.forEach(cb => cb(...args))
    }
  }

  // 只调用一次
  once(eventName, cb) {
    const wrapper = (...args) => {
      cb(...args)
      this.off(eventName, wrapper)
    }
    this.on(eventName, cb)
  }
}

五 参考文档

01 观察者模式 vs 发布-订阅模式

02 理解 观察者模式 和 发布订阅 的区别

03 JS设计模式小册-观察者模式

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYylhL0G' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片