iT邦幫忙

2024 iThome 鐵人賽

DAY 7
0

觀察者模式讓物件可以被動地接收訊息,而不需要主動追蹤主題的變化。

生活案例

大家平時有收聽 Podcast 的習慣嗎?我時常在捷運上聽 Podcast,聽聽主持人的生活趣事,順便打發漫長的移動時間。我有幾個特別喜歡的節目,但總是記不得這些節目的更新時間。幸好有訂閱功能,讓我在每次節目更新時收到通知,而不用一一查看它們是否發布新集數。

觀察者模式就像 Podcast 的訂閱功能,它讓用戶訂閱感興趣的服務,並在服務更新時通知訂閱者,節省訂閱者反覆確認服務狀態的麻煩。

舉個例子

假設我們要建立一個郵件系統,在收到郵件時更新信箱並通知使用者。我們可以將郵件系統定義為主題,再讓信箱與通知中心訂閱郵件系統。這樣,每當系統收到新郵件時就會通知這些服務。

ObserverObservable 是觀察者介面和主題(被觀察者)介面,前者定義了觀察者的更新方法,後者則定義了新增、移除以及通知觀察者的方法。

interface Observer {
  update(observable: Observable): void;
}

interface Observable {
  attach(observer: Observer): void;
  detach(observer: Observer): void;
  notify(): void;
}

讓郵件系統實現主題介面。

class MailService implements Observable {
	// ...
  private observers: Observer[];

  constructor() {
		// ...
    this.observers = [];
  }

  attach(observer: Observer) {
    this.observers.push(observer);
  }

  detach(observer: Observer) {
    this.observers = this.observers.filter((o) => o !== observer);
  }

  notify() {
    for (const observer of this.observers) {
      observer.update(this);
    }
  }

  receiveMail(mail: Mail) {
    console.log(`\nMail arrives: ${mail.title}`);
    this.mails.push(mail);
    this.notify();
  }

  getLastMail() {
    return this.mails[this.mails.length - 1];
  }
}

讓信箱與通知中心實現觀察者介面。

class Mailbox implements Observer {
  private mails: Mail[];

  constructor() {
    this.mails = [];
  }

  update(observable: Observable) {
    if (observable instanceof MailService) {
      this.addMail(observable.getLastMail());
      this.printMails();
    }
  }

  private addMail(mail: Mail) {
    this.mails.push(mail);
  }

  private printMails() {
    console.log("Mailbox updated...");

    for (const mail of this.mails) {
      console.log(`- ${mail.title}`);
    }
  }
}

class NotificationCenter implements Observer {
  update(observable: Observable) {
    if (observable instanceof MailService) {
      const alert = this.createAlert(
        observable.name,
        observable.icon,
        observable.getLastMail().title
      );
      this.printAlert(alert);
    }
  }

  private createAlert(title: string, icon: string, content: string) {
    return new Alert(title, icon, content);
  }

  private printAlert(alert: Alert) {
    console.log(`${alert.title} ${alert.icon}: ${alert.content}`);
  }
}

讓信箱與通知中心註冊郵件系統,每當系統收到郵件就通知它們做出回應。如此一來,信箱與通知中心就不用持續確認郵件系統的最新狀態,郵件系統也不必依賴於這些服務。

class MailServiceTestDrive {
  static main() {
    const mailService = new MailService();

    const mailbox = new Mailbox();
    const notificationCenter = new NotificationCenter();

    mailService.attach(mailbox);
    mailService.attach(notificationCenter);

    mailService.receiveMail(new Mail("React as a full-stack framework?"));

    mailService.detach(notificationCenter);

    mailService.receiveMail(new Mail("What's new for us in ECMAScript 2024"));
  }
}

MailServiceTestDrive.main();

定義

Observer Pattern

  • 主題(Subject): 被觀察的對象,每當狀態改變就會通知觀察者
  • 觀察者(Observer): 對主題感興趣的對象,每當主題更新狀態便會做出回應

觀察者模式透過訂閱制的方式繫結主題與觀察者物件,每當主題改變狀態就會通知觀察者,觀察者可以被動地接收訊息而不需持續檢查主題狀態。

就像現實生活中的訂閱制服務一樣,觀察者物件可以隨時建立、解除訂閱關係。由於主題只依賴於抽象的觀察者介面,觀察者的增減不會影響主題的程式運作,充分展現出開放封閉原則的優點。

總結

  • 觀察者可以訂閱感興趣的主題,並在主題更新時收到通知
  • 觀察者只需要被動地接受訊息,而不需要持續追蹤主題的變化
  • 觀察者可以隨時建立、解除訂閱關係
  • 任何物件都可以實現觀察者介面成為觀察者

完整範例

https://github.com/chengen0612/design-patterns-typescript/blob/main/patterns/behavioral/observer.ts


上一篇
Day 06 - Strategy 策略
下一篇
Day 08 - Decorator 裝飾者
系列文
前端也想學設計模式30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言