面试官:能用JS写一个发布订阅模式吗?

更新日期: 2021-11-03阅读: 1.2k标签: 模式

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

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";
},
};

现在问题来了

  1. 如果Nami又收获了一个粉丝Robin,我既要添加一个robin对象,又要修改workUpdate方法
  2. 如果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的作品,并且为其添加get和set方法

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业务繁忙,所以他们需要经纪人来维持艺人与粉丝的联系

经纪人的工作包括:

  1. 维护大V的粉丝,经纪人手中会有一个粉丝名单
  2. 大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 发布订阅模式

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

这就是发布订阅模式

我们将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 观察者模式和发布订阅模式的对比

从角色数量看

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

从耦合程度看

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

从意图来看

  • 两者都:实现了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知并自动更新
公众号【前端嘛】
原文链接:https://www.cnblogs.com/bidong/archive/2021/11/03/15501903.html


链接: https://fly63.com/article/detial/10771

js设计模式之单例模式,javascript如何将一个对象设计成单例

单例模式是我们开发中一个非常典型的设计模式,js单例模式要保证全局只生成唯一实例,提供一个单一的访问入口,单例的对象不同于静态类,我们可以延迟单例对象的初始化,通常这种情况发生在我们需要等待加载创建单例的依赖。

前端设计模式:从js原始模式开始,去理解Js工厂模式和构造函数模式

工厂模式下的对象我们不能识别它的类型,由于typeof返回的都是object类型,不知道它是那个对象的实例。另外每次造人时都要创建一个独立的person的对象,会造成代码臃肿的情况。

JavaScript设计模式_js实现建造者模式

建造者模式:是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象

html和xhtml,DOCTYPE和DTD,标准模式和兼容模式

主要涉及知识点: HTML与XHTML,HTML与XHTML的区别,DOCTYPE与DTD的概念,DTD的分类以及DOCTYPE的声明方式,标准模式(Standard Mode)和兼容模式(Quircks Mode),标准模式(Standard Mode)和兼容模式(Quircks Mode)的区别

前端四种设计模式_JS常见的4种模式

JavaScript中常见的四种设计模式:工厂模式、单例模式、沙箱模式、发布者订阅模式

javascript 策略模式_理解js中的策略模式

javascript 策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。 策略模式利用组合,委托等技术和思想,有效的避免很多if条件语句,策略模式提供了开放-封闭原则,使代码更容易理解和扩展, 策略模式中的代码可以复用。

javascript观察者模式_深入理解js中的观察者模式

javascript观察者模式又叫发布订阅模式,观察者模式的好处:js观察者模式支持简单的广播通信,自动通知所有已经订阅过的对象。存在一种动态关联,增加了灵活性。目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。

Vue中如何使用方法、计算属性或观察者

熟悉 Vue 的都知道 方法methods、计算属性computed、观察者watcher 在 Vue 中有着非常重要的作用,有些时候我们实现一个功能的时候可以使用它们中任何一个都是可以的

我最喜欢的 JavaScript 设计模式

我觉得聊一下我爱用的 JavaScript 设计模式应该很有意思。我是一步一步才定下来的,经过一段时间从各种来源吸收和适应直到达到一个能提供我所需的灵活性的模式。让我给你看看概览,然后再来看它是怎么形成的

Flutter 设计模式 - 简单工厂

在围绕设计模式的话题中,工厂这个词频繁出现,从 简单工厂 模式到 工厂方法 模式,再到 抽象工厂 模式。工厂名称含义是制造产品的工业场所,应用在面向对象中,顺理成章地成为了比较典型的创建型模式

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!