依存性の注入(Dependency Injection、DI)は、概念的で初心者が理解するにはハードルは高めです。
ただ、実態として意味するところはシンプルであり、「何をすることか?」「何故それをするのか?メリットは?」という点をピンポイントで押さえれば難しい概念ではありません。
今回は、そういった背景を整理するために、シンプルに解説をしてみたいと思います。
◇目次
「依存性の注入」とは、「依存性の注入」パターンを満たすようにプログラムを書くこと
「依存性の注入」と書くと注入する行為そのものを指すように感じさせますが、実態的には「依存性の注入」パターンを満たすようにプログラムを書くことを指すと捉えたほうが分かりやすいです。
より具体的には、次のようにソースコードを書くことを言います。
- とあるクラスAの中で、他のとあるクラスBのインスタンスを生成しない。
- クラスAの中でクラスBが必要であれば、クラスAの中で生成せずメソッドの引数で与える。その時は、実体クラスBではなくインターフェースの形式にする。
具体的にコードに起こすと次のようになります。
//「依存性の注入」パターンを満たすように書かれたプログラムの例
//クラスAの実装。Hogeメソッドの引数がインターフェースであることがポイント。
class ClassA {
public bool Hoge(IClassB classB) {
return classB.Fuga();
}
}
//引数とするインターフェース
interface IClassB {
bool Fuga();
}
「依存性の注入」パターンを満たすと、実体クラスに依存しないコードとなり、変更やテストが効率化する
「依存性の注入」パターンを満たすと、インターフェースを実装した別物の実体クラスに引数をすり替えてメソッドを実行できます。
これにより、引数インスタンスのプロパティ値やメソッドの戻り値を固定化したり、パターンテスト出来たりできます。
//①「依存性の注入」パターンを満たし、実体クラスBに依存しないメソッド。
//引数はインターフェースで指定。
public void Hoge(IClassB classB) {
//引数のインターフェースオブジェクトを使った処理
}
//②実体クラスBに依存するメソッド。「依存性の注入」のパターンではない。
//引数の型がインターフェースではなく実態クラスであることがポイント。
public void Hoge(ClassB classB) {
//引数の実態クラスインスタンスを使った処理
}
上記2つの実装において、実際に使うものはいずれもClassBだとしても、①の書き方=依存性注入を満たす書き方であれば、インターフェースIClassBを実装する別のクラスに置き換えて実行することが可能です。
極端な話、インターフェースIClassBに定義されたメソッドやプロパティだけ持つ空っぽなクラスのインスタンスでも引数にして実行することが出来るのです。
この工夫によって次のようなメリットが生じます。
- ClassBが削除されたり変更されたりしても、メソッドに影響を及ぼさない実装となる。
- メソッドの単体テストが容易になる(メソッドテスト時に、実体クラスBのバグの影響を排除できる。)
尚、単体テストが容易になるという点については、例えば次のコード例のようにメソッドの戻り値などを固定値に置き換えたインターフェース実装クラスに置き換えてテストすることによって発揮されます。
実体クラスClassBが自チームで開発しているクラスであればまだ制御しやすいですが、例えばコミュニティ開発のフレームワーク内のクラスなどであれば、いつ変更が入るかも分かりません。なので尚更DIに沿った実装が求められるのです。
//テスト用クラス。
//メソッドの戻り値などを固定して、クラスB側でのバグ影響を排除。
class ClassBForTest : IClassB {
public bool Hoge() {
return true;
}
}
//実装クラス。テストには使用しない。
class ClassB : IClassB {
public bool Hoge() {
//複雑な処理
}
}
「依存性の注入」は、保守性が高いソフトウェアの開発には必須知識
以上の通り、「依存性の注入」は関心の分離(Separation of Concerns、SoC)実現のため疎結合化を行うプログラミング技法です。
例えば多くのオープンソースソフトウェアなどで実際に採用されており、保守性の高いコードを作成するためにはもはや必須知識といえます。
また、DIをも含む「関心の分離」の考え方は、変更やテストに強い保守性の高いコードを作る為に重要な考え方です。関連記事に詳細を記載していますので、是非あわせてご参考いただき理解が進めば嬉しく思います。
「DIコンテナ」は依存性注入(DI)を手軽にするツール
以上のように、DIは保守性を高める一方で、主にコンストラクタ周りのコード記述量が増えてしまう点が難点と言えます。
特にDIするクラスが増えてくると、以下のように一つインスタンスを作るにしても「たくさんのインスタンスを作る→コンストラクタに全部入れる」という流れになり不効率です。
プログラマとしては当然こういったコードは効率化したいという思いが湧いてきます。
//たくさんの注入インスタンスを事前に作るのが大変
var a = new ClassA();
var b = new ClassB();
var c = new ClassC();
var d = new ClassD();
・
・
・
var main = new MainClass(a, b, c, d・・・);
そこでDIを行うクラスやインターフェースをまとめて(=コンテナ化して)楽をする目的で使用されるのがDIコンテナです。
例えばSpringやASP.NETなど、ある程度大きめのAppフレームワークでは機能として組み込まれていることが多く、手書きでDIコンテナを作成する必要がない場面も多いです。
以下に典型的な実装例を示します。コンテナのインスタンスから、コンテナに登録した様々なクラスのインスタンスを生成できることがポイントです。
//アプリケーションのスタートポイントやDIファイルなどでDIコンテナの内容を定義
void Main() {
//DIコンテナクラス
var container = new Container();
//インターフェースと実体クラスを登録
//(FWによっては、インスタンス生成パターンを、引数や登録メソッドの使い分けで指定できる場合が多い)
container.TypeRegist<IClassA, ClassA>();
container.TypeRegist<IClassB, ClassB>();
container.TypeRegist<IClassC, ClassC>();
・
・
・
}
void Run() {
//コンテナからインスタンス取得(通常のインスタンス生成の感覚で利用可能)
var classA = this.Container.Resolve<IClassA>();
var classC = this.Container.Resolve<IClassC>();
}
例えばSpring(Javaで構築されたWeb等の用途で用いられるFW)では記述をより簡潔化し、フィールドに属性(アノテーション)を付与することで自動でDIコンテナから実体インスタンスを与えられる構成としています。
個別のFWの実装については、それぞれのドキュメントをご参照ください。
Java
//SpringにおけるDIインスタンス取得の例
@Component
public class DiTest {
@Autowired //←アノテーションにより、自動でインスタンスclassAが生成される
public ClassA classA;
}
記事筆者へのお問い合わせ、仕事のご依頼
当社では、IT活用をはじめ、業務効率化やM&A、管理会計など幅広い分野でコンサルティング事業・IT開発事業を行っております。
この記事をご覧になり、もし相談してみたい点などがあれば、ぜひ問い合わせフォームまでご連絡ください。
皆様のご投稿をお待ちしております。