用JS写一个发布订阅模式


Posted in Javascript onNovember 07, 2021

什么是发布订阅模式?能手写实现一下吗?它和观察者模式有区别吗?...

1 场景引入

我们先来看这么一个场景:

假设现在有一个社交平台,平台上有一个大V叫Nami

Nami很牛,多才多艺,目前她有2个技能:会写歌、会拍视频

她会把这些作品发布到平台上。关注她的粉丝就会接收到这些内容

现在他已经有3个粉丝了,分别是:Luffy、Zoro、Sanji

每次只要Nami一发布作品,3个粉丝的账号上收到的消息就会更新

现在用代码来表示:

const luffy = {
  update: function (songs, videos) {
    console.log(songs, videos);
  },
};
const zoro = {
  update: function (songs, videos) {
    console.log(songs, videos);
  },
};
const sanji = {
  update: function (songs, videos) {
    console.log(songs, videos);
  },
};

const nami = {
  // 只要Nami的作品一更新,这个方法就会被调用
  workUpdate: function () {
    // 获取作品
    const songs = this.getSongs();
    const videos = this.getVideos();

    // 账号更新
    luffy.update(songs, videos);
    zoro.update(songs, videos);
    sanji.update(songs, videos);
  },
  getSongs: function () {
    return "mp3";
  },
  getVideos: function () {
    return "mp4";
  },
};

现在问题来了

  • 如果Nami又收获了一个粉丝Robin,我既要添加一个robin对象,又要修改workUpdate方法
  • 如果Nami又有了一项新技能:写小说,我既要修改workUpdate函数,又要修改每个粉丝对象中的update方法,因为参数增加了一个

发现问题没有?

粉丝对象和大V对象之间的耦合度太高,导致两者很难各自扩展

2 代码优化

2.1 解决增加粉丝问题

先来解决上述第1个问题,使得增加粉丝的时候不用再修改workUpdate方法

首先,我们将“大V”抽象成一个类Star,用数组fans来保存粉丝列表,并新增一个添加粉丝的方法addFans

class Star {
  constructor() {
    this.fans = [];
  }
  addFans(fan) {
    this.fans.push(fan)
  }
  workUpdate() {
    const songs = this.getSongs();
    const videos = this.getVideos();
    this.fans.forEach((item) => item.update(songs, videos));
  }
  getSongs() {
    return "MP3";
  }
  getVideos() {
    return "MP4";
  }
}

接着,将“粉丝”也抽象成一个类Fan,我们在创建粉丝对象的时候传入“大V”对象,调用该大V的addFans方法来添加到粉丝列表

class Fan {
  constructor(name, star) {
    this.name = name
    this.star = star
    this.star.addFans(this)
  }
  update(songs, videos) {
    console.log(songs, videos);
  }
}

现在我们添加粉丝就不必再更改代码了

const nami = new Star()
const luffy = new Fan("luffy", nami);
const zoro = new Fan("zoro", nami);
const sanji = new Fan("sanji", nami);
const robin = new Fan("robin", nami);
nami.workUpdate()

2.2 解决添加作品问题

我们新增一个works数组来保存大V的作品,并且为其添加getset方法

class Star {
  constructor() {
    this.fans = [];
    this.works = [];
  }
  addFans(fan) {
    this.fans.push(fan);
  }
  setWorks(work) {
    this.works.push(work);
    // 添加作品后,调用更新方法
    this.workUpdate();
  }
  getWorks() {
    return this.works;
  }
  workUpdate() {
    this.fans.forEach((item) => item.update());
  }
}

对类Fan进行相应修改:

class Fan {
  constructor(name, star) {
    this.name = name
    this.star = star
    this.star.addFans(this)
  }
  update() {
    console.log(`${this.name}:${this.star.getWorks()}`)
  }
}

现在大V添加作品就不必再更改代码了:

const nami = new Star();
nami.setWorks('song')
nami.setWorks('video')
nami.setWorks('novel')
const luffy = new Fan("luffy", nami);
const zoro = new Fan("zoro", nami);
const sanji = new Fan("sanji", nami);
nami.workUpdate();

3 观察者模式

可以看到,在上述例子中,一个nami对象和多个粉丝对象之间存在着一种一对多的依赖关系,当nami对象有作品更新的时候,所有关注她的粉丝对象都会收到通知。

事实上,这就是观察者模式

观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

我们将2.2中的代码进行进一步的抽象:

将“粉丝”看作观察者(Observer),将“大V”看作被观察的对象,称为主题(Subject)

Subject维护一个观察者列表observerList(原fans数组)。当Subject的状态发生变化(原作品更新)时,通过调用notify(原workUpdate)方法通知所有观察者,执行它们的update方法

