はじめに
DIについて自分なりに勉強したとを、当時何も理解できなかった自分に向けて説明するつもりで書いていこうと思います。
「僕はこう解釈しています」というところがたくさん出てくるので、「それは解釈としておかしくね?」というところがあったら教えてください🙇
それにしても、Dependency Injection
っていう響きがかっこいいですね。
邪王炎殺黒龍波並みに厨二心をくすぐられます。必殺技感がたまんないですね。はい。
この辺がわかっていると理解しやすいかも!
- protocolの使い方がある程度理解してますという人
こんな人に読んでほしい
- DIってなんか聞いたことあるけど、よくわからんねんって人
結論から話しましょう
雑に言うとDIってこんな感じだと認識しています。
クラスA内でクラスBを使う。このときに、クラスA内でクラスBのインスタンスを生成するのではなく、クラスAの外で生成されたクラスBのインスタンスをクラスAが受け取るようにすること。
と。言われましても...ってなると思うので実際にコードを使ってみましょう。
DIを使わない場合
- ポイントはクラスAの中でクラスBを使うとき、インスタンスの生成はクラスAが行っている
final class A {
private let b = B()
func greeting() {
b.sayHello()
}
}
final class B {
func sayHello() {
print("hello")
}
}
let a = A()
a.greeting() // hello
DIを使った場合
- ポイントはクラスAの中でクラスBを使うときにクラスA内でBのインスタンス生成を行っていないということ
class A {
private let b: B
init(b: B) {
self.b = b
}
func greeting() {
b.sayHello()
}
}
class B {
func sayHello() {
print("hello")
}
}
let a = A(b: B())
a.greeting()
つまりどういうこと?
つまり、DIというのは使用するクラスのインスタンス生成を自らは行わない。
誰かにインスタンスを生成してもらって外から注入してもらうことということです。
誰かがインスタンス(依存性)
を生成しそのインスタンスを注入
してくれるので依存性の注入というんですね。
DIを使うことのメリットをイメージでつかむ
初めにDIを使うとどんな良いことがあるのか?ということから説明をしていきます。
何か難しいことを理解するときはイメージが大切だと思うので、まずは僕の中のイメージを共有します。
これはDI自体のイメージではなく、DIによって得られるメリットの1つのイメージです!
では、下のキャラクターのイラストをご覧ください。
フック船長 | クロコダイル |
---|---|
フック船長とクロコダイルですね。2人の共通点はなんでしょう?
左手の鉤爪
ですね。
2人とも同じ鉤爪を持っていますが、この2人の鉤爪は少し違います。
何が違うかというと、クロコダイルの鉤爪は付け替えが可能
です。鉤爪のカバーみたいものを外すと、毒針のようにもなるし、鉤爪が折れてしまえば、中からナイフもだせます。しかし、フック船長の鉤爪はワンパターン
です。たぶん。
クロコダイルは状況や相手に合わせて、使う武器を選べますが、フック船長はそんなことはできません。
これがDIとどんな関係があるのか?
DIを使うとクロコダイルのように状況に応じて使うものを変更することができます。
DIを使わないでコードを書くのがフック船長
、DIを使ってコードを書くのがクロコダイル
といったイメージです。
DIを使うことで得られるメリットは都合の良いように何かを変更することが可能になるということです。
では、実際にDIを用いて都合のいいように変更するという例をコードを使って紹介していきます。
計算するサンプルを用いてDIのメリットを紹介!
DIを使わないパターン
Model
- 足し算をするだけのModelクラスです
- サーバーと通信している想定です(非同期にできてないじゃないかというのは無視してください...)
- サーバーでは足し算の結果を戻すとともにログをデータベースに保存しているみたいなこともしているとします
final class AdditionModel {
func calculate(num1: Int, num2: Int) -> Int {
// サーバーと通信して結果を取得する。
// サーバーではデータベースの何かしらの書き換えも行っている
num1 + num2
}
}
Modelを呼び出すクラス
- Modelクラスの計算メソッドを呼び出します
- DIをしていないのでクラス内でAdditionModelのインスタンスを生成しています
- MVPを使用していればPresenterにあたります
final class Hoge {
private let calculator = AdditionModel()
func showResult() {
let result = calculator.calculate(num1: 3, num2: 2)
print(result)
}
}
Hogeクラスの実行
- 以下のようにHogeクラスをインスタンス化し、
showResult
を呼ぶと計算結果が出力されます
let hoge = Hoge()
hoge.showResult()
特に複雑なことはしていないので、ここまでは大丈夫かと思います。
テストをしたい
では、Hogeクラスのテストをしたいと思ったとしましょう。
ただ、テストのときにはAdditionModel
のcalculateメソッド
は呼びたくありません。
このメソッドを呼ぶとサーバーと通信して、データベースの値を書き換えてしまいます。
Hogeクラスを書き換えずにテストをできるようにするためにはどうしたらいいのでしょうか?
ここでDIが登場します!
DIを使う場合
protocol
- protocolを以下のように作ります
protocol Calculator {
func calculate(num1: Int, num2: Int) -> Int
}
Modelクラス
- Modelクラスは2つ作ります
- 1つは本番用、もう1つはテスト用でサーバーとは通信しないクラスです
- DIをしないときと違うのはModelにprotocolを準拠させているところです
- そのほかは変わりません
final class AdditionModel: Calculator {
func calculate(num1: Int, num2: Int) -> Int {
// サーバーと通信して結果を取得する。
// サーバーではデータベースの何かしらの書き換えも行っている
print("本番だよ")
return num1 + num2
}
}
final class AdditionModelMock: Calculator {
func calculate(num1: Int, num2: Int) -> Int {
// サーバーと通信しないようにしておく。
// サーバーから返ってきてほしい値を設定する
print("モックだよ")
return num1 + num2
}
}
Modelを呼び出すクラス
- DIを使って、クラスのインスタンス生成を
Hogeクラス
のなかではしないようにしました
final class Hoge {
private let calculator: Calculator
init(calculator: Calculator) {
self.calculator = calculator // ← ココ! DI! インスタンスを外部から貰ってる!
}
func showResult() {
let result = calculator.calculate(num1: 3, num2: 2)
print(result)
}
}
Hogeクラスの実行
本番用のModelを使う
- 本番用の
AdditionModel
を使いたいときは以下のようにします - Hogeクラスをインスタンス化するときに、使用したいModelのインスタンスを生成して渡します
- AdditionModelは
Calculator
protocolに準拠しているのでHogeクラスのインスタンス生成時に使用することができます。
let hoge = Hoge(calculator: AdditionModel())
hoge.showResult()
// 以下のようにコンソールに表示されます
// 本番だよ
// 5
テスト用のModelを使う
- テスト用のModelを使いたいときは以下のようにします
- AdditionModelMockも
Calculator
protocolに準拠しているのでHogeクラスのインスタンス生成時に使用することができます。
let hoge = Hoge(calculator: AdditionModelMock())
hoge.showResult()
// 以下のようにコンソールに表示されます
// モックだよ
// 5
このように、protocolとDIを組み合わせることでHogeクラス
の内容を変えることなく、呼び出す処理の内容は変えることができます。
呼び出すクラスが自由に変えられるというのが僕の中ではクロコダイルのイメージでした。
補足
今紹介したように、クラスを差し替えられることがDIのメリットであるという紹介をしました。
ただ実際はDIを用いてDIPをするから、このようなメリットが得られると解釈しています。
「え?どゆこと?」ってなった人はとりあえずDIのメリット=差し替え可能と理解しておいていいと思っています。
DIPってなんやねんという話をすると長くなるので、ここでは説明を省きますが、詳しく知りたい方は依存性逆転の法則(DIP)
を調べてみてください。
まとめ
初めに紹介したように、DI
とは
使いたいクラスのインスタンスを自分で生成するのではなく、外から注入してもらうこと
です。
おまけ
具象を注入することはDIではないのか?
僕の解釈では具象を注入する場合、抽象を注入する場合のどちらもDIになると解釈しています。
ただし、具象を注入することによって得られるメリットはほとんどないのではないか?と思っています。
具象を注入する場合、先に挙げた、クラスを置き換えるということができなくなってしまいますね。
おわりに
いかがだったでしょうか。少しでもDIとDIを使うことのメリットをイメージできていたら幸いです。
僕の解釈が盛大に間違っている可能性も大いにあるので、ご自身でも、ぜひ調べてみてください!