質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.12%

DIコンテナの作り方について

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 2,241
退会済みユーザー

退会済みユーザー

単純な疑問なのですが、DIコンテナというのはどのように作るのでしょうか?

以下は普通にコンストラクタを通して依存性を注入するサンプルコードです。
※TypeScriptは長いこと書いていなかったので、間違っていたらすみません。。。

DIコンテナを作ると、コンストラクタの引数に依存するクラスのインスタンスを手動で渡してあげなくても、勝手にDIされる訳ですよね!?
それがどういう仕組みで実現されるのかがマジカルでわかりません。

このクラスにはこのクラスが依存するみたいなマッピングを、DIコンテナクラスみたいなところで行うのかなとは思ったりするのですが、どういう流れで依存先のクラスに自動で依存するクラスのインスタンスが注入されるのかがわかりません。

簡易的なものでも作れるようになれば理解が深まるとは思っているのですが、簡易的なものすら、どのように作ってよいやら。。。
ご存知の方がおりましたら、ご教授頂けるとありがたいです!

interface Sendable {

    send(): void;

}


class GmailSender implements Sendable{

    public send() {

        console.log("send a gmail");

    }

}


class HotmailSender implements Sendable {

    public send() {

        console.log("send a hotmail");

    }

}

class Subject {

    private mailer: Sendable;

    constructor(mailer: Sendable) {

        this.mailer = mailer;

    }

    public send() {
        this.mailer.send();

    }

}

let sender = new GmailSender();
let subject = new Subject(sender);
subject.send();
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

コードはC#で悪いんだけども、コンストラクターインジェクションをベースに話をしよう。

まず、コンストラクターは情報さえあれば呼び出すことができる。
そこはわかってるかな?
C#やJavaはリフレクションができるから特にある型のインスタンス化は簡単にできる。
C++とかでも関数ポインタを集積すればそこを経由してインスタンス生成を間接的に行うことができる。
とにかく…最初に大事なことは、new 型名ってしなくても、情報からインスタンス化することは可能だってことだ。

そういう情報を集積して、取得したいクラスからその必要なパラメーター(子供)にわたって連結して値を生成できるように再帰呼び出しする仕組みを作るんだ。それが大体DIコンテナのやってることだ。

下記は、今、このページで打っただけだから下記のコードはぐだぐだだけども、一通り必要そうなコードを書いてみた。

// あるクラスのインジェクションに関係する情報を集める
// これは関数で支持を出しても良いし、よりよい形ではリフレクションでも良い
BindingInfo<T> Bind<T>()
{
    // このコンストラクタにどんな情報があるかを事前にまとめておくためのクラスがあるとして
    var info = new BindingInfo(typeof(T));

    // まずは器を用意しておく
    cache[typeof(type)] = info;

    return info;
}

// で、実装クラスの関連情報を作成した収集クラスに集める
public class BindingInfo<T>
{
    // 例えばある型と紐づける
    public BindedTypeInfo To(Type type)
    {
        // 紐づける型が派生形であることを調べる
        this.Type.IsAssignableFrom(type);

        // C#だとこんな風にコンストラクタの一覧はとってこれるし
        var constructors = type.GetConstructors();
        foreach (var constructor in constructors)
        {
            // ConstructorInfoからはGetParametersとかで引数はとってこれる
            this.Add(new BindingConstructorInfo(constructor));

            // そもそも、ConstructorInfoは実行できる
            // constructor.Invoke(/*引数はオブジェクトの配列*/);
        } 
    }

    // もしくはあるインスタンスと紐づけたり
    public BindedTypeInfo ToConstant(T value)
    {
        // ここでは、この型情報のインスタンスが欲しい時に
        // ここで受け取ったvalueを返すようにする

        // で、紐づけられた関連情報を更にreturn することによって
        // 細かいオプションを足せるようにしてもいいだろうね!
    }
}

// DIコンテナで値を取得するときは、再帰的に生成を行っていく
public T Get<T>()
{
    // ある型の情報からインスタンスを取るために必要なものを考える
    var info = cache[typeof(T)];
    // 例えばinfoがコンスタントならその値を返すだろうし
    if (info.IsConstant)
    {
        return info.ConstantValue;
    }
    else
    {
        // じゃなくてインスタンスを生成しなければならないなら
        // 必要なコンストラクタの引数を生成するために再帰的にGet<T>する
        var constructor = info.GetConstructor();
        var parameters = new List<object>();
        foreach(var parameterInfo in constructor.GetParameters())
        {
             // Getメソッドを再帰的に呼び出せばいつか端にたどり着く
             parameters.Add(this.Get(parameterInfo.ParameterType));
        }

        // で、生成したパラメータを使ってコンストラクタを実行する
        return constructor.Invoke(parameters);
    }
}

