この記事は何
DI(依存性の注入)を実施する際に、コードとしてはDIになっているけど上手く活用できていないパターンを見かけることがあります。
この記事では、DIをする際にこういう運用はしない方が良いよ、というところをまとめていきます。
DI(依存性の注入)とは
依存性の注入とは、上位レイヤーのオブジェクトを下位のレイヤーのオブジェクトから利用をする必要がある場合でも、
上位のレイヤーから下位のレイヤーを参照するような依存関係を作り上げるためのテクニックです。
依存関係は上位レイヤーから下位レイヤーへの一方通行にする、というのは保守性を高めるためにも非常に重要な考え方です。
一番わかりやすい例だと、データの永続化をするようなユースケースを実現する際に、ユースケース層より上位レイヤーであるインフラ層に属するDBクライアントなどを利用する必要がある、等の場合に活用できます。
DBクライアントに依存させないようにすることで、実行環境ごとの差分などを下位のレイヤーに意識させないで実装を行なっていくことができます。
DIとは、具体的に以下のようなことを実施します。
- 下位のレイヤー(参照したい側)で、上位のレイヤーのオブジェクトのインターフェースを定義する
- 上位のレイヤーのオブジェクトを呼び出したいクラスのコンストラクタで、引数として定義したインターフェースの値を受け取る
- コンストラクタで受け取ったオブジェクトを利用して処理を実装する
こちらの記事には具体的なコードも記載されているので、ぜひ参考にしてください。
DIについての概念は以下の記事にも記載していますので、一読いただければと思います。
DIをするときによくある間違った運用方法
DI自体は今日からでも使えるシンプルなテクニックなのですが、目的を忘れてしまうと本来の役割全うしていないようなDIになっていることがあります。その例を紹介しながら、それぞれどのような考え方をしていくと良いかを紹介します。
1. 上位レイヤーのオブジェクトの事情で下位レイヤーで実装したインターフェースを変更する
上位レイヤーのオブジェクトの更新をするために、下位レイヤーで定義したインターフェースを変更する対応ををしている方をたまに見かけます。
前項でも説明した通り、DIは依存の流れを上位レイヤーから下位レイヤーへとするためのテクニックです。
基本的に依存の関係は、依存されている側はしている側の事情によって実装の内容を変えるなどの対応は行いません。
何か上位レイヤーのオブジェクトに変更が加わった場合は、下位レイヤーのオブジェクトのコンストラクタに値を渡すタイミングで、定義されているインターフェースに合うような形に修正する対応を行うか、そもそもインターフェースで定義されている範疇を超えない範囲でできるだけ上位レイヤーのオブジェクトの実装を行うようにしましょう。
ちなみに下位のレイヤーのオブジェクトで必要なインターフェースが変わった際は、インターフェースを変え、上レイヤーのオブジェクトの変更を行うのは全く問題ありません。
2. 下位レイヤーに定義したいインターフェースの事情で、上位レイヤーのオブジェクトのインターフェースを変える
逆に定義したインターフェースの内容によって、上位レイヤーのオブジェクトにメソッドを追加する方もいます。
これもあまり良くないアプローチである場合があります。
このパターンは依存性の流れに対して何かを違反しているわけではないですが、このような対応をするときは、「インターフェースに依存している別のオブジェクトと処理を揃えるために変更を加える」パターンが多いと感じています。
この場合は、そもそも同じインターフェースを参照するべきなのかが怪しい場合があるので、その場合は、コンストラクタで渡す値を分割し、インターフェースは最小の構成にできないかを検討した方が良いと考えています。
public interface IUserRepository {
int getDBId();
void save();
...
}
public class UserRepository: IUserRepository {
public int getDBId() {
...
}
public void save() {
...
}
}
public class InMemoryUserRepository: IUserRepository {
public int getDBId() {
0 // 本来は必要のないメソッドをインターフェースに合わせるために追加している
}
public void save() {
...
}
}
public class UserRegistrationService {
public void register() {
IUserRepository repository = new UserRepository()
RegisterUser registerUser = new RegisterUser(repository)
registerUser.call()
}
}
public interface IUserRepository {
void save();
...
}
public class UserRepository: IUserRepository {
public int getDBId() {
... // 何かしらの処理
}
public void save() {
...
}
}
public class InMemoryUserRepository: IUserRepository {
public void save() {
...
}
}
public class UserRegistrationService {
public void register() {
IUserRepository repository = new UserRepository()
RegisterUser registerUser = new RegisterUser(repository, repository.getDBId())
registerUser.call()
}
}
最後に
最後までお読みいただきありがとうございました。
Devトークも公開しているので、もし直接話してみたい、と感じていただけた方はぜひDevトークの方も「話したい」を押していただけると嬉しいです!