1. Home
  2. PHP
  3. DesignPatterns
  4. Observerパターン - PHPデザインパターン

Observerパターン - PHPデザインパターン

  • 公開日
  • カテゴリ:DesignPatterns
  • タグ:PHP,Behavior,DesignPatterns,Observer
Observerパターン - PHPデザインパターン

Observer パターン(オブザーバー・パターン)は、振る舞いに関するデザインパターン手法の一つで、状態変化を関連オブジェクトに通知し、付随する処理を行う関係を築く事で、各オブジェクトの依存性を切り離し、再利用性を高める処理モデルです。「出版(publication)-購読型(subscription)モデル」とも言われます。

Observer パターンの基本的なクラス図は以下の通りです。

実装例

今回は、掲示板でのコメント処理と、それに付随する裏側の処理について、Observer パターンで実装します。

掲示板でコメントを行うと Web ページ上に自分の発言が表示されますが、それ自体の処理とは別に、裏側では、コメントがあった事を知らせる処理が走ったりします。例えば、何らかのログが書き込まれたり、メールや Slack でコメントがあった事を通知する処理などです。

この「コメントを受け付け表示する」という、その WEB ページの一番のメイン部分と、「その事を通知する」というサブの部分を Observer パターンで切り離す事が目的です。

まずは Subject クラスのインターフェイスを作成します。

App/Interfaces/SubjectInterface.php
<?php

namespace App\Interfaces;

use App\Interfaces\ObserverInterface;

interface SubjectInterface
{
    public function addObserver(ObserverInterface $listener);
    public function removeObserver(ObserverInterface $listener);
    public function notify();
}

Observer パターンの場合、上記3つの機能がお約束なので、インターフェイスとしてこれを定めます。

  • addObserver() メソッドは、Observer を登録します。
  • removeObserver() メソッドは、Observer を削除します。
  • notify() メソッドは、状態変化を Observer へ通知します。

次に、Subject の具象クラス、つまり掲示板クラスを作成します。

App/Subjects/BulletinBoard.php
<?php

namespace App\Subjects;

use App\Interfaces\SubjectInterface;
use App\Interfaces\ObserverInterface;

class BulletinBoard implements SubjectInterface
{
    private $name;
    private $comments = [];
    private $listeners = [];

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function comment($message)
    {
        echo sprintf('%s : %s<br>',$this->getName() ,$message);
        $this->addComment($message);
    }

    private function addComment($message)
    {
        $this->comments[] = $message;
        $this->notify();
    }

    public function getComments()
    {
        return $this->comments;
    }

    public function getName()
    {
        return $this->name;
    }

    public function addObserver(ObserverInterface $listener)
    {
        $this->listeners[get_class($listener)] = $listener;
    }
    public function removeObserver(ObserverInterface $listener)
    {
        unset($this->listeners[get_class($listener)]);
    }
    public function notify()
    {
        foreach ($this->listeners as $listener) {
            $listener->execute($this);
        }
    }
}

序盤は掲示板へのコメントに対する処理ですが、後半で Subject インターフェイスを実装しています。

登録された Observer, つまりここではリスナーと呼びますが、登録されたリスナーをオブジェクト内で保持し、状態変化が起きた場合に notify() メソッドにて登録リスナーへ一斉に実行指示を出す、というのが大まかな流れです。あとは通知を受け取った各リスナーが所定の処理を実行(ここでは execute() メソッド)します。

次に、Observer クラスです。インターフェイスを作成します。

App/Interfaces/ObserverInterface.php
<?php

namespace App\Interfaces;

use App\Subjects\BulletinBoard;

interface ObserverInterface
{
    public function execute(BulletinBoard $board);
}

Observer クラスは共通インターフェイスとして、subject 具象クラスを引数に取る execute() メソッドを持ちます。

最後に、ログ書き込み・メール送信・ Slack通知を行う Observer の具象クラスを作成します。

App/Listeners/LoggingListener.php
<?php

namespace App\Listeners;

use App\Interfaces\ObserverInterface;
use App\Subjects\BulletinBoard;

class LoggingListener implements ObserverInterface
{
    public function execute(BulletinBoard $board)
    {
        // ログ書き込み処理
        echo'<small>ログ書き込みを行いました</small><br>';
    }
}
App/Listeners/MailListener.php
<?php

namespace App\Listeners;

use App\Interfaces\ObserverInterface;
use App\Subjects\BulletinBoard;

class MailListener implements ObserverInterface
{
    public function execute(BulletinBoard $board)
    {
        // メール送信処理
        echo'<small>メールの送信を行いました</small><br>';
    }
}
App/Listeners/SlackListener.php
<?php

namespace App\Listeners;

use App\Interfaces\ObserverInterface;
use App\Subjects\BulletinBoard;

class SlackListener implements ObserverInterface
{
    public function execute(BulletinBoard $board)
    {
        // Slack通知処理
        echo'<small>Slackでの通知を行いました</small><br>';
    }
}

上記に関しては、「その処理を行った」という前提で、代わりに文字列を出力しています。

これで実装が完了したので、クライアントから利用してみます。

index.php
<?php

use App\Subjects\BulletinBoard;
use App\Listeners\LoggingListener;
use App\Listeners\MailListener;
use App\Listeners\SlackListener;

// 掲示板クラス インスタンス生成
$user_1 = new BulletinBoard('rito');

// リスナー登録
$user_1->addObserver(new LoggingListener());
$user_1->addObserver(new MailListener());
$user_1->addObserver(new SlackListener());


$user_1->comment('おはよう');
echo '<hr>';

$user_1->comment('こんにちは');
echo '<hr>';

$user_1->comment('こんばんは');
echo '<hr>';

結果は以下の通りです。

メイン処理とサブ処理を Observer パターンで切り離し処理を実装できた事を確認できました。

まとめ

「Observer = 観察者」を意味する通り、Observer パターンでは観察する側(Observer) と観察される側(Subject) の関係を築き、Subject の状態が変化した際には Observer へその通知を行い、ある機能の一連の処理として必要な処理をさらに走らせるという流れになっています。

これを一つのコントローラやクラスで行うと、いくつもの処理や役割が複雑に入り組む、いわゆる深い依存関係が生まれてしまいますが、Observer パターンを用いる事で、各々の役割を分離させる事が出来、密結合を防ぎシンプルな関係を構築できます。

ただし、なんでもかんでも Observer パターンを用いると処理の流れが追いづらくなるという面もある為、計画的な設計が必要です。

とはいえ、オブジェクトを疎結合に保てる良い手法なので是非試してみてください。

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級