iimon TECH BLOG

iimonエンジニアが得られた経験や知識を共有して世の中をイイモンにしていくためのブログです

○○に依存しつづけてはや6年〜Dependency Injection(依存性の注入)〜

はじめに


こんにちは!

株式会社iimonのエンジニアマネージャー、松田です。

突然ですが皆さん、何に”依存”してますか?

私は恥ずかしながら、とあるアイドルに依存しています(笑) その子に出会って6年、ずっと一途に推し続けていて、もはや生活の一部です! いずれその時がくるんでしょうが、もし卒業なんてされた日には・・・私はなんのために働けばいいんでしょうか!!

エンジニアリング文脈での依存とは

閑話休題、エンジニアリングの文脈で出てくる”依存”とは一体なんでしょうか。わかりやすく私を例にして確認してみましょう。

// 今推してる子の名前がFから始まるので笑
class IdolF {
    public getMessage(): string {
        return "松田さん、イケメンでかっこいい!";
    }
}

class Matsuda {
    private idolF: IdolF;

    constructor() {
        this.idolF = new IdolF();
    }

    // アイドルからもらったメッセージを表示するメソッド
    public displayMessageFromIdol(): void {
        console.log(this.idolF.getMessage());
    }
}

const matsuda = new Matsuda();
matsuda.displayMessageFromIdol();

うん、ずっぽり依存してますね。

松田の心の奥底(コンストラクタやプロパティ)までアイドルFちゃんでがっつり固まっています。 万が一、アイドルFちゃんに何かがあったときには簡単に推し変できるようにしておいたほうがメンタル的にはよいでしょう!

では、松田からアイドルFちゃんへの依存を取り除いてみましょう。

interface IdolInterface {
  getMessage(): string;
}

class IdolF implements IdolInterface {
    public getMessage(): string {
        return "松田さん、イケメンでかっこいい!";
    }
}

// 仮にAちゃんに推し変すると仮定
class IdolA implements IdolInterface {
    public getMessage(): string {
        return "松田さん、これからよろしくね!";
    }
}

class Matsuda {
    private idol: IdolInterface;

    constructor(idol: IdolInterface) {
        this.idol = idol;
    }

    // アイドルからもらったメッセージを表示するメソッド
    public displayMessageFromIdol(): void {
        console.log(this.idol.getMessage());
    }
}

const idol = new IdolA();
const matsuda = new Matsuda(idol);
matsuda.displayMessageFromIdol();

こうしておけば、アイドルAちゃんに簡単に推し変できますし、反省してFちゃんに戻ることもすぐにできそうです!! これがDependency Injection(依存性の注入)=DIとも呼ばれるもの=の最も簡単で生々しい例です!

DIのメリット

では、DIをすることによるメリットはなんでしょうか。

まずは今回の例でも出てきた”推し変”、つまり、依存先の変更が容易になるということです。 仮にここで出てきたアイドルクラスがDBにアクセスしてデータを取ってくるクラスだと想像してみてください。 これまでMySQLにデータを保存してきたけど、やっぱりPostgreSQLに変更したくなったときはどうでしょう。 DIなら、コンストラクタに渡すパラメータを1つ変更するだけで済みますね。

あとは注入されるオブジェクトをモックにできるということもあります。 テストを書くときも簡単ですし、仮にIdolFクラスの実装がまだ終わっていなくてもMatsudaクラスの実装はすすめることができます。

さらに、これも依存先の変更が容易ということと関連してくるのですが、注入するオブジェクトを変更することで簡単に動作を変更することができる、というものもあります。 ロガーのオブジェクトを外から注入するようにして、ファイルにログを書き込む、DBにログを書き込む、みたいなことも簡単に切り替えられますね。

依存性を注入する方法

さて、今回の例ではコンストラクタに依存するオブジェクトを渡す方法を見てきましたが、これを「コンストラクタインジェクション」と呼んだりします。 注入する方法は他にもいくつか種類があるので、ぜひ調べてみてください。

  • コンストラクタインジェクション
  • メソッドインジェクション
  • プロパティインジェクション

DIコンテナとは

「DIコンテナ」という言葉を聞かれたことがある方もいらっしゃると思います。かくいう私も、最初はDIとDIコンテナの違いがわからずに頭の中でごちゃごちゃになってしまっていました。ここで一度整理しておきましょう。

「DI」は”依存の注入”を行うためのデザインパターンです。コンストラクタインジェクションやメソッドインジェクションなどの方法を用いて、依存(先のオブジェクト)を依存元に注入すること自体を指します。そのためには、「DIコンテナ」は必須ではありません。

「DIコンテナ」は、いちいち

const idol = new IdolA();

のように依存先のオブジェクトのインスタンス化を行わなくてもよくするための仕組み(フレームワーク)です。

「コンテナ」という名前のとおり、依存先オブジェクトのインスタンスを格納しておくための入れ物、と考えるといいかもしれません。

例として、TypeScript製のDIコンテナ、「TSyringe」を使ってみましょう。

import 'reflect-metadata';
import { container, injectable } from 'tsyringe';

interface IdolInterface {
    getMessage(): string;
}

@injectable()
class IdolF implements IdolInterface {
    public getMessage(): string {
        return "松田さん、イケメンでかっこいい!";
    }
}

@injectable()
class IdolA implements IdolInterface {
    public getMessage(): string {
        return "松田さん、これからよろしくね!";
    }
}

@injectable()
class Matsuda {
    private idol: IdolInterface;

    constructor(idol: IdolInterface) {
        this.idol = idol;
    }

    // アイドルからもらったメッセージを表示するメソッド
    public displayMessageFromIdol(): void {
        console.log(this.idol.getMessage());
    }
}

// コンテナに登録
container.registerSingleton(IdolF);
container.registerSingleton(IdolA);

// IdolFを使う場合
const matsuda = container.resolve(Matsuda, { idol: container.resolve(IdolF) });
matsuda.displayMessageFromIdol();

// IdolAを使う場合
// const matsuda = container.resolve(Matsuda, { idol: container.resolve(IdolA) });
// matsuda.displayMessageFromIdol();

依存先のオブジェクトをnewしなくてもよくなったところに注目ください。 コンテナに登録(register)しておいて、どのクラスにはどのオブジェクトを注入するか(resolve)を書いておくだけで簡単にDIを実現することができます。

使いどころのヒント

DIがなんなのかはわかった、DIコンテナのこともなんとなくわかった、では、実際にはどんな場面で使うと嬉しいのでしょうか。 最後にそこを見ていきましょう。

「依存性逆転の原則」(DIP)という言葉を聞いたことはあるでしょうか。 そう、「SOLID原則」の「D」です。(SOLID原則を知らない人は調べてみてください)

クラスAがクラスBに依存している状況を、DIのテクニックを使って実質クラスBがクラスAに依存しているように逆転させることができます。

勉強熱心な方はピンときたかもしれません。 「クリーンアーキテクチャ」に代表される、UIがコントローラに依存して、コントローラがユースケースに依存して、ユースケースドメインに依存して・・・という「依存方向のコントロール」が自由自在になるのです。

これ以上深入りするとDIの説明からはずれてしまうので「クリーンアーキテクチャ」等の説明は省きますが、ドメインは何にも依存しない(依存される側)として実装すると、変更に強い設計にすることができるようになります。

ぜひお試しください!

最後まで読んでくださりありがとうございます! 弊社ではエンジニアを募集しております。 ご興味がありましたらカジュアル面談も可能ですので、是非ご応募ください!

Wantedly / Green