用JS写一个发布订阅模式

编辑: admin 分类: javascript 发布时间: 2021-11-17 来源:互联网
目录
  • 1 场景引入
  • 2 代码优化
    • 2.1 解决增加粉丝问题
    • 2.2 解决添加作品问题
  • 3 观察者模式
    • 4 经纪人登场
      • 5 发布订阅模式
        • 6 观察者模式和发布订阅模式的对比

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

          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";
            }
            getVid韩国cn2服务器http://www.558idc.com/kt.htmleos() {
              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写一个发布订阅模式内容请搜索hwidc以前的文章或继续浏览下面的相关文章希望大家以后多多支持hwidc!