DI(依存性注入)を小学生にもわかるように説明する試みです。(タイトルそのまま)
ケーススタディを通じてなぜDIという考え方が便利かを紹介します。
#ケーススタディ①
ログ出力を目的として、ILoggerインターフェースを実装したLoggerAクラスがあるとする。
public interface ILogger {
/** ログ出力 */
public void WriteLog();
}
public class LoggerA implements ILogger {
@Override
public void WriteLog() {
// ファイルにログを出力する処理
}
}
あなたはLoggerAクラスを使ってSampleクラスに以下のようなDoSomethingメソッドを作成しました。
public class Sample {
/** あなたが考えたすごい処理 */
public void DoSomething() {
LoggerA loggerA = new LoggerA();
loggerA.WriteLog();
}
}
DoSomethingメソッドを作成してしばらく経った頃、あなたの同僚があなたにこう言います。
「君の作ったDoSomethingメソッド、僕も使いたいんだが、一つだけ問題があるんだ。LoggerAクラスをnewしているが、僕の目的を達成するためにはLoggerBクラスをnewしておいてほしかった。」
こんなことを言われたあなたは、怒髪天を衝くぐらい怒り狂って笑顔でDoSomething2メソッドを彼のために作るのでした。
#ケーススタディ②
DB(データベース)にアクセスして、SELECTした結果から計算した結果を返すためにICalculatorインターフェースを実装したCalculatorAが、今回はすでに、用意されているとします。
public interface ICalculator {
/** 計算する処理 */
public int Calculate();
}
public class CalculatorA implements ICalculator {
@Override
public int Calculate() {
int result = 0;
// ①DBにアクセスする
// ②SELECTした結果を計算する
return result;
}
}
あなたはCalculatorAクラスを使ったCalculateSomethingメソッドをSampleクラスに作成しました。
public class Sample {
/** あなたが考えたすごい処理その2 */
public void CalculateSomething(){
CalculatorA calculatorA = new CalculatorA();
int calculatorResult = calculatorA.Calculate();
// calculatorResultを使ったすごい処理
}
}
しかし、CalculateSomethingメソッドをテストしようとした時問題が発生しました。
- CalculatorAメソッドが参照しているDBの内容は日々更新されるので、安定したテストコードが書きにくい
- CalculatorAメソッドが参照しているDBの内容をテストのために書き換えてはいけない
あなたが書いた素晴らしいロジックには万が一にもバグはないことは自明の理ですが、それでもテストしないわけにはいかないです。でもこのままでは気持ちの良い単体テストが実施できません。
#DI(依存性注入)という考え方
これらのケースで役に立つのがDI(依存性注入)という考え方です。
(あんまり好きではないですが、今回はいい文章が書いてあったので参考にします。)
Wikipediaによると、
dependency injection is a technique in which an object receives other objects that it depends on, called dependencies.
だったり、
The 'injection' refers to the passing of a dependency (a service) into the client that uses it.
であるとのこと。
###つまりDIとは「オブジェクトが依存するオブジェクトを受け取るテクニック」である。「オブジェクトを中でnewするんじゃなくて、newされたオブジェクトを渡してもらいましょう。」っていうこと。
#ケーススタディ①をDIに基づいて書くと
public class Sample {
private ILogger Logger;
Sample(ILogger iLogger){
this.Logger = iLogger;
}
/** あなたが考えたすごい処理 */
public void DoSomething() {
this.Logger.WriteLog();
}
}
こう書けば、あなたの素敵な同僚はSampleクラスをnewする際に、コンストラクタの引数にLoggerBクラスを渡せば目的を達成できます(LoggerBのWriteLogメソッドが実行されます)。仮に素敵な同僚がもっと増えてLoggerCやLoggerDを使いたいと言い出しても大丈夫そうですね。
#ケーススタディ②をDIに基づいて書くと
public class Sample {
private ICalculator Calculator;
Sample(ICalculator calculator) {
this.Calculator = calculator;
}
public void CalculateSomething() {
int calculatorResult = this.Calculator.Calculate();
// calculatorResultを使ったすごい処理
}
}
になります。
テストコードを以下のように書きさえすれば、CalculateSomethingメソッドのテストは大丈夫でしょう。(DBの内容なんて関係なくなるため。)
public class App {
public static void main(String[] args) throws Exception {
Sample sample = new Sample(new TestCalculator());
sample.CalculateSomething();
// このあと結果の確認
}
}
class TestCalculator implements ICalculator {
@Override
public int Calculate() {
// 固定値を返すだけ
return 57;
}
}
#まとめ
DIとは「オブジェクトが依存するオブジェクトを受け取るテクニック」である。「オブジェクトを中でnewするんじゃなくて、newされたオブジェクトを渡してもらいましょう。」っていうこと。
#参考文献