RitoLabo

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

  • 公開:
  • カテゴリ: PHP DesignPatterns
  • タグ: PHP,Behavior,DesignPatterns,Observer

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

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

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);
}

public 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>';

結果は以下の通りです。

PHPでのObserverパターン実行結果

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

まとめ

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

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

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

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