ドラ🔵もんで依存性の注入と依存性逆転の原則を理解する!!

# はじめに

依存性の注入(Dependency Injection, DI)は、習得するのは難しいですが、理解すると簡単にできるようになります。また、依存性の注入と間違えやすい概念として、SOLID原則の一つである依存性逆転の原則(Dependency Inversion Principle, DIP）があります。

今回は、依存性の注入と依存性逆転の原則という概念をドラ🔵もんで分かりやすく解説したいと思います。

ちなみにスライドもあります。(スライドにはコード例は載ってません)

<script defer class="speakerdeck-embed" data-id="f585e08b87e5475dbf855f9faf15d161" data-ratio="1.7777777777777777" src="//speakerdeck.com/assets/embed.js"></script>

# 依存性の注入(DI)

## DIをドラ🔵もんで解説する

の○太はドラ🔵もんに依存しています。ドラ🔵もんを他のロボットに変えるとの🟡太は死にます。生死がドラ🔵もんにかかっていますが、の🟡太は誰にも相談しないので、誰もドラ🔵もんがの🟡太と関わっていることを知りません。この状態がDIしていない状態です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156096/ed0412c4-ef6a-5dd3-2108-aaed8ada0962.png)

上記のままだと、誰もの🟡太を助けようがありません。
そこで、の🟡太が周りに相談します。の🟡太がドラ🔵もんに依存しているということが周知となりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156096/7c3d7003-0416-9d4b-ecc1-d49a19bf6f30.png)

この状態が依存性を注入した状態です。

## 依存性の注入の誤解

* DIをしたら、依存関係がなくなる　→ 依存関係は無くなりません
* DIをしたので、テストや切り替えが可能となる　→ できる場合とできない場合がある

### テストできる場合

抽象なものに依存しているときはテストできます。

例えば、

```go
func main() {
    f()
}
func f() {
    hoge()
    foo()
}
func hoge() {
    return 1
}
func foo() {
    return 9
}
```

上記のコードをDIすると、

```go
func main() {
 result := sum(hoge() + foo())
 fmt.Println(result)
}

func sum(a int, b int) int {
 return a + b
}

func hoge() int {
 return 1
}

func foo() int {
 return 9
}
```

となりますが、以下のようにテストができます。注入しているものがintなので、他のものに置換可能です。

```go
func Test_sum(t *testing.T) {
 expected := 10
 actual := sum(2, 8)
 if actual != expected {
  t.Errorf("sum() = %v; want %v", actual, expected)
 }
}
```

### テストできない場合

具体に依存していたら、モックができないのでテストできません。
例えば、先ほどのドラ🔵もんの例は置換ができません。(ドラ🔵もんの代わりになるものなんていない!)

## 結局DIとは何なのか？

DIとは、色々な意味で使用されることがありますが、以下の2つだと考えております。

### 狭義のDI

明示的に周りに何を必要とするかを宣言すること

### 広義のDI

単一責任の原則、リスコフの置換原則、Adapterパターン、Compositeパターンなど、様々な原則とパターンを集めたもの。

## DIの目的

コード疎結合にして、保守容易性を向上させる。

# 依存性逆転の原則(DIP)

## 依存性逆転の原則をドラ🔵もんで解説する

の○太はドラ○もんに依存しています。ドラ🔵もんを他のロボットに変えるとの🟡太は死にます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156096/7c3d7003-0416-9d4b-ecc1-d49a19bf6f30.png)

ドラ🔵もんは所詮四次⚪️ポケットであることに気づきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156096/b251c47f-153d-da64-c1cd-7ffeb6cb79d5.png)

四次⚪️ポケットさえあればの○太は生きていけます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156096/14bed52b-396c-bfa8-fad4-ef56c336e5e3.png)

ドラ○もんじゃなくても、四次⚪️ポケットを持っていれば、ぶっちゃけド🟠ミちゃんでも誰でもいいです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156096/0e1f98c4-abd7-5bec-29ca-d5ef6e875bdb.png)

つまり、ドラ🔵もんを代わりのものに置き換えることが可能となります。この状態が、依存関係の逆転した状態です。

## 依存性逆転の原則とは

具象から抽象へ依存させることで、依存関係を逆転すること。
つまり、interfaceや抽象クラスに依存させることによって、依存関係を逆転させ、実装の切り替えや置換を可能とします。

## 四次⚪️ポケットは具象ではないのか？

ここでは、ドラ🔵もん、ド🟠ミちゃんなどの「秘密道具を出す」という振る舞いを抽象化しているので、四次⚪️ポケットにしています。

上記の例で、より抽象的なポケットにしてしまうと、ズボンのポケットを持っている者だったら誰でもドラ🔵もんの代わりができてしまいます。

次に、ドラ🔵もんやド🟠ミちゃんを抽象化しものが、ロボットであった場合、ドラ🔵もんの代わりを鉄腕ア⚪︎ムやガ⚪︎ダムができるようになってしまいます。

つまり、「秘密道具を出す」という振る舞いに限定したいので、その **振る舞いに適した抽象度** である四次⚪️ポケットとしております。(四次⚪️ポケット以外でも *SecretToolsSupplier* とかでもいい)

