2-5:資源を直接結び付けるよりも依存性注入を選ぶ
要点
クラス内で new やシングルトンを直接作る(資源を直接結び付ける)と「結合度が高まり」「テストしにくく」「再利用性が下がる」ため、依存オブジェクトは外から渡す(依存性注入)べき、という話。
なぜDI が良いのか
-
テスト容易性:依存を差し替えられるのでモック/スタブで簡単に単体テストできる。
-
疎結合:実装の差し替えや拡張が容易(インタフェースを使えば実装に依存しない)。
-
構成の分離:オブジェクトの生成(構成)はアプリケーション起動時やコンテナに任せ、本体ロジックは依存を使うことだけに集中できる。
-
ライフサイクル管理:DI コンテナはスコープやライフサイクル(singleton, prototype など)を管理できる。
悪い例(資産を直接結び付ける)
public class SpellChecker {
private final Dictionary dict;
public SpellChecker() {
// 悪い:具体クラスを直接 new している(ハードワイヤリング)
dict = new EnglishDictionary("/dictionaries/english.dict");
}
public boolean isValid(String word) {
return dict.contains(word);
}
}
問題点:SpellChecker をテストするには EnglishDictionary の実際のファイル依存を解決する必要がある(重い/外部資源に依存)。実装の差し替えが困難。
良い例(コンストラクタ注入)(推奨)
public interface Dictionary {
boolean contains(String word);
}
public class EnglishDictionary implements Dictionary {
public EnglishDictionary(String path) { /* load dictionary */ }
@Override public boolean contains(String word) { /* ... */ }
}
public class SpellChecker {
private final Dictionary dict;
// 必須依存はコンストラクタで注入する(最も明示的で安全)
public SpellChecker(Dictionary dict) {
this.dict = Objects.requireNonNull(dict);
}
public boolean isValid(String word) {
return dict.contains(word);
}
}
利点: SpellChecker のテストでは Dictionary のスタブやモックを渡すだけで済む。
オプション依存はセッター注入で
public class ReportGenerator {
private Formatter formatter = new DefaultFormatter();
// オプション依存や後から差し替えたい場合は setter を使う
public void setFormatter(Formatter formatter) {
this.formatter = formatter;
}
}
方針: 必須依存はコンストラクタ注入、任意依存はセッター注入 が一般的なガイドライン。