SwiftのDelegateをPHPで使って見る

by codechord. 0 Comments

  • このエントリーをはてなブックマークに追加
  • このエントリーをはてなブックマークに追加

geralt / Pixabay

一つのクラスに対して、何かしらの機能を追加したい場合、継承する方法を単純に思いつきますが、
SwiftにProtocolとDelegateという考え方がありましたので、PHPに置き換えてみたいと思います。

Protocolはhttpとかhttpsとかそういった話ではなく、単に「ルール」といった意味あいのもので、
PHPのinterface、LaravelなどのframeworkではContractと言われてているやつです。

Delegateは、直訳だと委譲とか。そういうやつ。
Delegateという単語を見ると身構えてしまいますが、
Interfaceを意識したプログラミングしてると、知らずのうちに似たような事をしているというケースは結構ありそうです。

Real world

掲示板を参考にしてみます。

  • 一覧を表示する
  • 投稿する

投稿する場合、データベースに投稿を保存することのなりますが、
掲示板の種類によっては、

  • 管理者に必ずメールを送信する
  • Slackに通知する
    などの機能を追加してみようと思います。

ここで作成したサンプルを少し修正したものは、githubにおきました。

基本形

掲示板クラスBBSがあって、基本的な機能はそこにまとまっているとします。

class Bbs
{
    protected $posts = [];

    function getAll()
    {
        return $this->posts;
    }

    function post($data)
    {
        $this->save($data);
    }

    function save($data)
    {
        return $this->posts[] = $data;
    }
}

class Main
    public $bbs;
    public function __construct()
    {
        $this->bbs = new Bbs();
    }

    public function index(){
        print_r($this->bbs>getAll());
    }

    public function run($data){
        $bbs = new Bbs();
        $bbs->post($data);
    }
}

使用する方は、次のような感じですね。

$main = new Main();

// 一覧取得
$main->index();

// 投稿
$main->post([
    'subject' => 'sample subject',
    'body'=> 'sample body',
    'user'=> 'sample user'
]);

Protocolと、Delegateの準備

Protocol の準備

ここでは2種用意します。

// メール機能を取り扱いますよというプロトコル
interface MailDelegate
{
    public function send();
}

// 通知機能を取り扱いますよというプロトコル
interface NotificationDelegate
{
    public function notify();
}

Delegate の準備

BBSクラスにDelegateを追加します。setterを用意して、上記Protocol
差分のみ記載します

class Bbs
{
    ...
    protected $mailDelegate;
    protected $notificationDelegate;

    function setMailDelegate(MailDelegate $delegate)
    {
        $this->mailDelegate = $delegate;
    }

    function setNotificationDelegate(NotificationDelegate $delegate)
    {
        $this->notificationDelegate = $delegate;
    }

    function post($data)
    {
        $this->save($data);

        if (isset($this->mailDelegate)) {
            $this->mailDelegate->send();
        }
        if (isset($this->notificationDelegate)) {
            $this->notificationDelegate->notify();
        }
    }

    ...

}

以上で準備は完了です。

利用

Case1: Protocolの実装を注入して例。

Slackに通知するイメージ。
プロトコルNotificationDelegateを実装したSlackNotificationクラス。

class SlackNotification implements NotificationDelegate
{
    public function notify()
    {
        // 本当はSlackに通知する処理
        print('message to slack');
    }
}

class Main{
    ...
    public function run(){
        $this->bbs->setNotificationDelegate(new SlackNotification());
        $this->bbs->post($data);
    }
}

$this->bbs->setNotificationDelegate(new SlackNotification());
の処理で、SlackNotificationが注入されるので、

$this->bbs->post($data);
の中の処理で、Slackに通知される処理が発火します。

実行結果は次のようになります

message to slack

Case2: 自身のクラスを直接注入する。

SwiftなどではこちらのケースでをViewControllerに書くことが多いみたい。
Mainクラスに、直接、プロトコル(interface)を追加します。

class Main implements MailDelegate, NotificationDelegate{
    ...
    public function run(){
        $this->bbs->setMailDelegate(new self());
        $this->bbs->setNotificationDelegate(new self());
        $this->bbs->post($data);
    }

    public function send()
    {
        print('send mail to admin');
    }

    public function notify()
    {
       print('message to chatwork');
    }
}

$this->bbs->setMailDelegate(new self());
$this->bbs->setNotificationDelegate(new self());
の処理で、自分自身をセットします

$this->bbs->post($data);
の中の処理で、Main::send()、Main::notify()が発火されるようになります。

実行結果は次のようになります

send mail to admin
message to chatwork

Conclusion

方法としては、

  • 機能だけを用意したクラスを外から注入する。
  • 拡張したいサービスを使おうとしているクラスに、プロトコルを追加して、機能を注入する。

Case2の場合は、このクラスは、こういう機能を使うんだな。
メソッドが増えるので、良し悪しはありますけど、
ということが、Class宣言のところで、一目でわかるようになります。

ことが可能になります。

参照


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA