DIを調べると、、、
DI 【 Dependency Injection 】 依存性注入
DIとは、コンピュータプログラムのデザインパターンの一つで、オブジェクトなどの間に生じる依存関係をオブジェクト内のコードに直接記述せず、外部から何らかの形で与えるようにする手法。
[参考]http://e-words.jp/w/DI.html
依存性注入(Dependency Injection)とは?
-
Dependencyとは
- サービスとして使用できるオブジェクトのこと。
-
Injectionとは
- オブジェクト(サービス)を、それを使うオブジェクト(クライアント)に渡すこと。
ということで上記をまとめると
あるオブジェクト(サービス)を別のオブジェクト(クライアント)に渡すことを依存性注入という
DIのメリット・デメリット
-
メリット
- クラス同士を疎結合にできる、独立性を高められる
- クラスの単体テストがやりやすくなる
- クラスの再利用性が向上する
-
デメリット(DIのデメリットというかDIフレームワークのデメリット。。。)
- はじめにクラスを沢山作るので工数がかかる場合が多い
- プログラムの実行スピードが遅くなる可能性が高い
- 学習コストがかかる
[参考]https://blog.shin1x1.com/entry/di-memo
DIのパターン
非DI:コンストラクタを直接呼び出す
class Car {
Engine engine;
Wheels wheels;
Car() {
this.engine = new Engine();
this.wheels = new Wheels();
}
}
CarクラスにEngineクラス、Wheelsクラスが必要な場合に、コンストラクタ内でインスタンス化すると密結合になってしまい、EngineクラスとWheelsクラスが完成しないとCarクラスの実装、テストが進められない。という事態になる。。。
DI①:コンストラクタの引数を利用したDI
class Car {
Engine engine;
Wheels wheels;
Car(Engine engine, Wheels wheels) {
this.engine = engine;
this.wheels = wheels;
}
}
Car car = new Car(new Engine(), new Wheels())
外からインスタンス化したEngineクラス、Wheelsクラスを渡しているため、Carクラス内でインスタンス化するよりも疎結合になってます。
クラスを変更したいときはコンストラクタに渡す引数を変更すれば良いのでテストもしやすくなってます。
→ ただ、new
地獄になってしまう可能性があります。また、クラスを変更する際に書き換える作業があリマス。。。
DI②:Factoryクラス
class Factory {
public Engine getEngine() {
return new DieselEngine();
}
public Wheels getWheels() {
return new SteelWheels();
}
}
Engine、Wheelsのインターフェースを作成
interface Engine {}
interface Wheels {}
Engine、Wheelsインターフェースを実装したDieselEngineクラスとSteelWheelsクラスを作成
class DieselEngine implements Engine {}
class SteelWheels implements Wheels {}
Car car = new Car(factory.getEngine(), factory.getWheels());
Factoryクラスを作成することで管理しやすくなり、また、Engineインターフェース、Wheelsインタフェースを挟むことでクラスの差し替えが容易になりました。
→ デメリットとしては、多くのクラスからオブジェクト生成を委譲されるためFactoryクラスが大きくなりすぎる可能性があります。また、Factory
地獄になる可能性も。。。
DI③:Dagger2
最後にAndroidのDIフレームワークの1つであるDagger2でコンストラクタインジェクション(Constructor Injection)の簡単な実装例を紹介して終わりたいと思います。
使用するアノテーションを簡単に説明
-
@Inject
- 依存性を注入したいオブジェクトへ付与する
- @Injectアノテーションが付与されたコンストラクタを使ってインスタンスを生成する
-
@Component
- 必要なインスタンスを渡してくれるインターフェース
- 注入される側はComponent経由でインスタンスをもらう
Dagger2は@Injectアノテーションが付与されたコンストラクタを使ってインスタンスを生成します。
なので、Carクラスのコンストラクタに@Injectアノテーションを付与します。
class Car {
Engine engine;
Wheels wheels;
@Inject
Car(Engine engine, Wheels wheels) {
this.engine = engine;
this.wheels = wheels;
}
void drive() { ... }
}
上記と同様の理由でEngineクラス、Wheelsクラスのコンストラクタにも@Injectアノテーションを付与します。
class Engine {
@Inject
Engine(){}
}
class Wheels {
@Inject
Wheels(){}
}
Carインスタンスを渡してくれるインターフェースを作成します。
→ 必要なインスタンスを渡してくれる@Componentアノテーションを付与します。
@Component
interface CarComponent {
Car getCar();
}
MainActivityです。
CarComponentインターフェースを実装したDaggerCarComponent
からインスタンス化します。
その後、component.getCar()
にて、Carクラスを受け取ります。
class MainActivity extends AppCompatActivity {
Car car;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CarComponent component = DaggerCarComponent.create(); ←ココ
car = component.getCar();
car.drive();
}
}
Dagger2が自動生成してくれるソースで、先ほど定義したCarComponentインターフェースを実装してます。
pubic final class DaggerCarComponent implements CarComponent {
private DaggerCarComponent(){}
public static Builder builder() {
return new Builder();
}
public static CarComponent create() {
return new Builder().build();
}
@Override
public Car getCar() {
return new Car(new Engine(), new Wheels());
}
public static final class Builder {
private Builder() {}
public CarComponent build() {
return new DaggerCarComponent();
}
}
}
以上です。
[参考]
http://blog.a-way-out.net/blog/2015/08/31/your-dependency-injection-is-wrong-as-I-expected/
http://yuki312.blogspot.com/2016/03/android-dagger2.html