天天看點

面試官:能用JS寫一個釋出訂閱模式嗎?

什麼是釋出訂閱模式?能手寫實作一下嗎?它和觀察者模式有差別嗎?...

目錄

  • 1 場景引入
  • 2 代碼優化
    • 2.1 解決增加粉絲問題
    • 2.2 解決添加作品問題
  • 3 觀察者模式
  • 4 經紀人登場
  • 5 釋出訂閱模式
  • 6 觀察者模式和釋出訂閱模式的對比

我們先來看這麼一個場景:

假設現在有一個社交平台,平台上有一個大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對象之間的耦合度太高,導緻兩者很難各自擴充

先來解決上述第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()
           

我們新增一個

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();
           

可以看到,在上述例子中,一個

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}`);
  }
}
           

由于大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()}`);
  }
}
           

前面我們用了經紀人來負責釋出和訂閱的工作,而不讓

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)
           

從角色數量看

  • 觀察者模式隻有兩個角色:觀察者和被觀察者
  • 釋出訂閱模式有三個角色:釋出者、訂閱者以及中間人(釋出訂閱中心)

從耦合程度看

  • 觀察者模式處于一種松耦合的狀态,即兩者依然有互動,但是又很容易各自擴充且不互相影響
  • 釋出訂閱模式中的釋出者和訂閱者則完全不存在耦合,達到了對象之間解耦的效果

從意圖來看

  • 兩者都:實作了對象間的一種一對多的依賴關系,當一個對象的狀态發生改變時,所有依賴于它的對象都将得到通知并自動更新

繼續閱讀