SwiftのDelegateをPHPで使って見る

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宣言のところで、一目でわかるようになります。
ことが可能になります。