05. 依存関係逆転の原則(DIP: Dependency Inversion Principle)
Clean Architecture 達人に学ぶソフトウェアの構造と設計の第 11 章を読もう。
概要
依存関係逆転の原則(DIP: Dependency Inversion Principle)とはモジュールを疎結合にするための原則である。
実際に依存する向きとクラス図上の依存の向きが逆になっているとき、依存関係逆転の原則に従っているという。
具象に依存するのではなく、抽象に依存するように設計したとき、依存関係逆転の原則に従っているというという方がわかりやすいかもしれない。
もう少し詳しい概要
以下のようにアプリケーションAppがユーザーのデータベースUserRepositoryに依存することはよくある。
依存関係逆転の原則に従うと、インターフェースを用意することになる。
このときAppとIUserRepositoryをアプリケーション層というくくりで見る。
最初のクラス図と見比べると、層間の矢印の向きが逆転していることがわかる。これが依存関係逆転である。
具体例
だから何?という感じなので、依存関係逆転の原則が活きる具体例を紹介する。
ユーザーのデータベースにアクセスしてユーザーの一覧を画面に出力するアプリケーションを考えよう。
とりあえず作る
まず単一責任の原則を踏まえると、データベースアクセスの担当と、結果の画面表示の担当は分けるべきだ。
今回はそれぞれUserRepositoryとAppとしてみた。
| src/user-repository.ts | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 | |
| src/app.ts | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
| src/main.ts | |
|---|---|
1 2 3 4 | |
このコードの依存関係をクラス図で表現すると以下になる。
| src/main.tsの実行結果 | |
|---|---|
1 2 3 4 | |
完成した。
何がまずいのか
Appの単体テストが難しい。
あるクラスが実クラス(抽象クラスでない普通のクラス)に依存することを密結合といい、一般的に良くないことであるとされる。
今回の例でいうとAppというクラスがUserDatabaseという実クラスに依存しており、密結合になっている。
密結合になっているために、Appの単体テストが難しい。
Appを単体テストするときはリポジトリ層をモックに差し替えるべきだ。しかし差し替えるすべがない。
どうしたら良いのか
密結合を改善するために、依存関係逆転の原則を適用する。
依存関係逆転の原則によると、Appは抽象的なインターフェースに依存すべきなのだ。
依存関係逆転の原則を適用する
AppをUserRepositoryという実クラスに依存させるのでなく、IUserRepositoryというインターフェースに依存させるようにする。
| src/i-user-repository.ts | |
|---|---|
1 2 3 | |
| src/user-repository.ts | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
| app.ts | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
| src/main.ts | |
|---|---|
1 2 3 4 | |
依存関係を表すクラス図はこうなる。
| src/main.tsの実行結果 | |
|---|---|
1 2 3 4 | |
依存関係は逆転したのだが、結局AppはUserRepositoryに紐づくように固定されているため、まだ単体テストを行うことができない。
Appの単体テストを可能にするにはAppに依存性注入(Dependency Injection)の仕組みを取り入れる必要がある。
依存性注入(Dependency Injection)というのはクラスが依存しているクラスを外部から設定することだ。
今回でいうとApp.userRepositoryの実体を外から設定できるようにする。
依存性注入を実現する
依存性注入の実現方法として Constructor Injectionを採用する。
なんのことはなく、Appのインスタンスを生成するときにUserRepositoryのインスタンスを生成してやって、メンバーに設定するだけだ。
| src/app.ts | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
| src/main.ts | |
|---|---|
1 2 3 4 5 | |
| src/main.tsの実行結果 | |
|---|---|
1 2 3 4 | |
Appの依存する実体をAppを new する側で自由に設定できるようになった。
Appの単体テストコードを書く
これでようやくAppの単体テストのコードを書くことができる。
| src/test-code.ts | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
| src/test-code.tsの実行結果 | |
|---|---|
1 2 3 4 | |
本番のコードではAppはUserRepositoryを使うし、テストコードではAppはMockUserRepositoryを使うようになった。
おわりに
というわけでリポジトリ層をモックに置き換えてAppの単体テストができるようになった。
依存関係逆転の原則のメリットを享受するには、依存性注入も併せて取り入れる必要があると思う。
依存性注入の方法として例ではConstructor Injectionを使ったが、他にも DI コンテナのライブラリを利用する方法がある。
依存関係を本格的に整理したい場合は DI コンテナのライブラリを導入するのが良いだろう。