Observerパターン
CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 73/91。
オブザーバー・パターン(Observer Pattern)は、オブジェクト間に1対多のリレーションシップを構築する振る舞いに関するデザインパターンです。1つのオブジェクト(サブジェクト)の状態が変化すると、そのすべての依存関係にあるもの(オブザーバー)に自動的に通知され、更新されます。
ニュースレターの購読を考えてみましょう。ニュースレターがサブジェクトであり、購読者がオブザーバーです。新しい号が発行されると、ニュースレター側が各購読者の詳細を知る必要なく、すべての購読者が自動的にそれを受け取ります。
PHPでの基本的な実装は以下の通りです:
<?php
interface Observer {
public function update(string $message): void;
}
class Newsletter {
private array $subscribers = [];
public function subscribe(Observer $observer): void {
$this->subscribers[] = $observer;
}
public function publish(string $content): void {
foreach ($this->subscribers as $subscriber) {
$subscriber->update($content);
}
}
}
class EmailSubscriber implements Observer {
public function __construct(private string $email) {}
public function update(string $message): void {
echo "Sending to {$this->email}: $message\n";
}
}
$newsletter = new Newsletter();
$newsletter->subscribe(new EmailSubscriber("alice@example.com"));
$newsletter->subscribe(new EmailSubscriber("bob@example.com"));
$newsletter->publish("New article released!");
出力:
Sending to alice@example.com: New article released!
Sending to bob@example.com: New article released!このパターンは、サブジェクトとそのオブザーバーを分離します。Newsletter クラスは、メール購読者、SMS購読者、またはプッシュ通知サービスのどれに通知しているかを知る必要はありません。Observer インターフェースを実装しているものに対して、単に update() を呼び出すだけです。これにより、既存のコードを修正することなく、新しいオブザーバーの種類を簡単に追加できるようになります。
チャレンジ
簡単オブザーバーパターン(Observer Pattern)を使用して、株価アラートシステムを構築しましょう。株価が変動したときに、登録されているすべての投資家に自動的に通知が送信されるようにします。これは、このパターンの完璧な実世界での応用例です。
コードは4つのファイルに分けて構成します:
Observer.php—update(string $stockName, float $price): voidという単一のメソッドを持つObserverインターフェースを定義します。これは、すべてのオブザーバーが通知を受け取るために従わなければならない契約です。Investors.php— Observer ファイルをインクルードし、インターフェースを実装する2つのクラスを作成します:EmailInvestor— コンストラクタでメールアドレスを受け取ります(コンストラクタのプロパティ昇格を使用してください)。そのupdate()メソッドは"Email to {$this->email}: {$stockName} is now \${$price}\n"を出力します。SmsInvestor— コンストラクタで電話番号を受け取ります。そのupdate()メソッドは"SMS to {$this->phone}: {$stockName} is now \${$price}\n"を出力します。
Stock.php— Observer ファイルをインクルードし、サブジェクト(主題)として機能するStockクラスを作成します。この株式クラスは以下の機能を持つ必要があります:- 銘柄名と現在の価格を保持する(銘柄名にはコンストラクタのプロパティ昇格を使用し、価格は
0.0で初期化します) - オブザーバーのプライベート配列を維持する
- オブザーバーを追加するための
subscribe(Observer $observer): voidメソッドを持つ - 価格を更新し、すべてのオブザーバーに通知する
setPrice(float $price): voidメソッドを持つ
update()メソッドを銘柄名と新しい価格で呼び出します。- 銘柄名と現在の価格を保持する(銘柄名にはコンストラクタのプロパティ昇格を使用し、価格は
main.php— Investors と Stock ファイルをインクルードします。銘柄名、メールアドレス、電話番号の3つの入力を受け取ります。指定された名前で
Stockを作成します。メールアドレスを指定してEmailInvestorを、電話番号を指定してSmsInvestorをサブスクライブ(登録)させます。株価を
150.50に設定して、すべての購読者に通知をトリガーします。
オブザーバーパターンを使用することで、Stock クラスを具体的な通知方法から切り離された(疎結合な)状態に保つことができます。これにより、後で Stock クラスを一切変更することなく、PushInvestor や SlackInvestor を簡単に追加できるようになります。
チートシート
オブザーバーパターン(Observer Pattern)は、サブジェクトの状態が変化したときに、すべてのオブザーバーに自動的に通知する「一対多」のリレーションシップを確立します。
主要なコンポーネント:
- オブザーバーインターフェース(Observer Interface) —
update()メソッドを使用して、オブザーバーのコントラクトを定義します - サブジェクト(Subject) — オブザーバーのリストを保持し、状態の変化を通知します
- 具体的なオブザーバー(Concrete Observers) — 通知を受け取るためにオブザーバーインターフェースを実装します
基本的な実装:
<?php
interface Observer {
public function update(string $message): void;
}
class Subject {
private array $observers = [];
public function subscribe(Observer $observer): void {
$this->observers[] = $observer;
}
public function notify(string $data): void {
foreach ($this->observers as $observer) {
$observer->update($data);
}
}
}
class ConcreteObserver implements Observer {
public function __construct(private string $name) {}
public function update(string $message): void {
echo "Notifying {$this->name}: $message\n";
}
}
$subject = new Subject();
$subject->subscribe(new ConcreteObserver("Observer1"));
$subject->subscribe(new ConcreteObserver("Observer2"));
$subject->notify("State changed!");
メリット:
- サブジェクトとオブザーバーを疎結合にします
- 既存のコードを修正することなく、新しいオブザーバーの種類を簡単に追加できます
- 動的な購読(サブスクリプション)と解除をサポートします
自分で試してみよう
<?php
require_once 'Investors.php';
require_once 'Stock.php';
// 入力を読み込む
$stockName = trim(fgets(STDIN));
$email = trim(fgets(STDIN));
$phone = trim(fgets(STDIN));
// TODO: 指定された名前でStock(株式)を作成する
// TODO: 指定されたメールアドレスでEmailInvestorを購読させる
// TODO: 指定された電話番号でSmsInvestorを購読させる
// TODO: 通知をトリガーするために株価を150.50に設定する
?>このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。