八葉の日記

日々、感じたことをまとめる場として利用する

DI依存性注入

依存性の注入(Wikipediaから抜粋すると)
依存性の注入(いそんせいのちゅうにゅう、英: Dependency injection)とは、コンポーネント間の依存関係をプログラムのソースコードから排除し、
外部の設定ファイルなどで注入できるようにするソフトウェアパターンである。英語の頭文字からDIと略される。

コード中に依存関係を定義するコードがあると、ユニットテストがやりにくいという問題がある。
例えば、スクリーンに何かを描画するMainScreenが実際にディスプレイドライバーを触っているとする。

class MainScreen
{
        void Disp()
        {
            DisplayDriver* drv = new DisplayDriver980() ;
            bool ret = drv.Setup();
            if (FALSE = ret)
            {
                  drv.Teardown();
                  return FALSE;
            }

            return TRUE;
        }
};

上のコードを単体試験するときは、本物ドライバに依存しているため、ドライバーがインストールしている環境でないと動かないし、
ドライバーは大抵外部から提供されものであるため、自由に中を見れずエラーケースの試験を行うことが難しくなる。

こういうときは、MainScreenに対して依存するオブジェクトをMainScreenに依存する人が渡す方法がある。
これが依存性の注入(Dependency injection)。

class MainScreen
{
        Driver& m_drv;
  MainScreen(Driver& drv) : m_drv(drv){}
        void Disp()
        {
            bool ret = m_drv.Setup();
            if (FALSE = ret)
            {
                  m_drv.Teardown();
                  return FALSE;
            }

            return TRUE;
        }
};

利用者側が依存性を解決してあげる必要があるが(*)、これで特定のオブジェクトに対する依存性はなくなり、
依存するオブジェクトが特定のシグネチャに沿っていればよくなりました。
テンプレートを使うとよくわかるかと思います。下記コードでは渡すオブジェクトには継承関係がなくても
API仕様が同じであれば一切問題がないです。

template< class T >
class MainScreen
{
        T& m_depend;
  MainScreen(class T& depend) : m_depend(depend){}
        void Disp()
        {
            bool ret = m_depend.Setup();
            if (FALSE = ret)
            {
                  m_depend.Teardown();
                  return FALSE;
            }

            return TRUE;
        }
};

(*)
依存関係を解決するのを利用者に渡しているため、利用者が必要な依存を判断する必要があります。
なので、、、
利用者に対して依存関係を知られたくない場合はこの方法は使えません。
例えば、外部の顧客に対して提供するAPIに対して、DIを利用するのは難しいかもしれません。
これは、依存するクラスをセットしてというより、モード切替といったAPIを用意する。もしくは
自分で動作環境を判断して内部で切り替えるといった仕組みを用意することが多いからかなっと思います。