はじめに
業務でDIを利用する機会があったのですが、「DIってなに?」という状況だったので、調査しました。
DIとは
DIとはDependency(依存性) injection(注入)の略称で、その名前のとおり「依存性」を「注入」するという意味です。
ではまず「依存性」とはなんでしょうか。
依存性とは
次のコードはObjectAがObjectBに依存している(依存性を持っている)例です。
class ObjectA {
ObjectB objectB
}
ObjectAはObjectBを参照しています。この状況を、ObjectAはObjectBに依存している(ObjectAがObjectBへの依存性を持っている)と言います。
もう少し具体的な例で説明します。
次のコードはCar(車)がEngine(エンジン)への依存性を持っている例です。Carの内部でEngineを生成し持っています。先ほどのObjectAをCarに、ObjectBをEngineに置き換えて見ると分かりやすいです。
// 車
class Car {
private Engine engine = new Engine();
// 車を発進するメソッド
public void start() {
engine.start();
}
}
// エンジン
class Engine {
// エンジンを起動するメソッド
public void start() {
// エンジンを起動する処理
}
}
class Main {
public static void main(String[] args) {
Car car = new Car();
// 車を発進
car.start();
}
}
Carのstartメソッドで車を発進させますが、そのためにはEngineのstartメソッドを呼び車のエンジンを起動する必要があります。つまり、Engineが存在しなければCarは発進できず車として成り立ちません。これはCarがEngineに依存していると言えます。
では、このような依存性を「注入」するとはどのようなことなのでしょうか。
依存性を注入する(DI)とは
依存性を注入するとは、 依存性を内部で生成し持つのではなく、外部で生成し渡す(注入する) ということです。
先ほどまではCarの内部でEngineという依存性を生成し持っていましたが、これを外部から依存性を注入(DI)する形に変えてみます。
次のコードはCarの内部で行っていたEngineの生成をCarの外部(mainメソッド)で行い、Carをインスタンス化する際に生成したEngineをコンストラクタに渡すことによって、CarにEngineという依存性を「注入」しています。
class Car {
private Engine engine;
// コンストラクタでEngineを受け取る = Engineという依存性を注入される
public Car(Engine engine) {
this.engine = engine;
}
// 車を発進するメソッド
public void start() {
engine.start();
}
}
// エンジン
class Engine {
// エンジンを起動するメソッド
public void start() {
// エンジンを起動する処理
}
}
class Main {
public static void main(String[] args) {
Engine engine = new Engine();
// コンストラクタにEngineを渡す = Engineという依存性を注入する
Car car = new Car(engine);
// 車を発進
car.start();
}
}
これがDI (依存性注入)です。しかしDIが何か分かったものの、なぜDIをする必要があるのでしょうか。DIを行うことのメリットはなんでしょうか。
DIのメリット
クラスの使い回しができる
DIメリットとして、クラスを使い回せるようになることが挙げられます。
DIの利用有無でクラスがどう使い回せるようになるのかを具体的なコードで説明します。具体例として、Engineを継承したGasolineEngine(ガソリンエンジン)とElectricEngine(電気エンジン)があり、それぞれのエンジンを持つガソリン自動車と電気自動車を作ることを考えます。
次のコードはGasolineEngine(ガソリンエンジン)とElectricEngine(電気エンジン)です。
// ガソリンエンジン
class GasolineEngine extends Engine {
// startメソッドをオーバーライド
@Override
public void start() {
// ガソリンエンジンを起動する処理
}
}
// 電気エンジン
class ElectricEngine extends Engine {
// startメソッドをオーバーライド
@Override
public void start() {
// 電気エンジンを起動する処理
}
}
これらをCarで持ちガソリン自動車と電気自動車を作成するわけですが、作成にDIを利用しない場合とDIを利用する場合とでどのような違いが出るかを比べます。
DIを利用しない場合
DIを利用しない場合、外部から依存性を注入せず内部で依存性を生成し持つことになります。
DIを利用せずガソリン自動車と電気自動車を作成すると次のコードのようになります。
// ガソリン自動車
class GasolineCar {
// ガソリンエンジンを内部で生成し持つ
private Engine engine = new GasolineEngine();
// 車を発進するメソッド
public void start() {
engine.start();
}
}
// 電気自動車
class ElectricCar {
// 電気エンジンを内部で生成し持つ
private Engine engine = new ElectricEngine();
// 車を発進するメソッド
public void start() {
engine.start();
}
}
class Main {
public static void main(String[] args) {
// ガソリン自動車
GasolineCar gasolineCar = new GasolineCar();
// 電気自動車
ElectricCar electricCar = new ElectricCar();
// ガソリン自動車を発進
gasolineCar.start();
// 電気自動車を発進
electricCar.start();
}
}
コードのとおり、搭載するエンジンの種類によって2つのクラス定義(GasolineCarとElectricCar)が必要になります。しかし、GasolineCarとElectricCarではエンジンを生成する部分(new~)以外のコードはすべて同じです。また、GasolineEngineとElectricEngineはEngineを継承しているので、エンジンを保持するengineプロパティにはどちらの型のエンジンも代入することができます。つまり、ガソリン自動車と電気自動車を作るのに必要な車のクラス定義は2つもいらず、1つで十分なのです。DIをしない作りにしたせいで、エンジンの種類の分の無駄なクラス定義が必要になってしまっています。
今回は依存性(エンジン)が2種類であったためクラス定義も少なく済みましたが、もっとたくさんの依存性を持つ場合には膨大な量のクラス定義が必要になってきます。
例えば、車が持つ依存性にエンジンに加えて車体の色とタイヤが加わったとしましょう。
class Car {
// エンジン
private Engine engine;
// 車体の色
private Color color;
// タイヤ
private Tires tires;
(・・・略・・・)
}
その場合に、DIをせず内部で依存性を生成すると下記のように大量のクラス定義が必要となります。
// 赤いスポーツタイヤのガソリン自動車
class RedSportsTiresGasolineCar {
// エンジン = ガソリンエンジン
private Engine engine = new GasolineEngine();
// 車体の色 = 赤
private Color color = new RedColor();
// タイヤ = スポーツタイヤ
private Tires tires = new SportTires();
(・・・略・・・)
}
// 青いレーシングタイヤの電気自動車
class BlueRacingTiresElectricCar {
// エンジン = ガソリンエンジン
private Engine engine = new ElectricEngine();
// 車体の色 = 青
private Color color = new BlueColor();
// タイヤ = レーシングタイヤ
private Tires tires = new RacingTires();
(・・・略・・・)
}
// 赤いレーシングタイヤのガソリン自動車
class RedRacingTiresGasolineCar{(・・・略・・・)}
// 青いスポーツタイヤの電気自動車
class BlueSportsTiresElectricCar{(・・・略・・・)}
// 赤いレーシングタイヤの電気自動車
class RedRacingTiresElectricCar{(・・・略・・・)}
...etc
このように、必要なクラス定義は1つであるにもかかわらず、依存性を内部で生成しているばっかりに大量のクラス定義をせざる終えなくなっています。とてもめんどくさいですね。
DIを利用する場合
DIを利用する場合、内部で依存性を生成し持つのではなく、外部で依存性を生成し注入します。
DIを利用してガソリン自動車と電気自動車を作成すると次のコードのようになります。
// 依存性が外部から注入されるため、クラス定義が1つのみでよくなる
class Car {
private Engine engine;
// コンストラクタでEngineを受け取る = Engineという依存性を注入される
public Car(Engine engine) {
this.engine = engine;
}
// 車を発進するメソッド
public void start() {
engine.start();
}
}
class Main {
public static void main(String[] args) {
Engine gasolineeEngine = new GasolineEngine();
Engine electricEngine = new ElectricEngine();
// ガソリン自動車
// gasolineeEngineという依存性を注入する
Car gasolineCar = new Car(gasolineEngine);
// 電気自動車
// electricEngineという依存性を注入する
Car electricCar = new Car(electricEngine);
// ガソリン自動車を発進
gasolineCar.start();
// 電気自動車を発進
electricCar.start();
}
}
DIを利用しない場合と比べ、車のクラス定義は1つでよくなったことが分かります。ガソリン自動車と電気自動車のどちらを作成するにしてもCarを使い回せば作成できるため、無駄なクラス定義をしなくてよくなりました。これは先ほど例に出した、車体の色やタイヤのような複数の依存性を持つ場合でも変わりません。先ほどのような大量のクラス定義は同様に必要なく、1つのクラス定義で済んでしまいます。
参考文献
ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本
Android での依存関係インジェクション
依存性注入(Dependency Injection: DI)について理解する