Dependency Injection

公開日:2024-10-11


はじめに

  • 依存性注入(DI: Dependency Injection)について、その概念をGo言語を用いて理解する。

DIとは

  • DI(Dependency Injection:依存性の注入) とは、プログラム内でオブジェクトが必要とする依存オブジェクトを、自分自身で生成するのではなく、外部から提供(注入)される設計パターンのこと。
  • 依存オブジェクト(例:Database)を、利用するクラス(例:Service)の内部で作成するのではなく、外部から注入する方法を指す。
  • コンストラクタ、セッター、メソッド呼び出し時の形で行われることが一般的
  • DIはSOLID原則の一つである、依存性逆転の原則(Dependency Inversion Principle: DIP) を実現するための手段の一つ。(DIP: 高レベルのモジュールは低レベルのモジュールに依存すべきではなく、どちらも抽象に依存すべきである。また、抽象は詳細に依存すべきではなく、詳細が抽象に依存すべきであるというルール)

DIのメリット

  • 疎結合: DIを使用すると、クラスは依存するクラスの具体的な実装に依存しなくなり、異なる実装を容易に差し替えることができる。これにより、システム全体が柔軟で拡張性の高いものになる。
  • テスト容易性: モックオブジェクトやスタブを使って、依存関係を置き換えることで、依存するクラスのテストが容易になる。
  • 保守性向上: 各クラスが自分自身で依存オブジェクトを生成する必要がなくなるため、クラスの役割が明確化され、メンテナンスが容易になる。

DIのコード例

  • DIを理解するために、Go言語を用いてDIの例を示す。

  • このコードでは、コンストラクタインジェクションを使用し、Service構造体がDatabaseインタフェースに依存しており、Serviceは具体的なデータベースの実装に依存せずに柔軟にデータベースを切り替えることができる。

    package main
    
    import (
      "fmt"
    )
    
    // データベース操作を抽象化するインターフェース
    type Database interface {
      QueryData() string
    }
    
    // 実際のDB構造体(本番用)
    type DB struct {
      ConnectionString string
    }
    
    // DBのQueryDataメソッド
    func (db *DB) QueryData() string {
      return "Data from Real DB"
    }
    
    // Service構造体
    type Service struct {
      db Database // インターフェースを使って依存関係を抽象化
    }
    
    // Serviceに依存関係を注入するコンストラクタ
    func NewService(db Database) *Service {
      return &Service{db: db}
    }
    
    // Serviceが提供するビジネスロジック
    func (s *Service) GetData() string {
      return s.db.QueryData()
    }
    
    func main() {
    
      // DBのインスタンスを作成
      db := &DB{ConnectionString: "localhost:5432"}
    
      // ServiceにDB Interfaceを注入してインスタンスを作成
      service := NewService(db)
    
      // Serviceを使用
      fmt.Println(service.GetData())
    }
    
  • 実際に、DB構造体をモックDB構造体に置き換えることが可能。

    • DB構造体をモックDB構造体に置き換える
    - // 実際のDB構造体(本番用)
    - type DB struct {
    -   ConnectionString string
    - }
    -
    - // DBのQueryDataメソッド
    - func (db *DB) QueryData() string {
    -   return "Data from Real DB"
    - }
    
    + // モックDB構造体(テスト用)
    + type MockDB struct{}
    +
    + // モックのQueryDataメソッド
    + func (mdb *MockDB) QueryData() string {
    +   return "Mock Data"
    + }
    
    • main関数内でモックDBを使用する
    - // DBのインスタンスを作成
    - db := &DB{ConnectionString: "localhost:5432"}
    -
    - // ServiceにDB Interfaceを注入してインスタンスを作成
    - service := NewService(db)
    
    + // モックオブジェクトを作成
    + mockDB := &MockDB{}
    +
    + // ServiceにモックDBを注入してインスタンスを作成
    + service := NewService(mockDB)
    
  • 上記のコードの理解を深めるために、図解した。

    DI

    1. Database Interfaceでデータベースに必要なメソッドを定義する
    2. 本番用のDB StructとモックDB StructでDatabase Interfaceを実装しているため、そのインスタンスはDatabase Interfaceを満たしている。
    3. 一方でService Structは、Database Interfaceに依存しており、Database Interfaceに実装しているメソッドを呼び出し、Serviceのビジネスロジックを実装している。ここでDB Structに依存しておらず、Database Interfaceに依存しているため、DB Structの種類(本番用、モック用などなど)を増やすことが可能となる
    4. NewService関数でDatabase Interfaceを引数に取り(注入し)、serviceのインスタンスを生成し、そのインスタンスを使用する。このNewService関数内でDependency Injectionが行われている。

まとめ

  • 今回は、依存性注入(DI)について、その概念とメリット、コード例をGo言語を用いて理解しました。特にコードを図解したことにより、interfaceを利用するメリットがわかりました。

😄 END 😀