0Pricing
PHP Academy · レッスン

振る舞いパターン:Strategy、Observer、Command

振る舞いパターンで、動作と通信をモデル化します。

「振る舞いパターン:Strategy、Observer、Command」はCoddyKit上の無料PHP Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはPHP Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 PHP Academyコースには全4レッスンが含まれています。

振る舞いをモデル化する

振る舞いパターンは、オブジェクトがどのように協調し、責任を分担するかを定めます。ここでは、実際のPHPシステムで頻繁に登場する次の3つに焦点を当てます。

  • Strategy — 実行時にアルゴリズムを差し替えます。
  • Observer — 関心のあるリスナーにイベントを通知します。
  • Command — リクエストを、キューに入れたり、ログに記録したり、取り消したりできるオブジェクトに変換します。

Strategy:考え方

Strategyパターンは、交換可能なアルゴリズムのファミリーを1つのインターフェースの背後に定義し、クライアントが実行時に選択できるようにします。以下の複雑なswitchをポリモーフィズムに置き換え、コンテキストは保持しているStrategyに処理を委譲します。

<?php
// The smell Strategy removes: branching on a type code
function price(string $type, float $base): float {
    switch ($type) {
        case 'standard': return $base + 5;
        case 'express':  return $base + 15;
        default: throw new InvalidArgumentException($type);
    }
}
echo price('express', 10), PHP_EOL; // 25

コードで見るStrategy

ここでは、配送費が選択した配送業者によって変わります。各Strategyが1つの料金計算アルゴリズムをカプセル化するため、配送業者を追加してもコンテキストは変更せずに済みます。

<?php
interface ShippingStrategy { public function cost(float $weight): float; }
final class Standard implements ShippingStrategy {
    public function cost(float $w): float { return 5 + $w * 0.5; }
}
final class Express implements ShippingStrategy {
    public function cost(float $w): float { return 15 + $w * 1.2; }
}

final class Cart {
    public function __construct(private ShippingStrategy $strategy) {}
    public function setStrategy(ShippingStrategy $s): void { $this->strategy = $s; }
    public function quote(float $weight): float { return $this->strategy->cost($weight); }
}

$cart = new Cart(new Standard());
echo $cart->quote(10), PHP_EOL;     // 10
$cart->setStrategy(new Express());
echo $cart->quote(10), PHP_EOL;     // 27

クロージャーを使ったStrategy

PHPでは、Strategyに必ずしもクラスが必要とは限りません。軽量なケースでは、callableや第一級 callable 構文をStrategyとしてそのまま利用できます。アルゴリズム自体に状態や依存関係がある場合は完全なクラスを使い、単純でステートレスな選択肢にはクロージャーを使います。

<?php
$strategies = [
    'asc'  => fn(array $a) => sort($a) ? $a : $a,
    'desc' => fn(array $a) => rsort($a) ? $a : $a,
];
$data = [3, 1, 2];
print_r($strategies['desc']($data)); // [3,2,1]

Observer:考え方

Observerパターンでは、subjectの状態が変化したとき、誰が購読しているかを知らなくても、多数のobserverに通知できます。これにより、イベントの発生元とハンドラーが分離され、イベントシステム、フック、リアクティブな更新の基盤になります。

コードで見るObserver

subjectはobserverの一覧を保持し、それぞれにupdate()を呼び出します。subjectのロジックを変更せずに、新しいリスナーを接続できます。

<?php
interface Observer { public function update(string $event): void; }

final class OrderSubject {
    private array $observers = [];
    public function subscribe(Observer $o): void { $this->observers[] = $o; }
    public function placed(string $id): void {
        foreach ($this->observers as $o) { $o->update("order $id placed"); }
    }
}
final class EmailObserver implements Observer {
    public function update(string $e): void { echo "email: $e" . PHP_EOL; }
}
final class AuditObserver implements Observer {
    public function update(string $e): void { echo "audit: $e" . PHP_EOL; }
}

$subject = new OrderSubject();
$subject->subscribe(new EmailObserver());
$subject->subscribe(new AuditObserver());
$subject->placed('42');

ObserverとPSR-14

