📌

遅延バインディングってなんですか?DIの文脈で出てきて、交換容易性の話かなと思った

2024/05/18に公開

なぜ依存を注入するのか DIの原理・原則とパターン」を読んでてわからなかった「遅延バインディング」を調べてまとめました

遅延バインディングとは

先にインターフェース型の変数を宣言しておいて、あとでその実装を決めること。
つまり、

  • インターフェース型の変数を宣言したあとに=遅延
  • 具体的な実装を選択=バインディング

遅延バインディングってなんですか?DIの文脈で出ていて、交換容易性の話かなと思いました

遅延バインディング(Late Binding)とは、プログラムの実行時にオブジェクトや関数の実体が決定される技術のことを指します。DI(依存性注入)の文脈で遅延バインディングが言及される場合、依存関係を実行時に動的に解決し、必要に応じて異なる実装に置き換えることができるという特性を指します。これにより、柔軟性と交換容易性が向上します。

具体的には、遅延バインディングを用いることで以下のような利点があります:

  1. モジュール性の向上: 依存関係を動的に解決できるため、各コンポーネントが独立して開発・テスト可能になります。
  2. 交換容易性: 依存関係を実行時に差し替え可能なため、例えば異なるデータベース接続やサービスを容易に切り替えることができます。
  3. テストのしやすさ: モックやスタブを使って依存関係を置き換えることが容易になり、ユニットテストがしやすくなります。

以下に、Go言語を用いたDIと遅延バインディングの例を示します。

package main

import (
    "fmt"
)

// サービスのインターフェース定義
type Service interface {
    Execute() string
}

// サービスの実装A
type ServiceA struct{}

func (s *ServiceA) Execute() string {
    return "Service A Executed"
}

// サービスの実装B
type ServiceB struct{}

func (s *ServiceB) Execute() string {
    return "Service B Executed"
}

// クライアントが依存する構造体
type Client struct {
    service Service
}

// クライアントのコンストラクタ
func NewClient(service Service) *Client {
    return &Client{service: service}
}

// クライアントがサービスを使うメソッド
func (c *Client) DoWork() {
    fmt.Println(c.service.Execute())
}

func main() {
    // 実行時にサービスの実装を選択
    var service Service

    if true { // ここを条件により変更
        service = &ServiceA{}
    } else {
        service = &ServiceB{}
    }

    client := NewClient(service)
    client.DoWork()
}

この例では、main関数内でサービスの実装を動的に選択してClientに注入しています。これが遅延バインディングの一例で、実行時に依存関係を決定していることがわかります。このようにすることで、柔軟に依存関係を切り替えられるメリットがあります。

このコードの中で遅延バインディングが行われている具体的な部分は以下の箇所です:

var service Service

if true { // ここを条件により変更
    service = &ServiceA{}
} else {
    service = &ServiceB{}
}

client := NewClient(service)
  1. 依存関係の変数宣言:

    var service Service
    

    ここで、Serviceインターフェース型の変数serviceを宣言していますが、この時点では具体的な実装が何であるかは決まっていません。

  2. 実行時の条件に基づく依存関係の決定:

    if true { // ここを条件により変更
        service = &ServiceA{}
    } else {
        service = &ServiceB{}
    }
    

    この部分で、条件(ここでは単純にtrueを使用していますが、実際には他の条件や設定値などが使われることがあります)に基づいて具体的な実装を選択しています。この時点で初めてserviceが具体的なServiceAまたはServiceBのインスタンスを指すようになります。

  3. 依存関係の注入:

    client := NewClient(service)
    

    選択された具体的なサービスの実装をClientのコンストラクタに渡し、Clientのインスタンスを生成します。

これらのステップにより、依存関係の実装がプログラムの実行時に決定され、注入されることで、柔軟性と交換容易性が確保される遅延バインディングが実現されています。

遅延バインディングは以下のように分けられます:

  • 遅延(Late): インターフェース型の変数を先に宣言しておき、すぐには具体的な実装を決定しない。
  • バインディング(Binding): 実行時に条件などに基づいて具体的な実装を選択し、そのインターフェース型の変数にバインド(紐付け)する。

以下に、具体的な遅延バインディングの流れを再確認します:

  1. インターフェース型の変数を宣言する(遅延の部分):

    var service Service
    
  2. 実行時に条件に基づいて具体的な実装を選択しバインドする(バインディングの部分):

    if true { // ここを条件により変更
        service = &ServiceA{}
    } else {
        service = &ServiceB{}
    }
    

この流れにより、遅延バインディングが実現されます。ここでは、依存関係の具体的な実装が実行時まで決定されないため、プログラムの柔軟性や拡張性が向上します。

Discussion