关于观察者模式/发布订阅模式我所知道的

简介: 关于观察者模式/发布订阅模式我所知道的

image.png


本文已参与「新人创作礼」活动,一起开启掘金创作之路。

关键词:Observable-Observer EventEmitter-EventListener


什么是观察者模式


定义:在对象之间定义一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。

类比到生活中,就像是为了逃避城管的小摊贩,每天出摊的时候就在朋友圈或者微信群里发一句:今天在 xxx 出摊。

我们先来看一个观察者模式的简单实现:

// 摊主
const shop = {
  // 微信群友
  observers: [],
  // 进微信群
  subscribe: function (fn) {
    this.observers.push(fn)
  },
  // 发布消息
  notify: function () {
    this.observers.forEach(fn => fn.apply(this, arguments))
  }
};
// 小k 进群
shop.subscribe((address) => {
  console.log(`小k: 今天在${address}出摊。`);
});
// 大D 进群
shop.subscribe((address) => {
  console.log(`大D: 今天在${address}出摊。`);
});
shop.notify('人民广场');
shop.notify('提篮桥');
// 小k: 今天在人民广场出摊。
// 大D: 今天在人民广场出摊。
// 小k: 今天在提篮桥出摊。
// 大D: 今天在提篮桥出摊。

将上面这段代码梳理下业务逻辑,如图示:


image.png


在观察者模式中有两个主要角色:Observable(观察对象)和 Observer(观察者)。

  1. 观察者监听观察对象
  2. 观察对象的状态发生变化时就会通知所有的观察者
  3. 观察者更新自己

整个过程里,观察对象负责监视事件,观察者负责处理收到的数据。

  • Observable 观察对象
  • observers  观察者列表
  • subscribe  注册
  • unsubscribe 取消注册
  • notify     通知观察者
  • Observer 观察者
  • update 更新自己

用 ts 表达会更直观一些。

interface IObservable {
  subscribe(observer: Observer): void;
  unsubscribe(observer: Observer): void;
  notify: Function;
}
interface IObserver {
  update: Function;
}
class Observable implements IObservable {
  private observers: IObserver[] = [];
  // 注册
  public subscribe(observer: IObserver): void {
    this.observers.push(observer);
  }
  // 取消注册
  public unsubscribe(observer: IObserver): void {
    const n: number = this.observers.indexOf(observer);
    n != -1 && this.observers.splice(n, 1);
  }
  // 通知
  public notify(...args): void {
    this.observers.forEach((observer) => observer.update(...args));
  }
}
class Observer implements IObserver {
  constructor(private name: string) {}
  // 更新
  update(address) {
    console.log(`${this.name}:今天在${address}出摊`);
  }
}
const subject: Observable = new Observable();
const k = new Observer("小K");
const D = new Observer("大D");
subject.subscribe(k);
subject.subscribe(D);
subject.notify("人民广场");
// 小K:今天在人民广场出摊
// 大D:今天在人民广场出摊
subject.unsubscribe(k);
subject.notify("提篮桥");
// 小K:今天在提篮桥出摊


什么是发布订阅模式?


相比于简单的观察者模式,发布订阅多了一个映射(topics)。-> 关联一下 策略模式。

先来看张图:


image.png


从图中我们可以看到,相比于简单的观察者模式,发布订阅多了 topics 映射,所有发布者的消息都需要通过 topics 派发(dispatch) 到订阅者。

为什么这么设计呢?

如果 Observable 过于复杂,notify 通知所有 observer 就会导致性能问题。

换句话说:微信群里这么多人,阿猫阿狗都在水群,但是我只想知道今天的出摊信息,那怎么办?

我会把整个群聊屏蔽了,除了几个特别关注特别是:摊主、网红。

// 小摊群
const shop = {
  // 粉丝团
  observers: {},
  // 进粉丝团
  subscribe: function (key, fn) {
    // 如果还没有订阅过此类消息,给该类消息创建一个缓存列表
    if (!this.observers[key]) {
      this.observers[key] = [];
    }
    // 订阅的消息添加进消息缓存列表
    this.observers[key].push(fn);
  },
  // 发布消息
  notify: function () {
    // 取出消息类型
    const key = Array.prototype.shift.call(arguments)
    // 取出该消息对应的回调函数集合
    const fns = this.observers[key];
    // 如果没有订阅该消息,则返回
    if (!fns || fns.length === 0) {
      return false;
    }
    fns.forEach(fn => fn.apply(this, arguments));
  },
  unsubscribe: function (key, fn) {
    let fns = this.observers[key];
    // 如果key对应的消息没有被人订阅,则直接返回
    if (!fns) {
      return false;
    }
    // 如果没有传入具体的回调函数,表示需要取消 key 对应消息的所有订阅
    if (!fn) {
      this.observers[key] = null
    } else {
      this.observers[key] = fns.filter(_fn => _fn !== fn);
    }
  }
};

