Carpe Diem

備忘録

GoでDependency Injection

概要

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で抽象化しておけば、後からデータストアを変更するといった対応もスムーズにできます。

ソース