はじめに
BEAR.Sunday アドベントカレンダー 23日目の記事です。
長い間、DIに対する理解がふわっとしていたのですが、BEAR.Sundayのチュートリアルをやってみたところ、今まで喉に引っかかっていた小骨な取れたかのように飲み込めたような気がしたので、BEAR.SundayでDIを実践してみたことで理解できたことを記事にしてみようと思った次第です。
DI とは
DI とは、Dependency Injection の略で日本語では依存性の注入という意味です。
依存性をオブジェクトのことだと捉えると、オブジェクトを他のオブジェクトに渡してあげるということになります。
あるクラスの中で他のクラスをNewで生成したりせずに、引数として外部から渡してもらって利用することをDIになります。
DIではない例
<?php
namespace src;
class Car {
private $tire;
public function __construct()
{
$this->tire = new Tire();
}
public function viewCar()
{
settingCar($this->tire);
return $this;
}
}
Car
クラスのコンストラクタ内でTire
オブジェクトを生成しています。
DIの例
<?php
namespace src;
class Car {
private $tire;
public function __construct(Tire $tire)
{
$this->tire = $tire;
}
public function viewCar()
{
settingCar($this->tire);
return $this;
}
}
今度は、コンストラクタの引数にTire
オブジェクトを渡してあげるようにしました。
このようにクラス内で他のクラスを生成しないように、外部から注入してあげることがDIになります。
DIには、以下のようなパターンがあります。
- コンストラクターインジェクション
- コンストラクタの引数にオブジェクトを渡す
- セッターインジェクション
- セッターにオブジェクトを渡してプロパティを設定する
- メソッドインジェクション
- メソッドにオブジェクトを渡す
- セッターインジェクションと似ていますが、注入されたオブジェクトのメソッドを利用することを目的としている場合がこちらに該当するのかなと認識しています
- メソッドにオブジェクトを渡す
ちなみに、上記の例はコンストラクターインジェクション
の例になります。
DI すると何が嬉しいのか
DIのメリットとしてよく説明されるのは
- 粗結合になり柔軟性が上がる
- テストしやすくなる
という点だと思います。
例えば、クラスAの中でクラスBを生成している実装の場合、クラスBのオブジェクトを生成する際のパラメータに変更があった場合、クラスA側も修正しなければなりません。
しかも、クラスBを廃止してクラスCに置き換えたくなった場合、クラスBからオブジェクトを生成していた箇所すべてを修正しなければいけません。
現実世界を例に説明してみる
例えば、車工場でタイヤを作っていたら、製造した車に適したタイヤかどうかを組み立てる段階で確認しなければなりません。
ですが、車工場はタイヤ工場が作ったタイヤをただ組み立てるだけにしておけば、車の製造のみに集中することができます。
タイヤ工場も、車工場のことを気にせず、「自分たちの工場でつくるタイヤはこういう規格なんだ!」ということを明確にしておくだけで良くなります。
このように、オブジェクト同士を小さい単位で切り分けられると、他の部品に取り替えることも容易になり、部品交換による影響も小さくすることができます。
BEAR.Sunday(Ray.DI)でDIを試してみる
それでは、具体的な実装の場面でDIを活用するとどうなるかをBEAR.Sundayを利用しながら確認していきたいと思います。
まずは、与えられた正の整数までの正の整数をランダムに生成してレスポンスする処理を作成してみます。
<?php
namespace MasaKuVendor\DiSampleApp\Resource\App;
use BEAR\Resource\ResourceObject;
class Number extends ResourceObject
{
public function onGet(int $maxNum) : ResourceObject
{
$this->body = ['rando' => rand(1, $maxNum)];
return $this;
}
}
このコードに指定された値が正の整数かを判定するバリデーションクラスをDIしてみます。
まずはバリデーションクラスのインターフェースを作成します。
<?php
namespace MasaKuVendor\DiSampleApp;
interface CheckNumberInterface
{
public function checkPositiveInteger(int $num) : bool;
}
次は先程の整数を生成するメソッドに上記のバリデーションクラスをDIする処理を追加します。
<?php
namespace MasaKuVendor\DiSampleApp\Resource\App;
use BEAR\Resource\ResourceObject;
use MasaKuVendor\DiSampleApp\CheckNumberInterface;
class Number extends ResourceObject
{
/**
* @var CheckNumberInterface
*/
private $checkNum;
public function __construct(CheckNumberInterface $checkNum)
{
$this->checkNum = $checkNum;
}
public function onGet(int $maxNum) : ResourceObject
{
if($this->checkNum->checkPositiveInteger($maxNum)){
$this->body = ['rando' => rand(1, $maxNum)];
}else{
$this->body = ['message' => `正の整数を入力してください`];
}
return $this;
}
}
コンストラクタでバリデーションクラスのオブジェクトを注入してクラス内でバリデーションメソッドが利用できるようにしました。
次に具体的なバリデーション処理を記載していきましょう。
<?php
namespace MasaKuVendor\DiSampleApp;
class CheckNumber implements CheckNumberInterface
{
public function checkPositiveInteger(int $num) : bool
{
if($num > 0){
return true;
}else{
return false;
}
}
}
すごく単純なチェック処理ですが、正の整数かをチェックする処理を追加しました。
最後に、アプリケーション内でDIできるようにバリデーションチェッククラスを束縛します。
<?php
namespace MasaKuVendor\DiSampleApp\Module;
use BEAR\Package\AbstractAppModule;
use BEAR\Package\PackageModule;
use MasaKuVendor\DiSampleApp\CheckNumber;
use MasaKuVendor\DiSampleApp\CheckNumberInterface;
class AppModule extends AbstractAppModule
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$appDir = $this->appMeta->appDir;
require_once $appDir . '/env.php';
$this->bind(CheckNumberInterface::class)->to(CheckNumber::class); // この行でCheckNumberInterfaceが引数の型に指定された際にCheckNumberが渡されるようになります
$this->install(new PackageModule);
}
}
これで、各アプリケーション内でインターフェースを指定してメソッドを呼び出した際に、自動的にオブジェクトを注入してもらえるようになりました。
おわりに
こうしてBEAR.Sundayのチュートリアルを進めながら実際に動かしてみることで、自分として引っかかっていたポイントがするっと理解することができました。
というのも、もともとLaravelアプリケーションで「他の処理でも利用したい共通の処理をサービスクラスとして切り出して、必要な箇所で呼び出しながら利用できるようにしたい
」と思っていました。
そこで、いろいろと調べているうちに、DIやファサードという手段があることを知ったのですが、ある処理にクラスを外部から注入する、ということがどうしても理解できず、何となく見様見真似で利用していた状態でした。
BEAR.Sundayでは、あるリクエストを処理する上で必要となるオブジェクト(依存性)だけを注入して処理するということが一連の処理の中で明確になっていたのでチュートリアルを進めるだけでも「なるほど、こういうことか!」と理解することができました。
ツールや手法を実践の中で覚えていくことも大事ですが、意味を理解しながら覚えていくことで、自分が実装しているコードにも自信がついてくると思うので、自分が書いているコードでは何を実現しようとしているのかを意識してコードを書いていきたいですね。
参考サイト
チュートリアル | BEAR.Sunday
[https://bearsunday.github.io/manuals/1.0/ja/tutorial.html]
DI | BEAR.Sunday
https://bearsunday.github.io/manuals/1.0/ja/di.html
PHPでDIする
https://www.slideshare.net/OhasiYuki/phpdi-53487642
「なぜDI(依存性注入)が必要なのか?」についてGoogleが解説しているページを翻訳した
https://qiita.com/mizunowanko/items/53eed059fc044c5aa5dc