测试代码:

// 添加 摊主 - k 订阅
shop.subscribe('master', k = function (address) {
  console.log(`小k: 今天在${address}出摊。`);
});
// 添加 摊主 - D 订阅
shop.subscribe('master', D = function (address) {
  console.log(`大D: 今天在${address}出摊。`);
});
shop.notify('master', '人民广场');
// 添加 网红 - k 订阅
shop.subscribe('网红', k = function (address) {
  console.log(`小k: 今天在${address}出摊。`);
});
// 添加 网红 - D 订阅
shop.subscribe('网红', D = function (address) {
  console.log(`大D: 今天在${address}出摊。`);
});
// 删除订阅
shop.unsubscribe('网红', D);
shop.notify('网红', '提篮桥');
// 小k: 今天在人民广场出摊。
// 大D: 今天在人民广场出摊。
// 小k: 今天在提篮桥出摊。


这样我们就不用把群里的每一条消息都看过去了。


应用场景


什么时候使用?当一个对象的改变需要同时改变其他对象的时候。

满足以下两个条件:

  • 一对多。当一个对象的改变需要同时改变其他对象。
  • 不知道多几个。不知道具体有多少对象需要改变。

典型案例:网站登录。

需求: 用户登录之后更新头像 avatar , 更新消息列表 message , 刷新购物车 cart 。

login.succ(function (data) {
  // 更新头像
  header.setAvatar(data.avatar);
  // 刷新消息列表
  message.refresh();   
  // 刷新购物车列表             
  cart.refresh();                   
});

需求蔓延:新增地址簿 address 模块,登陆之后也刷新下。

login.succ(function (data) {
  // 新增代码
  address.refresh();                   
});

需求蔓延:那个新增 收藏夹 favorites 也更新下?新增 订单列表 orders 也更新下?

就这样,后续面对的是越来越多这样突如其来的业务诉求,面对这种情况,就可以用上发布订阅了。

// 登录成功
login.succ(function (data) {
  // 发布登录成功的消息   
  login.trigger('loginSucc', data);    
});
// 各个业务模块注册登录成功事件
login.listen('loginSucc', function (data) {
  header.setAvatar(data.avatar);
});
login.listen('loginSucc', function (data) {
  message.refresh( data );
});
login.listen( 'loginSucc', function( obj ){
  address.refresh( obj );
});


参考资料:

细数九种常见的设计模式

观察者模式

目录
相关文章
|
6月前
|
消息中间件 存储 Cloud Native
揭秘发布订阅模式:让消息传递更高效
揭秘发布订阅模式:让消息传递更高效
揭秘发布订阅模式:让消息传递更高效
|
30天前
|
设计模式 消息中间件 安全
C# 一分钟浅谈:观察者模式与订阅发布模式
【10月更文挑战第11天】本文介绍了观察者模式和订阅发布模式,这两种设计模式主要用于实现对象间的解耦。观察者模式通过事件和委托实现一个对象状态改变时通知多个依赖对象;订阅发布模式则通过事件聚合器实现发布者与订阅者之间的解耦。文章详细探讨了这两种模式的实现方式、常见问题及避免方法,帮助开发者在实际项目中更好地应用这些模式,提升代码的可维护性和扩展性。
57 1
|
5月前
|
设计模式
观察者模式-大话设计模式
观察者模式-大话设计模式
|
6月前
|
设计模式
设计模式-观察者(发布订阅)模式
设计模式-观察者(发布订阅)模式
|
6月前
|
消息中间件 设计模式 前端开发
【面试题】说说你对发布订阅、观察者模式的理解?区别?
【面试题】说说你对发布订阅、观察者模式的理解?区别?
104 0
|
6月前
行为型 观察者模式(发布订阅)
行为型 观察者模式(发布订阅)
39 0
4 # 发布订阅模式
4 # 发布订阅模式
52 0
|
设计模式 开发者
设计模式之订阅发布模式
设计模式之订阅发布模式
233 0
|
设计模式 资源调度 Dart
发布订阅模式原理及其应用
本文介绍发布订阅模式原理及其应用
152 0
|
设计模式 JavaScript 调度
它们不一样!透析【观察者模式】和【发布订阅模式】
观察者模式常常会和发布订阅模式一起哪来比较,它们二者同样重要。