具体代码如下:

// 被观察者:主题
class Subject {
  constructor() {
    this.observerList = [];
    // 代表主题状态
    this.state = 0;
  }
  addObserver(observer) {
    this.observerList.push(observer);
  }
  // 更改主题状态
  setState(state) {
    this.state = state;
    // 状态改变后,通知所有观察者
    this.notify();
  }
  getState() {
    return this.state;
  }
  notify() {
    this.observerList.forEach((observer) => observer.update());
  }
}

// 观察者
class Observer {
  constructor(name, subject) {
    this.name = name;
    this.subject = subject;
    this.subject.addObserver(this);
  }
  update() {
    console.log(`${this.name}:${this.subject.state}`);
  }
}

4 经纪人登场

由于大V业务繁忙,所以他们需要经纪人来维持艺人与粉丝的联系

经纪人的工作包括:

  • 维护大V的粉丝,经纪人手中会有一个粉丝名单
  • 大V的新作品会交给经纪人,经纪人则负责把新作品发送给粉丝名单中的粉丝

抽象成一个类,如下:

class Manager {
  constructor() {
    this.fans = [];
    this.works = [];
  }
  addFans(fan) {
    this.fans.push(fan);
  }
  setWorks(work) {
    this.works.push(work);
    // 添加作品后,调用更新方法
    this.workUpdate();
  }
  getWorks() {
    return this.works;
  }
  workUpdate() {
    this.fans.forEach((item) => item.update());
  }
}

嗯?这段代码貌似在哪儿见过?

没错,和2.2的Star类一模一样,只不过把类名改了改。

那这么做有意义吗?

事实上,代码一模一样是因为在2.2的Star类中我们只写了有关发布(即发布作品)和订阅(即维护粉丝列表)的功能;而Star类本身可能不止这个工作,比如创作内容。

现在我们将Star类中的发布和订阅的工作抽离出来,交给Manager全权负责。而Star类只要在创作完成后把作品交给Manager就可以了

另一方面,粉丝Fan也不再直接和Star发生交互了,Fan只关心能不能收到作品,所以Fan直接和Manager发生交互,Fan去订阅(这个行为相当于在Manager维护的粉丝列表中添加粉丝)Manager并从Manager那儿获取想要的作品

于是Star和Fan的代码如下:

class Star {
  constructor() {}
  // 创作
  create(manager) {
    // 将创作的new work交给经纪人
    manager.setWorks("new work");
  }
}

class Fan {
  constructor(name, manager) {
    this.name = name;
    this.manager = manager;
    this.manager.addFans(this);
  }
  update() {
    console.log(`${this.name}:${this.manager.getWorks()}`);
  }
}

5 发布订阅模式

前面我们用了经纪人来负责发布和订阅的工作,而不让StarFan发生直接交互,达到了两者解耦的效果

这就是发布订阅模式

我们将4中的Manager进行进一步的抽象:

将“粉丝”看作订阅者(Subscriber);将“大V”看作内容的发布者,在发布订阅模式中称为发布者(Publisher);把“经纪人”看作发布订阅中心(或者说中间人Broker)

具体代码如下:

// 发布订阅调度中心
class Broker {
  constructor() {
    this.subscribers = [];
    // 代表主题状态
    this.state = 0;
  }
  // 订阅
  subscribe(subscriber) {
    this.subscribers.push(subscriber);
  }
  // 更改主题状态
  setState(state) {
    this.state = state;
    // 状态改变后,发布
    this.publish();
  }
  getState() {
    return this.state;
  }
  // 发布
  publish() {
    this.subscribers.forEach((subscriber) => subscriber.update());
  }
}

// 发布者
class Publisher {
  constructor() {}
  changeState(broker, state) {
    broker.setState(state);
  }
}

class Subscriber {
  constructor(name, broker) {
    this.name = name;
    this.broker = broker;
    this.broker.subscribe(this);
  }
  update() {
    console.log(`${this.name}:${this.broker.getState()}`);
  }
}

来运行一下看看效果:

// 创建调度中心
const broker = new Broker()
// 创建发布者
const publisher = new Publisher()
// 创建订阅者
const subscribe1 = new Subscriber('s1', broker)
const subscribe2 = new Subscriber('s2', broker)
const subscribe3 = new Subscriber('s3', broker)
// 发布者改变状态并通知调度中心,调度中心就会通知各个订阅者
publisher.changeState(broker, 1)

6 观察者模式和发布订阅模式的对比

从角色数量看

  • 观察者模式只有两个角色:观察者和被观察者
  • 发布订阅模式有三个角色:发布者、订阅者以及中间人(发布订阅中心)