現代のPHPでは、ObserverをPSR-14イベントディスパッチャーで表現することがよくあります。ディスパッチャーがイベントオブジェクトを登録済みのリスナーへ振り分けます。基本的な考え方は同じ(subject=イベント、observer=リスナー)ですが、ディスパッチャーは両者をさらに分離し、停止可能なイベントにも対応します。SPLにはSplObserver/SplSubjectが存在しますが、現在ではほとんど使われません。

Command:考え方

Commandパターンは、リクエストをオブジェクトとしてカプセル化します。そこにはアクション、受信者、パラメーターが含まれます。リクエストがデータになったことで、キューに入れたり、ログに記録したり、再試行したり、取り消したりできます。最も単純なCommandは、以下に示すように意図を値として具象化したものです。

<?php
final class SendEmail {
    public function __construct(
        public readonly string $to,
        public readonly string $subject
    ) {}
}
// The request is now data you can store, serialize, or enqueue
$cmd = new SendEmail('user@example.com', 'Welcome');
echo json_encode($cmd), PHP_EOL;

コードで見るCommand

各Commandはexecute()を実装します。InvokerはCommandを保持して実行しますが、Commandが何をするかには依存しません。undo()を追加すれば、完全な取り消しスタックになります。

<?php
interface Command { public function execute(): string; }

final class Light { public bool $on = false; }
final class TurnOn implements Command {
    public function __construct(private Light $light) {}
    public function execute(): string { $this->light->on = true; return 'light on'; }
}
final class TurnOff implements Command {
    public function __construct(private Light $light) {}
    public function execute(): string { $this->light->on = false; return 'light off'; }
}

final class Invoker {
    private array $queue = [];
    public function add(Command $c): void { $this->queue[] = $c; }
    public function run(): void {
        foreach ($this->queue as $c) { echo $c->execute(), PHP_EOL; }
    }
}

$light = new Light();
$inv = new Invoker();
$inv->add(new TurnOn($light));
$inv->add(new TurnOff($light));
$inv->run();

Command Bus

フレームワークはこのパターンを基にCommand Busを構築します。Command DTOが1つのハンドラーにだけディスパッチされます。Busの周囲にミドルウェアを配置すれば、トランザクション、バリデーション、ロギングを一律に追加できます。Commandは単純なシリアライズ可能オブジェクトなので、同じハンドラーを同期的に実行することも、キューワーカーに渡すこともできます。

使い分け

  • Strategy:1つの操作に対して複数の交換可能なアルゴリズムがあり、呼び出し側が選択する場合
  • Observer:1つのイベントに対して複数の独立した反応があり、発生元がハンドラーを知らない場合
  • Command:キュー投入、ロギング、再試行、取り消しができるよう、リクエストを具象化する場合

これらは相性よく組み合わせられます。たとえば、CommandのハンドラーがStrategyを選択し、Observerのイベントを発行できます。

理解度チェック

要件に対応するパターンを選んでください。

まとめ

3つのパターンを使って振る舞いをモデル化しました。Strategyで実行時にアルゴリズムを差し替え(クラスまたはクロージャー)、Observerで分離されたリスナーに状態変化を通知し(PSR-14ディスパッチャーの基盤)、Commandでキューに入れたり、ログに記録したり、取り消したりできるオブジェクトにリクエストを変換しました(Command Busの基盤)。これらを組み合わせることで、協調関係を柔軟に保ち、責任を明確にできます。

よくある質問

「振る舞いパターン:Strategy、Observer、Command」レッスンは無料ですか?

はい。「振る舞いパターン:Strategy、Observer、Command」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、PHP Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 PHP Academyコースには全4レッスンが含まれています。

「振る舞いパターン:Strategy、Observer、Command」で何を学びますか?

振る舞いパターンで、動作と通信をモデル化します。 ブラウザで直接実行するハンズオンコードでPHP Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

PHP Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのPHP Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「振る舞いパターン:Strategy、Observer、Command」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このPHP Academyレッスンでコードを書いて実行できますか?

はい。すべてのPHP Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. SOLID原則の実践
  2. 生成パターン:Factory、Builder、Singleton
  3. 構造パターン:Adapter、Decorator、Facade
  4. 振る舞いパターン:Strategy、Observer、Command
← PHP Academyに戻る