🕊️
デザインパターン:Observerパターン
これは何?
状態変化を通知するパターン
登場人物は以下となる。
🫙サブジェクト :監視対象となるオブジェクト
🗣️パブリッシャー :サブジェクトを監視し、サブスクライバーに通知する
👀サブスクライバー:パブリッシャーからの通知をトリガーに処理をする
何が嬉しいのか
オブジェクト間の結合を緩くできる
状態変化を効率的に伝達する
コードの拡張性を高める
問題
あるオフィスでは、1秒おきにペットボトルの状態が監視されています。
この緊迫した状況で、空のペットボトルを引き金に今日もまた悲しい事件が起きました。
ある社員のサブスクライバーが怒りをあらわにし、
その他社員のサブスクライバーは怯え発狂したのです。
この悲しき人治主義的なシステムに別れを告げ、
サブスクライバーの生産性と心理的安全性は救われなければなりません。
/* ----------------------------------------
* 無垢なペットボトル
---------------------------------------- */
class PetBottle {
private _isEmpty = false;
constructor(private readonly onEmpty: () => void) {}
get isEmpty(): boolean {
return this._isEmpty;
}
empty(): void {
this._isEmpty = true;
this.onEmpty();
}
}
/* ----------------------------------------
/* 怒りに震えるサブスクライバー
---------------------------------------- */
class AngrySubscriber {
constructor(private readonly petBottle: PetBottle) {
this.petBottle.onEmpty = this.onEmpty.bind(this);
}
private onEmpty(): void {
console.log("怒りに震えるサブスクライバー: 見つけた!");
// ペットボトルを捨てる処理
}
}
/* ----------------------------------------
/* 怯えるサブスクライバー
---------------------------------------- */
class ScaredSubscriber {
constructor(private readonly petBottle: PetBottle) {
this.petBottle.onEmpty = this.onEmpty.bind(this);
}
private onEmpty(): void {
console.log("怯えるサブスクライバー: ああああああああああああああ!!!!!!!!!!!!!!");
// ペットボトルを捨てる処理
}
}
/* ----------------------------------------
/* 一連の出来事
---------------------------------------- */
const petBottle = new PetBottle(() => {
// ペットボトルが空になったときの処理
console.log("ペットボトルが空になりました");
});
const angrySubscriber = new AngrySubscriber(petBottle);
const scaredSubscriber = new ScaredSubscriber(petBottle);
// サブスクライバーによる状態監視
setInterval(() => {
if (petBottle.isEmpty) {
// ペットボトルが空になったときの処理
console.log("ペットボトルが空になりました");
petBottle.empty();
}
}, 1000);
解決
世界はカッティングエッジな技術の産声に耳を傾けます。
ペットボトルはサブジェクトであり、またパブリッシャーになりました。
- ペットボトルは空になると、自らの状態変化をサブスクライバーへ通知します
→ サブスクライバーはペットボトルの監視業務から解放されます
→ 通知はタスク依頼と認識され、サブスクライバーの心理的安全性が確保されます
/* ----------------------------------------
* カッティングエッジなペットボトル
---------------------------------------- */
class PetBottle {
private _isEmpty = false;
private readonly _subscribers: Observer[] = [];
constructor() {}
get isEmpty(): boolean {
return this._isEmpty;
}
empty(): void {
this._isEmpty = true;
this.notify("ペットボトルが空になりました");
}
subscribe(observer: Observer): void {
this._subscribers.push(observer);
}
unsubscribe(observer: Observer): void {
const index = this._subscribers.indexOf(observer);
if (index >= 0) {
this._subscribers.splice(index, 1);
}
}
private notify(message: string): void {
for (const subscriber of this._subscribers) {
subscriber.update(message);
}
}
}
/* ----------------------------------------
* 役職としてのサブスクライバー
---------------------------------------- */
// 抽象的なサブスクライバー
interface Subscriber {
update(message: string): void;
}
/* ----------------------------------------
* 救われたサブスクライバー達
---------------------------------------- */
// もう怒りに震えないサブスクライバー
class AngrySubscriber implements Subscriber {
constructor(private readonly name: string) {}
update(message: string): void {
console.log(`${this.name}: 「${message}」ね。そうか、これが静寂か。`);
}
}
// もう怯えないサブスクライバー
class ScaredSubscriber implements Subscriber {
constructor(private readonly name: string) {}
update(message: string): void {
console.log(`${this.name}: 「${message}」ですって。いっちょ片付けますか!`);
}
}
/* ----------------------------------------
* 一連の出来事
---------------------------------------- */
const petBottle = new PetBottle();
const angrySubscriber = new AngrySubscriber("もう怒りに震えないサブスクライバー");
const scaredSubscriber = new ScaredSubscriber("もう怯えないサブスクライバー");
petBottle.subscribe(angrySubscriber);
petBottle.subscribe(scaredSubscriber);
petBottle.empty();
その他の推しポイント
- SNSの通知機能など、利用できる場面が多い
- 教材はサブジェクトとパブリッシャーを結合しているが、分離することでさらに柔軟性が高くなる
- パブリッシャーを複数作成し、粒度の異なる情報が提供できる
動く実装例
Discussion