DIとは
オブジェクトの注入
⇒オブジェクトの注入ってなんやねん
オブジェクト生成時に他のオブジェクトを注入すること
具体的には???
class Clazz
{
private $sample;
public function __construct()
{
$this->sample = new Sample();
}
}
上のような例だとclazzをインスタンス化する際にSampleクラスができていないといけない。
class Clazz
{
private $sample;
public function __construct(SampleInterface $sample)
{
$this->sample = $sample;
}
}
しかし、上のようにすればSampleクラスが完成していなくてもスタブを注入することでテストができるようになる
DIコンテナ
class Clazz
{
private $sample1;
private $sample2;
private $sample3;
public function __construct(Sample1Interface $sample1, Sample2Interface $sample2, Sample3Interface $sample3)
{
$this->sample1 = $sample1;
$this->sample2 = $sample2;
$this->sample3 = $sample3;
}
}
上記のようなDIをする実装にすると、以下のようにクラスをインスタンス化する際に大量のオブジェクトを注入しないといけなくなる。
$sample1 = new Sample1();
$sample2 = new Sample2();
$sample3 = new Sample3("test");
$clazz = new Clazz($sample1, $sample2, $sample3);
DIコンテナは、上記の処理を変数にまとめ、以下のようにシンプルに書くことができるようにする。
require_once __DIR__ . '/container.php';
$container['sample3'] = "test";
$clazz = $container['clazz'];
Inversifyとは?
JavaScriptでInversion of Control(制御の反転、IoC)もしくはDependency Injection(オブジェクトの注入、DI)を行うためのライブラリ
使い方
宣言
- inversifyで管理したいクラスは
@injectable
デコレータを付ける必要がある- 下記例はIRepositoryインターフェイスを実装したRepositoryImplクラス
@injectable()
class RepositoryImpl implements IRepository {
public func(): void {
console.log("RepositoryImpl.func()");
}
}
コンストラクタの各引数に@inject()デコレータを追加し、引数として識別子を渡します。
これでinversifyに「ServiceImplクラスのコンストラクタを呼ぶときは、第1引数に識別子Repositoryのオブジェクトを、第2引数に識別子Gatewayのオブジェクトを渡す必要がある」ということを伝えることができます。
@injectable()
class ServiceImpl implements IService {
private repository: IRepository;
private gateway: IGateway;
// @inject()デコレータは引数で指定した識別子のオブジェクトを注入する
public constructor(
@inject("Repository") repository: IRepository,
@inject("Gateway") gateway: IGateway
) {
this.repository = repository;
this.gateway = gateway;
}
public func(): void {
console.log("ServiceImpl.func()");
this.repository.func();
this.gateway.func();
}
}
コンテナに識別子とクラスなどをバインドする
「この識別子のオブジェクトを生成するときは、このクラスのコンストラクタを呼び出すように」ということを設定していく。
// 識別子「Repository」に「RepositoryImpl」クラスをバインドする。その型は「IRepository」である
container.bind<IRepository>("Repository").to(RepositoryImpl);
// 同様に残りもバインドしていく
container.bind<IGateway>("Gateway").to(GatewayImpl);
container.bind<IService>("Service").to(ServiceImpl);
container.bind<IClient>("Client").to(ClientImpl);
コンテナからオブジェクトを取得する
コンテナからオブジェクトを取得するには、get()メソッドにサービス識別子を渡します。TypeScriptが型を認識できるようにインターフェイス型を伝える必要があります。Client識別子のオブジェクトの取得は以下のようになります。
const client = container.get<IClient>("Client");
この時、inversifyは各クラスの @injectable()
, @inject()
デコレータと、コンテナのバインディング情報を元に以下を特定していきます。
- オブジェクトの生成方法。コンストラクタなのか、すでに生成済みのオブジェクトを使うのか、ファクトリー関数を使うのか
- (必要な場合のみ)オブジェクトを生成するクラス
- (必要な場合のみ)クラスのコンストラクタに渡すべきオブジェクトの識別子
参考資料
Dependency injection
DIとDIコンテナを3分で理解する - Qiita
DIとは?DIコンテナとは?試してみた(後編)[PHP][Pimple][DI] - あざらし備忘録。
Inversifyの基本