从耦合程度看

  • 观察者模式处于一种松耦合的状态,即两者依然有交互,但是又很容易各自扩展且不相互影响
  • 发布订阅模式中的发布者和订阅者则完全不存在耦合,达到了对象之间解耦的效果

从意图来看

  • 两者都:实现了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知并自动更新

到此这篇关于用JS写一个发布订阅模式的文章就介绍到这了,更多相关JS写一个发布订阅模式内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
Firebug 字幕文件JSON地址获取代码
Oct 28 Javascript
jquery中html、val与text三者属性取值的联系与区别介绍
Dec 29 Javascript
超级好用的jQuery圆角插件 Corner速成
Aug 31 Javascript
JavaScript原生对象之Number对象的属性和方法详解
Mar 13 Javascript
jQuery实现的产品自动360度旋转展示特效源码分享
Aug 21 Javascript
JS遍历数组和对象的区别及递归遍历对象、数组、属性的方法详解
Jun 14 Javascript
jquery easyUI中ajax异步校验用户名
Aug 19 Javascript
KnockoutJS 3.X API 第四章之click绑定
Oct 10 Javascript
vue多级多选菜单组件开发
Sep 08 Javascript
vue.js整合vux中的上拉加载下拉刷新实例教程
Jan 09 Javascript
jQuery实现获取form表单内容及绑定数据到form表单操作分析
Jul 03 jQuery
vue搜索页开发实例代码详解(热门搜索,历史搜索,淘宝接口演示)
Apr 11 Javascript
浅谈JavaScript浅拷贝和深拷贝
JavaScript严格模式不支持八进制的问题讲解
Javascript使用integrity属性进行安全验证
Nov 07 #Javascript
JavaScript中时间格式化新思路toLocaleString()
Nov 07 #Javascript
JavaScript中isPrototypeOf函数
JavaScript原型链详解
JavaScript组合继承详解
You might like
支持php4、php5的mysql数据库操作类
2008/01/10 PHP
解析获取优酷视频真实下载地址的PHP源代码
2013/06/26 PHP
PHP源码分析之变量的存储过程分解
2014/07/03 PHP
初识Laravel
2014/10/30 PHP
jQuery向下滚动即时加载内容实现的瀑布流效果
2016/01/07 PHP
40款非常棒的jQuery 插件和制作教程(系列二)
2011/11/02 Javascript
在javascript中对于DOM的加强
2013/04/11 Javascript
如何使用JS获取IE上传文件路径(IE7,8)
2013/07/08 Javascript
javascript删除字符串最后一个字符
2014/01/14 Javascript
node.js中的console.trace方法使用说明
2014/12/09 Javascript
如何高效率去掉js数组中的重复项
2016/04/12 Javascript
jQuery实现图片轮播效果代码(基于jquery.pack.js插件)
2016/06/02 Javascript
js 动态添加元素(div、li、img等)及设置属性的方法
2016/07/19 Javascript
jQuery添加新内容的四个常用方法分析【append,prepend,after,before】
2019/03/19 jQuery
微信小程序使用蓝牙小插件
2019/09/23 Javascript
js实现坦克移动小游戏
2019/10/28 Javascript
[59:08]DOTA2上海特级锦标赛C组小组赛#2 LGD VS Newbee第一局
2016/02/27 DOTA
Python中线程编程之threading模块的使用详解
2015/06/23 Python
Python在信息学竞赛中的运用及Python的基本用法(详解)
2017/08/15 Python
python出现"IndentationError: unexpected indent"错误解决办法
2017/10/15 Python
Python paramiko模块的使用示例
2018/04/11 Python
Python使用sort和class实现的多级排序功能示例
2018/08/15 Python
Python数据可视化实现正态分布(高斯分布)
2019/08/21 Python
基于Python实现船舶的MMSI的获取(推荐)
2019/10/21 Python
pytorch中获取模型input/output shape实例
2019/12/30 Python
将tensorflow.Variable中的某些元素取出组成一个新的矩阵示例
2020/01/04 Python
Python smtp邮件发送模块用法教程
2020/06/15 Python
美国名牌太阳镜折扣网站:Eyedictive
2017/05/15 全球购物
英国著名书店:Foyles
2018/12/01 全球购物
绢花、人造花和人造花卉:BLOOM
2019/08/07 全球购物
部队党性分析材料
2014/02/16 职场文书
暑假学习心得体会
2014/09/02 职场文书
2014年度党员自我评议
2014/09/13 职场文书
2015年前台文员工作总结
2015/05/18 职场文书
新年晚会主持词开场白
2015/05/28 职场文书
上个世纪50年代的可穿戴技术:无线电帽子
2022/02/18 无线电