觀察者模式讓物件可以被動地接收訊息,而不需要主動追蹤主題的變化。
大家平時有收聽 Podcast 的習慣嗎?我時常在捷運上聽 Podcast,聽聽主持人的生活趣事,順便打發漫長的移動時間。我有幾個特別喜歡的節目,但總是記不得這些節目的更新時間。幸好有訂閱功能,讓我在每次節目更新時收到通知,而不用一一查看它們是否發布新集數。
觀察者模式就像 Podcast 的訂閱功能,它讓用戶訂閱感興趣的服務,並在服務更新時通知訂閱者,節省訂閱者反覆確認服務狀態的麻煩。
假設我們要建立一個郵件系統,在收到郵件時更新信箱並通知使用者。我們可以將郵件系統定義為主題,再讓信箱與通知中心訂閱郵件系統。這樣,每當系統收到新郵件時就會通知這些服務。
Observer
和 Observable
是觀察者介面和主題(被觀察者)介面,前者定義了觀察者的更新方法,後者則定義了新增、移除以及通知觀察者的方法。
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();
觀察者模式透過訂閱制的方式繫結主題與觀察者物件,每當主題改變狀態就會通知觀察者,觀察者可以被動地接收訊息而不需持續檢查主題狀態。
就像現實生活中的訂閱制服務一樣,觀察者物件可以隨時建立、解除訂閱關係。由於主題只依賴於抽象的觀察者介面,觀察者的增減不會影響主題的程式運作,充分展現出開放封閉原則的優點。
https://github.com/chengen0612/design-patterns-typescript/blob/main/patterns/behavioral/observer.ts