0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DI(Dependency Injection:依存性の注入)をGo言語で実装

0
Posted at

DI(Dependency Injection:依存性の注入)とは?

プログラミングにおけるDIをひと言でいうと、「必要な道具(パーツ)を、自分で作らずに、外から持ってきてもらう仕組み」のことです。

これを現実世界の「お掃除ロボット」に例えてみましょう。

1. DIを使っていない状態(ガチガチに固定されたロボ)

「部屋を掃除するロボット」を開発したとします。
このロボットの内部に、「A社の普通の掃除機」を直接ハンダ付けして一体化させてしまいました。

問題点:

  • もし「水拭きもしたいな」と思っても、中の掃除機を外せないので、ロボット自体を丸ごと作り直す必要があります。
  • 掃除機の調子が悪くなったとき、ロボットを分解しないと修理やテストができません。

これをプログラムの世界では「依存度が固く結合している(密結合)」と言い、修正やテストがしにくい悪い状態とされます。

2. DIを使っている状態(アタッチメント式のロボ)

今度は、ロボット本体には「アタッチメントを差し込む穴(インターフェース)」だけを開けておきます。
そして、動かすときに「外から必要なパーツをカチッと差し込む」ようにしました。

メリット:

  • 絨毯を掃除したいときは「普通の掃除機パーツ」を差し込む。
  • フローリングをきれいにしたいときは「水拭きモップパーツ」を外から注入する。
  • テストをしたいときは「ダミーの軽いパーツ」を差し込んで、ロボットの動きだけを確認する。

このように、ロボット本体(クラス)のコードを一切書き換えることなく、外から道具(オブジェクト)を差し替えるだけで機能を変更できる仕組み、これが「依存性の注入(DI)」です。

開発における最大のメリット

DIを採用する理由は、主に次の2点です。

テストが圧倒的に楽になる
例えば「本番のデータベースにデータを保存するパーツ」の代わりに、「テスト用の仮のパーツ」を外から注入できるため、本番データを汚さずに安全にテストができます。

部品の交換(仕様変更)に強くなる
「決済システムをA社からB社に変えたい」という時も、パーツを差し替えるだけで済み、システム全体を修正する必要がなくなります。

Go言語で実装してみる

Go言語は「インターフェース」が非常にシンプルかつ強力に機能するため、DI(依存性の注入)の概念を表現するのに最適な言語の一つです。

先ほどのお掃除ロボットの例を、Goのコードで実装してみましょう。

1. 道具の規格(インターフェース)を決める

まずは、ロボットに差し込むパーツの共通ルール(規格)を作ります。Goではinterfaceを使って定義します。

package main

import "fmt"

// Cleaner は、お掃除パーツが満たすべき共通の規格(インターフェース)
type Cleaner interface {
	Clean() string
}

2. 具体的なパーツ(部品)を作る

次に、規格(Cleaner)に沿った具体的なパーツを2つ作ります。
Goでは、インターフェースに定義されているメソッド(今回は Clean())を実装していれば、自動的にそのインターフェースを満たしているとみなされます。

// VacuumCleaner は「普通の掃除機パーツ」
type VacuumCleaner struct{}

func (v VacuumCleaner) Clean() string {
	return "ゴミを強力に吸い取っています!"
}

// WaterMop は「水拭きモップパーツ」
type WaterMop struct{}

func (w WaterMop) Clean() string {
	return "床をピカピカに水拭きしています!"
}

3. お掃除ロボット本体を作る(DIの受け皿)

ロボット本体(Robot)は、具体的な「掃除機」や「モップ」のことは知りません。ただ、規格であるCleanerを持てるようにしておきます。

ここでのポイントは、構造体を初期化するときに外部からパーツを渡す(注入する)点です。

// Robot はお掃除ロボット本体
type Robot struct {
	// 具体的なパーツではなく、インターフェース(規格)を持たせる
	tool Cleaner 
}

// NewRobot はロボットを組み立てる関数(ここでパーツを注入する)
func NewRobot(c Cleaner) *Robot {
	return &Robot{tool: c}
}

// Start は掃除を開始するメソッド
func (r *Robot) Start() {
	fmt.Println("ロボットが起動しました。")
	// 注入されたパーツの能力を呼び出す
	result := r.tool.Clean() 
	fmt.Println("動作状況:", result)
	fmt.Println("掃除が完了しました。\n")
}

4. 動かしてみる(パーツの注入)

最後に main 関数で、ロボットに異なるパーツを「注入」して動かしてみます。

func main() {
	// パターンA: 掃除機パーツを注入
	vacuum := VacuumCleaner{}
	robotA := NewRobot(vacuum) // ここでDI(注入)
	robotA.Start()

	// パターンB: 水拭きモップパーツを注入
	mop := WaterMop{}
	robotB := NewRobot(mop) // ここでDI(注入)
	robotB.Start()
}

出力結果

ロボットが起動しました。
動作状況: ゴミを強力に吸い取っています!
掃除が完了しました。

ロボットが起動しました。
動作状況: 床をピカピカに水拭きしています!
掃除が完了しました。

GoにおけるDIのポイント

ロボット本体(Robot 構造体)のコードは一切書き換えていないのに、NewRobot() の引数に変えるだけで、ロボットの挙動(掃除機かモップか)を自由に変更できています。

もし「テスト用のダミーパーツ」を作りたい場合も、Clean() string を持つ構造体をサクッと作って NewRobot() に渡すだけで、本番環境を汚さずにテストが可能になります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?