情報を収集し、展開する。
収集方法は別にメソッドでもいいし、一般的なDIコンテナみたいになんらかのファイルでもいい。
基本は、あるインスタンスの生成方法を集積して、再帰的に呼ぶだけ。
インスタンス化できなければ情報不足として例外を投げればいい。

実行時の動作変更用のオプションの拡張、インスタンス化に関する情報のキャッシュで高速化だとか、まあそういう細かいのはいっぱいあるだろうと思う。

とりあえず… 既存のOSSのDIコンテナのソースを見よう!

追記

上のクラスの使い方の想定をかいてなかったね。

var container = new MyDIContainer();

// ISomeClassという型には、ImplemetedClassをインスタンス化してよこせ、と指示を出す。
container.Bind<ISomeClass>().To<ImplementedClass>();

var implemented = container.Get<ISomeClass>();

ちなみにこれはNinjectっていうC#のDIコンテナを僕が使っているのでその構造に強く影響を受けている。
NinjectはDIコンテナだけど設定ファイルを使わないので、その分プログラマーには直観的だと思う。
ymlだのXMLだので設定ファイルを書いてインジェクションするよりよっぽど使うのが簡単なので、発想はC#未経験でもページを見ればわかると思う。

設定ファイルを書くDIコンテナは、設定ファイルを読み込んで動作を変更できるようにちょっと変えただけだ。
設定ファイルを使う場合は、現在アプリケーションに読み込まれてる型情報をリフレクションで取得しておいて、その情報に設定ファイルから得たオプションを適用していく、という形になるだろうね。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/17 20:41

    お〜!!!ご丁寧にコード付きの解説ありがとうございます!!!!

    > まず、コンストラクターは情報さえあれば呼び出すことができる。
    そこはわかってるかな?
    C#やJavaはリフレクションができるから特にある型のインスタンス化は簡単にできる。
    C++とかでも関数ポインタを集積すればそこを経由してインスタンス生成を間接的に行うことができる。
    とにかく…最初に大事なことは、new 型名ってしなくても、情報からインスタンス化することは可能だってことだ。引用テキスト
    C#やJavaはリフレクションができるから特にある型のインスタンス化は簡単にできる。
    C++とかでも関数ポインタを集積すればそこを経由してインスタンス生成を間接的に行うことができる。
    とにかく…最初に大事なことは、new 型名ってしなくても、情報からインスタンス化することは可能だってことだ。

    出だしから自分は理解できていないですが、一旦、リフレクションについて調べてみたりしながら、投稿頂いた回答を読み込んでみます!

    ありがとうございます!

    キャンセル

  • 2017/05/17 20:43 編集

    ういーすー。質問の言語だと、Javaなんかがかなり作りやすいでしょう!

    下記のQiitaでJavaのコンストラクタのリフレクションの話がでてます。

    http://qiita.com/manahirosan/items/32da2cc9f5f03dc454ca

    後は引数の型を生成するのを繰り返していけば、単純なDIコンテナは完成しますよ!


    …たぶんね!(僕は既製品しか使ってないから。笑)

    キャンセル

  • 2017/05/17 21:36

    ありがとうございます。ちょっと読み込むのに時間かかりそうですが、有難く勉強させていただきます!

    キャンセル

+1

DIコンテナのそもそもの目的は、コンストラクタインジェクションやセッターインジェクションにて、引数が多くなりすぎて、可読性やメンテナンスの部分で困ることを防ぐために、DI用のファイルで管理しませんかってことだと思います。

public function __construct(Event $event,SomethingInterface $si, Sample $sp,・・・[いっぱいのインジェクション]){

}

例えば、symfonyだとymlに書いて、管理します。
DIコンテナはそんなにたくさんのインスタンスを返すとかの設計じゃないなら、必要ないかもしれません。作り方については、下記サイトが参考になるかと思います。
qiita

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/17 18:59

    小さい単位でクラス化していると、どうしてもDIするインスタンスが増えてしまうのですが、DIコンテナに興味を持っているのですが、簡易的な物を作って仕組みを理解したいところなのです。

    キャンセル

  • 2017/05/17 19:02

    リンク先は見たことあるのですが、作り方というよりはDIの概要と既存のDIコンテナの使い方がちょろっとって感じの記事ですね。

    キャンセル

  • 2017/05/17 19:29

    ymlを読んで、パースしてそれを元にクラスロードするコードを書けばいいのではないでしょうか。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.12%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる