概要
「Dependency Injection=依存性の注入」と言われますが、依存したオブジェクトを外部から入れることで何がメリットなのかを感じ取るのは実際に書いてみて分かると思うので、勉強としてまとめてみました。
Dependency Injectionとは
A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it. The service is made part of the client's state.[1] Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.
https://en.wikipedia.org/wiki/Dependency_injection
DIパターンではない書き方1
関数が直接書いてあるパターン
package main import "fmt" func main() { Show() } func Show() { Name() Age() } func Name() { fmt.Println("Taro") } func Age() { fmt.Println("15") }
問題点
Show()関数がName()関数やAge()関数に依存している。
テスト時にName()関数やAge()関数がテストしにくかったりすると、Show()関数もテストが大変になります。
DIパターンではない書き方2
委譲パターン
package main import "fmt" func main() { Show() } func Show() { s := Student{} s.Name() s.Age() } type Student struct{} func (s Student) Name() { fmt.Println("Taro") } func (s Student) Age() { fmt.Println(15) }
問題点
Show()関数がStudentオブジェクトに依存している。
同じくテスト時にName()関数等がテストしにくかったりすると、Show()関数もテストが大変になります。
DIパターンの書き方1
通常パターン
とりあえず外部から依存していたオブジェクトを入れたらDIパターンと言えます。
package main import "fmt" func main() { s := Student{} Show(s) } func Show(s Student) { s.Name() s.Age() } type Student struct{} func (s Student) Name() { fmt.Println("Taro") } func (s Student) Age() { fmt.Println(15) }
問題点
Show()関数が外部から注入したStudentオブジェクトに依存しています。
つまりDIパターンでも、この書き方だとSow()関数のテストが大変になります。
DIパターンを使ったからといって必ずしも依存が消えるわけではないです。
DIパターンの書き方2
抽象に依存させたパターン
ではどうやって依存を疎にするかというと、interfaceに依存させればいいのです。
package main import "fmt" func main() { s := &StudentImpl{} Show(s) } func Show(s Student) { s.Name() s.Age() } type Student interface{ Name() Age() } type StudentImpl struct{} func (s *StudentImpl) Name() { fmt.Println("Taro") } func (s *StudentImpl) Age() { fmt.Println(15) }
こうするとinterfaceを実装しているオブジェクトであればどれでも入れることができます。
つまりテスト時にはShow()関数にモック用のオブジェクトを入れても大丈夫、ということになります。
以下の様な感じですね。
func TestShow() { s := &StudentMock{} Show(s) } func Show(s Student) { s.Name() s.Age() } type Student interface{ Name() Age() } type StudentMock struct{} func (s *StudentMock) Name() { fmt.Println("Mock") } func (s *StudentMock) Age() { fmt.Println(100) }
「この関数って外部API使うから、それに依存してるテストが面倒」といった時にモックを注入させればテストが簡単に書けます。
またデータを保存する先をinterfaceで抽象化しておけば、後からデータストアを変更するといった対応もスムーズにできます。