goroumaru41gou

遊んでいる中でインプットした内容をアウトプットする場

Dipendency Injection(DI)について

詳細については、参考ページを参照するとして、ざっくりしたイメージを思い出せるような備忘録とする。

取り扱うもの

DI(Dipendency Injection)

※ DIコンテナについては、別の備忘録でまとめる予定なので、ここでは扱わない

依存しているとは?

機能Aと機能Bがあり、AはBを呼び出しているとする。 このとき、AはBに依存しているという。

DIとは?

DI(Dipendency Injection)とは、外部からオブジェクトを渡すという考え方・パターンのこと。 プログラミング言語に依らない考え方。

これは、機能Aから機能Bを呼ぶとき、外部からAとBへオブジェクトを渡すことへ置き換えて考える。

   1. 外部 → B (外部からBに必要なオブジェクトを渡す)
   2. 外部 → A (外部からAにBのオブジェクトを渡す)
   3. 項1、2によってAでBを使用できる

ちなみに、Dipendency Injectionは直訳すると「依存注入」だけれど、これは 「依存(されているオブジェクトを)注入(=渡す)」 と捉える。

なぜDI(オブジェクトを渡す)とするのか?

目的は、*コード変更があったとき、その影響範囲を最小限にすること。

機能Aと機能Bが独立していることで、中身が別々に構築でき、ユニットテストも可能となる。

   1. AからBを隠蔽する(抽象化する)
      AがBを呼び出すとき、AがBの内部を考えないようにする。

   2. Aのユニットテストを可能とする
      Bが完成してなくても、Aのみでユニットテストできるようにする。

NGなパターン

AがBを呼び出したいとき、Aでオブジェクトを作成して、Bへ渡してしまうパターン。

   1.  A → B (Bに必要なオブジェクトをAで作成して渡す)

これだと、AとBを独立させていないので、Aのユニットテストを書けないし、Bが変更されるとAも変更しなければいけない。

OKなパターン(コンストラクタを渡すパターン)

サービス(機能A)からレポジトリ(機能B)を呼び出す構成のとき。

   1. DIできるように受け皿をつくっておく
      * Bにおいて、Bのコンストラクタをつくる
      * Aにおいて、Aのコンストラクタをつくる

   2. DIする(オブジェクトを渡すだけ)
      * 外部 → B (オブジェクトを渡す)
      * 外部 → A (オブジェクトを渡す)

   3. AでBを使うなどの処理をする

ある「サービス」が「レポジトリ」を利用し、「レポジトリ」が「データベース」を利用する例をコードで確認する。

つまり、依存関係は・・・

  • レポジトリ(B)は、データベース(C)に依存している
  • サービス(A)は、レポジトリ(B)に依存している
func main() {
    db := defaultDB()
    
    // レポジトリに必要なオブジェクトを渡す
    repo := NewTicker(db)

    // サービスに必要なオブジェクトを渡す
    // サービスはrepoを生成していない
    service := NewTicker(repo)
}
// services

// 外部で使えるようインターフェースをつくる
type Ticker interface {
    Create(ticker models.Ticker) error
}

type ticker struct {
    repo repositories.Ticker
}

// コンストラクタ(=New)でオブジェクトを受け取る
func NewTicker(repo repositories.Ticker) *ticker {
    return &ticker{
        repo: repo,
    }
}

// これはサービスの機能
func (r *ticker) Create(ticker models.Ticker) error {
    return r.repo.Create(ticker)
}
// repositories

// serviceで使えるようインターフェースをつくる
type Ticker interface {
    Create(ticker models.Ticker) error
}

type ticker struct {
    db *gorm.DB
}

// コンストラクタ(=New)でオブジェクトを受け取る
func NewTicker(db *gorm.DB) *ticker {
    return &ticker{
        db: db,
    }
}

// これはリポジトリの機能
func (r *ticker) Create(ticker models.Ticker) error {
    r.db.Create(ticker)
    return nil
}

参考

GO言語とDependency Injection

Clean Architecture