# 実装例

実際に上記の例をGoで実装してみます。

## DIしていない例

の🟡太がドラ🔵もんに依存しているのを周り(main)から見えていない状態です。

[Go Playground](https://go.dev/play/p/o_tCf4EWATB)

```go
type Doraemon struct{}

func (d Doraemon) GetItem() (string, error) {
 return "暗記パン🍞", nil
}

func NewDoraemon() Doraemon {
 return Doraemon{}
}

// の🟡太がドラ🔵もんに依存している
type Nobita struct {
 doraemon Doraemon
}

func NewNobita() Nobita {
 doraemon := NewDoraemon()
 return Nobita{
  doraemon: doraemon,
 }
}

func (n Nobita) study() string {
 item, err := n.doraemon.GetItem()
 if err != nil {
  return "勉強できないよ〜😭"
 }
 return fmt.Sprintf("%sを使って勉強できた!", item)
}

func main() {
    // ドラ🔵もんがmainから見えていない
 nobita := NewNobita()
 result := nobita.study()
 fmt.Println(result)
}
```

時間があれば上記の一度テストをしてみてください。失敗テストを実装するのが困難だと思います。

## DIした例

の🟡太がドラ🔵もんに依存していることを、周り(*main*)に明示的に宣言します。(*NewNobita*)

[Go Playground](https://go.dev/play/p/x_ONMjilGJ3)

```go
type Doraemon struct{}

func (d Doraemon) GetItem() (string, error) {
 return "暗記パン🍞", nil
}

func NewDoraemon() Doraemon {
 return Doraemon{}
}

// の🟡太はドラ🔵もんに依存している
type Nobita struct {
 doraemon Doraemon
}

//　外部からドラ🔵もん注入している
func NewNobita(doraemon Doraemon) Nobita {
 return Nobita{
  doraemon: doraemon,
 }
}

func (n Nobita) study() string {
 item, err := n.doraemon.GetItem()
 if err != nil {
  return "勉強できないよ〜😭"
 }
 return fmt.Sprintf("%sを使って勉強できた!", item)
}

func main() {
    // ドラ🔵もんはmainで作成できる
 doraemon := NewDoraemon()
 nobita := NewNobita(doraemon)
 result := nobita.study()
 fmt.Println(result)
}
```

さて、こちらも時間があれば上記の一度テストをしてみてください。DIしていない例と同様、失敗テストを実装するのが困難です。

## 依存関係の逆転

[Go Playground](https://go.dev/play/p/MHMogbVb2Go)

```go
type FourDimensionalPocket interface {
 GetItem() (string, error)
}

type Doraemon struct {
}

func (d Doraemon) GetItem() (string, error) {
 return "暗記パン🍞", nil
}

func NewDoraemon() *Doraemon {
 return &Doraemon{}
}

// の🟡太は四次⚪️ポケットに依存する
type Nobita struct {
 pocket FourDimensionalPocket
}

func NewNobita(pocket FourDimensionalPocket) Nobita {
 return Nobita{
  pocket: pocket,
 }
}

func (n Nobita) study() string {
 item, err := n.pocket.GetItem()
 if err != nil {
  return "勉強できないよ〜😭"
 }
 return fmt.Sprintf("%sを使って勉強できた!", item)
}

func main() {
 doraemon := NewDoraemon()
 nobita := NewNobita(doraemon)
 result := nobita.study()
 fmt.Println(result)
}
```

さて、上記のテストは簡単にできます。

<details>
<summary>テストの例</summary>

```go
// ドラ🔵もんをモックする
type mockDoraemon struct {
 want bool
}

func mockNewDoraemon(want bool) mockDoraemon {
 return mockDoraemon{want: want}
}

func mockNewNobita() FourDimensionalPocket {
 return mockDoraemon{}
}

func (d mockDoraemon) GetItem() (string, error) {
 if d.want {
  return "", assert.AnError
 }
 return "暗記パン🍞", nil
}

func TestNobita_study(t *testing.T) {
 tests := []struct {
  name    string
  want    string
  wantErr bool
 }{
  {
   name:    "Success Test",
   want:    "暗記パン🍞を使って勉強できた!",
   wantErr: false,
  },
  {
   name:    "Fail Test",
   want:    "勉強できないよ〜😭",
   wantErr: true,
  },
 }
 for _, tt := range tests {
  t.Run(tt.name, func(t *testing.T) {
   doraemon := mockNewDoraemon(tt.wantErr)
   nobita := NewNobita(doraemon)
   if got := nobita.study(); got != tt.want {
    t.Errorf("Nobita.study() = %v, want %v", got, tt.want)
   }
  })
 }
}
```

</details>

# 終わりに

DIや依存関係の逆転は間違えて使ってしまうと、かえって悪化させてしまう可能性があるので、適切な使い方で使えるようにしましょう。

ちなみに、今回はGoでコード例実装してみましたが、GoのinterfaceとJavaのinterfaceでは扱いが少し異なります。気になる方は、以下の記事を参考にしてください。

https://qiita.com/twrcd1227/items/db89448d61aac7f57ed1