发布订阅模式

发布订阅

  • 发布订阅是一种消息范式,消息的发布者不会直接将消息发送给订阅者,而是将消息分为不同类别,也无需知道有哪些订阅者。
  • 同样订阅者可以选择一个或多个尽心消息订阅,只接收感兴趣的消息,不用知道发布者的存在及有多少消息可以订阅。

image.png
主要是创建一个类似于这样的对象{ “click”:[‘fn1’, ‘fn2’, …], “change”:[‘fn1’, …] }
主要分三步

  1. 发布订阅模式要有消息管理,或者事件管理中心
  2. 事件发布的方法$emit
  3. 事件订阅的方法$on
    1. class EventEmit{
    2. constructor(){
    3. this.subs = Object.create(null)
    4. }
    5. $on(eventType, handler){
    6. this.subs[eventType] = this.subs[eventType] || []
    7. this.subs[eventType].push(handler)
    8. }
    9. $emit(eventType){
    10. if(this.subs[eventType]){
    11. this.subs.forEach( handler => {
    12. handler()
    13. })
    14. }
    15. }
    16. }
    17. // 测试案例
    18. const em = new EventEmit()
    19. em.$on("click", ()=>{
    20. console.log("click1...")
    21. })
    22. em.$on("click", ()=>{
    23. console.log("click2...")
    24. })
    25. em.$emit("click")

    通过网站、体育比赛、粉丝完成发布订阅

    代码示例 ```javascript // 体育网站,事件处理中心 class Website { constructor() { this._events = {}; } // 订阅 subscribe(type, fn) { if (this._events[type]) { this._events[type].push(fn); } else { this._events[type] = [fn]; } } // 发布 publish(type) { let listeners = this._events[type]; let args = Array.prototype.slice.call(arguments, 1); if (listeners) { listeners.forEach((listener) => {
    1. listener(...args);
    }); } } // 移除事件 remove(type) { if (!this._events[type]) { return false; } else { this._events[type].length = 0; } } }

class Star { constructor(name) { this.name = name; } // 有体育比赛事件 event(type, address, time) { website.publish(type, address, time); } }

class Fan { constructor(name) { this.name = name; } see(type, website) { website.subscribe(type, (address, time) => { console.log( ${time}在${address}有一场${type}比赛,欢迎${this.name}前去观看 ); }); } }

let website = new Website(); website.subscribe(“篮球”, (address, time) => { ${time}在${address}有一场体育比赛; }); website.subscribe(“羽毛球”, (address, time) => { ${time}在${address}有一场体育比赛; }); let fan1 = new Fan(“tom”); let fan2 = new Fan(“sam”); fan1.see(“篮球”, website); fan2.see(“羽毛球”, website);

let star = new Star(); star.event(“篮球”, “上海”, “10.1”); let star2 = new Star(); star2.event(“篮球”, “新加坡”, “10.1”); star2.event(“羽毛球”, “新加坡”, “10.1”); // 可以移除监听的事件,移除后不再触发 website.remove(“羽毛球”); star2.event(“羽毛球”, “北京”, “10.1”);

  1. 网站是体育比赛和粉丝之间的一个事件管理中心,网站可以发布体育比赛消息通知粉丝,粉丝接收消息,体育明星可以发布消息给网站。这样解耦了明星和粉丝之间的直接关联。
  2. > Vue中的EventBus实现消息通信使用的发布订阅模式。通过创建一个事件中心EventBus
  3. > - 通过$on进行事件监听
  4. > - 通过$emit进行事件触发
  5. <a name="hGgDn"></a>
  6. ## 观察者模式
  7. > 观察者模式提供了对象间一种一对多的依赖关系,一个对象【被观察者Dep】发生变化,所有依赖它的对象【watcher】将收到通知,**并自动更新**。
  8. > 观察者模式属于行为模式,关注的是对象之间的通信。
  9. - 被观察者(目标Dep)维护观察者的一系列方法
  10. - 观察者提供更新接口
  11. - 观察者把自己注册到被观察者里
  12. - 被观察者发生变化时,调用观察者的更新方法
  13. <a name="KbwYl"></a>
  14. ### 类图
  15. ![Snipaste_2020-09-12_23-27-31.png](https://cdn.nlark.com/yuque/0/2020/png/737887/1599924473106-b4503550-6351-4ad8-9328-c2c46d8e2469.png#crop=0&crop=0&crop=1&crop=1&height=301&id=QIxZb&margin=%5Bobject%20Object%5D&name=Snipaste_2020-09-12_23-27-31.png&originHeight=301&originWidth=777&originalType=binary&ratio=1&rotation=0&showTitle=false&size=77773&status=done&style=none&title=&width=777)
  16. <a name="UHKIM"></a>
  17. ### 代码示例
  18. ```javascript
  19. class Star {
  20. constructor(name) {
  21. this.name = name;
  22. this.state = "";
  23. this.fans = [];
  24. }
  25. getState() {
  26. return this.state;
  27. }
  28. setState(state) {
  29. this.state = state;
  30. this.notify();
  31. }
  32. //添加粉丝
  33. addFan(fan) {
  34. this.fans.push(fan);
  35. }
  36. //通知粉丝
  37. notify() {
  38. if (this.fans.length > 0) {
  39. this.fans.forEach((fan) => {
  40. fan.update();
  41. });
  42. }
  43. }
  44. }
  45. class Fan {
  46. constructor(name, star) {
  47. this.name = name;
  48. this.star = star;
  49. this.star.addFan(this);
  50. }
  51. update() {
  52. console.log(
  53. `${this.name} ${this.star.name}${this.star.getState()}的比赛`
  54. );
  55. }
  56. }
  57. let star = new Star("姚明");
  58. let f1 = new Fan("小明", star);
  59. star.setState("在上海体育场");
  60. star.setState("在洛杉矶体育场");
  61. let starLin = new Star("林丹");
  62. let f2 = new Fan("小刚", starLin);
  63. starLin.setState("在新加坡体育场");

