PHP Dependency Injection依存性注入は難しくない
プログラミングの世界ではプログラミング用語が英語であることが多いことが原因なのか用語を読んでも全くといっていいほどイメージの湧かないものが少なくありません。そんな時もしアメリカ人ならどのようなイメージを持ってそれらの用語を理解しているのか気になる時があります。そのような用語の一つにDependency Injectというものがあります。日本語訳を見ると”依存性の注入”。意味不明。。。
用語の意味を含め、実はわかっていない人が多いのではということで今回はDependency Inject(=DI)について説明を行っていきます。DIをわかっていなくてもアプリケーションを構築することは可能です。しかし、わかっていないと他の人が記述したコードを理解することができません。特にフレームワークやだれかが作成したパッケージを利用する際にはこの知識は必須です。他の人のコードはコピー&ペーストで十分、動けばいいという人なら必要はないでしょう。
Dependencyは”依存”という意味を持ちInjectは”何かを入れる”という意味があります。一般的には日本語訳では”依存性の注入”と呼ばれているようですが個人的には”依存関係のあるものを外側から入れる”という理解をしています。
言葉の説明では大半の人がわからないと思うので、コードを確認して”依存関係のあるものを外側から入れる”を見てみましょう。
Dependencyとは
まずDependency(依存関係)を2つのクラスを使って説明します。
以下のコードのようにSlackクラスとMyClassクラスを作成します。Slackクラスはsendメソッドを持つだけのシンプルなクラスです。MyClassクラスはSlackクラスのsendメソッドをrunメソッドの中で使用するためSlackクラスが必須となります。このことからMyClassクラスはSlackクラスが必須なのでSlackクラスに依存していることになります。よってこの2つのクラスが依存関係を持つことになります。
class Slack{
public function send(){
var_dump('something happens');
}
}
class MyClass{
public $slack;
public function __construct(){
$this->slack = new Slack();
}
public function run(){
$this->slack->send();
}
}
$myclass = new MyClass();
$myclass->run();
Dependency Injectionとは
クラス間の依存関係がどういうものかぼんやりと理解できた??と思うので、Dependency Injectionの話に進みます。上記のコードを見るとSlackクラスはMyClassクラスの中のコンストラクターでインスタンス化(作成)されておりMyClassクラスの内側ですべての処理が行われていることがわかります。
ではクラスを外側から入れてみましょう。違いはMyClassクラスをnewでインスタンス化する際に引数でSlackクラスをインスタンス化しています。
class Slack{
public function send(){
var_dump('something happens');
}
}
class MyClass{
public $slack;
public function __construct(Slack $slack){
$this->slack = $slack;
}
public function run(){
$this->slack->send();
}
}
$myclass = new MyClass(new Slack());
$myclass->run();
これがDependency Injectionです。これで”依存関係のあるものを外側から入れる”という意味が理解できたのではないでしょうか。最初の例ではSlackクラスはMyClassクラスの中でインスタンス化されていたので外側からSlackクラスが入ったわけではありません。でも後半のコードではMyClassクラスの中ではなく外側からSlackクラスが入っていることがわかります。
用語を覚えるよりもDependency Injectionがどういうものなのか理解することの方が大事ですが、これはコンストラクター部分でDependency Injectionが行われているので、Constructor Injection(コンストラクターインジェクション)と呼ばれます。
Setter Injectionとは
先程は、Constructor Injectionで依存関係のあるクラスを外側から入れましたが、Setter Injectionというものもあります。コードを見ればわかりますが、Constructorではなくメソッドの部分でDependency Injectionを行います。
class Slack{
public function send(){
var_dump('something happens');
}
}
class MyClass{
public $slack;
public function set(Slack $slack){
$this->slack = $slack;
}
public function run(){
$this->slack->send();
}
}
$myclass = new MyClass();
$myclass->set(new Slack());
$myclass->run();
これも同様に依存関係のあるものを外側から入れていることがわかります。
Interfaceを利用したDI
上記までの例ではSlackクラスを使っていましたが、SlackはやめてMailクラスを使うことになった場合は、Interfaceを利用していれば簡単にSlackクラスとMailクラスを取り替えることができます。
Messageインターフェイスを作成して、sendメソッドをもたせます。
interface Message{
public function send();
}
Slack、MailクラスをMessageを継承して作成します。そのため、それぞれsendメソッドを持たせる必要があります。
class Slack implements Message{
public function send(){
var_dump('something happens by slack');
}
}
class Mail implements Message{
public function send(){
var_dump('something happens by mail');
}
}
これまでDependency Injectionではクラスを使っていましたが、Messageインターフェイスに変更します。
class MyClass{
public $message;
public function __construct(Message $message){
$this->message = $message;
}
public function run(){
$this->message->send();
}
}
この結果、外側から入れるクラスを変更することでSlackクラスとMailクラスを簡単に取替えることができます。
$myclass = new MyClass(new Mail());
$myclass->run();
$myclass = new MyClass(new Slack());
$myclass->run();
結果は下記のように表示されます。
string(25) "something happens by mail"
string(26) "something happens by slack"
本文書を読むことによってだれか一人でもDependency Injectionが難しいものではなかったんだと思ってもらえればうれしいのですが。