SwiftのDelegateをPHPで使って見る
.一つのクラスに対して、何かしらの機能を追加したい場合、継承する方法を単純に思いつきますが、
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宣言のところで、一目でわかるようになります。
ことが可能になります。