观察者模式只有被观察者(目标)和观察者,没有消息管理中心

vue中的观察者模式

  1. class Dep{
  2. constructor(){
  3. this.subs = []
  4. }
  5. addSub(sub){
  6. if(sub && sub.update){
  7. this.subs.push(sub)
  8. }
  9. }
  10. notify(){
  11. this.subs.forEach( sub =>{
  12. sub.update()
  13. })
  14. }
  15. }
  16. class Watcher{
  17. update(){
  18. console.log("update....")
  19. }
  20. }
  21. // 测试案例
  22. let dep = new Dep()
  23. let watcher = new Watcher()
  24. dep.addDep(watcher)
  25. dep.notify()

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

  1. 发布订阅模式由统一的调度中心调用,因此发布者和订阅者不需要知道对方的存在
  2. 观察者模式由具体目标调度,比如当事件触发,Dep就会去调用观察者的方法,所以观察者模式的订阅者与发布者之间存在着依赖关系

微信截图_20200907225817.png

举例对比发布订阅和观察者模式

首先是观察者模式

观察者.png
房东是被观察者,租客是观察者,租客关注着房东的优质房源信息的变动。有变化了立刻租下来。该模式存在一定的问题,房东和租客直接强耦合关系,租客关注的房源有可能一直没有空出来,就要一直等下去。租客关注的房屋信息少,没有关注更多的房屋信息。

发布订阅模式

发布订阅.png
发布订阅模式引入了房屋中介,第三方事件中心,该中心有多套房源,有多个房东,有多个租客,可以对事件进行调度管理。解决了房东和租客直接的强耦合关系。

举个生活例子:

  • 观察者模式:公司给自己员工发福利,是由公司的行政部门发送,这件事不适合交给第三方,原因是“公司”和“员工”是一个整体。存在耦合关系。
  • 发布-订阅模式:公司要给其他人发各种快递,因为“公司”和“其他人”是独立的,其唯一的桥梁是“快递”,这件事适合交给第三方快递公司解决,公司和其它人之间没有耦合关系。

上述过程中,如果公司自己去管理快递的配送,那公司就会变成一个快递公司,业务繁杂难以管理,影响公司自身的主营业务,因此使用何种模式需要考虑什么情况两者是需要耦合的。
image.png

  • 在观察者模式中,观察者是知道Subject的,Subject【被观察者】一直保持对观察者进行记录。在发布订阅模式中,发布者和订阅者不知道对方的存在,它们只有通过事件中心的消息代理进行通信。
  • 在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。
  • 观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。发布-订阅模式大多数时候是异步的(使用消息队列)