依存関係逆転の法則(Dependency Inversion Principle): Go 言語で柔軟で強い設計を実現するには?

依存関係逆転の法則(Dependency Inversion Principle)とは

依存関係逆転の法則とは、ソフトウェア設計において「上位モジュール(ビジネスロジックなど)が下位モジュール(詳細な実装、たとえばデータベースや外部サービスなど)に依存しないようにし、両者とも“抽象(インターフェースや抽象クラス)”に依存するべきだ」という原則です。これにより、システムは柔軟性・拡張性・保守性が高くなり、変更に強くなります。

2 つのポイント

  • 上位モジュールも下位モジュールも、具体的な実装ではなく抽象(インターフェースなど)に依存する
  • 実装の詳細(下位モジュール)が抽象に従うようにする(=依存の向きを逆転させる)

具体例で理解する

1. 具象に依存している悪い例

たとえば「メールを送る」機能を考えます。

// EmailSender はメール送信を担当する構造体
type EmailSender struct{}

// SendEmail はメールを送信するメソッド
func (e *EmailSender) SendEmail(message string) {
	// メール送信処理
	fmt.Printf("メールを送信中: %s\n", message)
}

// NotificationService は通知サービスを提供する構造体
type NotificationService struct {
	emailSender *EmailSender
}

// NewNotificationService はNotificationServiceの新しいインスタンスを作成
func NewNotificationService() *NotificationService {
	return &NotificationService{
		emailSender: &EmailSender{},
	}
}

// Send はメッセージを送信するメソッド
func (n *NotificationService) Send(message string) {
	n.emailSender.SendEmail(message)
}

// 使用例
func main() {
	service := NewNotificationService()
	service.Send("テストメッセージです")
}

この例では、NotificationServiceEmailSenderという具体的な実装に直接依存しています。もし今後、SMS 送信や他の通知手段を追加したい場合、NotificationServiceのコードを修正しなければなりません。


2. 抽象に依存させる良い例(依存関係逆転の法則の実践)

// MessageSender はメッセージ送信のインターフェース
type MessageSender interface {
	Send(message string)
}

// EmailSender はメール送信を実装する構造体
type EmailSender struct{}

// Send はMessageSenderインターフェースを実装
func (e *EmailSender) Send(message string) {
	// メール送信処理
	fmt.Printf("メールを送信中: %s\n", message)
}

// NotificationService は通知サービスを提供する構造体
type NotificationService struct {
	messageSender MessageSender
}

// NewNotificationService はNotificationServiceの新しいインスタンスを作成
func NewNotificationService(messageSender MessageSender) *NotificationService {
	return &NotificationService{
		messageSender: messageSender,
	}
}

// Send はメッセージを送信するメソッド
func (n *NotificationService) Send(message string) {
	n.messageSender.Send(message)
}

// 使用例
func main() {
	// EmailSenderを作成
	emailSender := &EmailSender{}

	// NotificationServiceに依存性を注入
	service := NewNotificationService(emailSender)

	// メッセージを送信
	service.Send("テストメッセージです")
}

このように、NotificationServiceMessageSenderという抽象(インターフェース)に依存し、EmailSenderはその実装を担います。今後、SmsSenderなど新しい通知手段を追加しても、NotificationServiceのコードを変更せずに済みます。

以下は、具体的に、SmsSenderメソッドを追加した場合のコードです。

// MessageSender はメッセージ送信のインターフェース
type MessageSender interface {
	Send(message string)
}

// ----------------------- 既存の実装 -----------------------

type EmailSender struct{}

func (e *EmailSender) Send(message string) {
	fmt.Printf("メールを送信中: %s\n", message)
}

// ----------------------- 追加する実装 ----------------------

type SmsSender struct{}

func (s *SmsSender) Send(message string) {
	// SMS 送信処理(ここではダミー)
	fmt.Printf("SMS を送信中: %s\n", message)
}

// -------------------- 共通サービス層 -----------------------

type NotificationService struct {
	messageSender MessageSender
}

func NewNotificationService(sender MessageSender) *NotificationService {
	return &NotificationService{messageSender: sender}
}

func (n *NotificationService) Send(message string) {
	n.messageSender.Send(message)
}

// ------------------------- 使用例 --------------------------

func main() {
	// 1) Email で通知
	emailSender := &EmailSender{}
	emailService := NewNotificationService(emailSender)
	emailService.Send("メールのテストメッセージです")

	// 2) SMS で通知
	smsSender := &SmsSender{}
	smsService := NewNotificationService(smsSender)
	smsService.Send("SMS のテストメッセージです")
}


日常的なたとえ

1️⃣ 具象(具体的なモノそのもの)に依存している例

イメージとしては、 “その道具・方法しか使えない” 状態です。

シーン 説明 困るポイント
➊ 乾電池式のおもちゃが “単三電池” 専用 取扱説明書には「このおもちゃは単三電池 2 本のみ対応」と書いてある。単四や充電池は入らない。 単三が切れたら遊べない。別規格が増えるたびに製品を買い替え or 改造が必要。
➋ コーヒーメーカーが “専用カプセル” だけを認識 機械内部のセンサーはメーカー純正カプセルの形状だけを前提に作られている。 新しい味のカプセルが出ても、形が少し違うとエラーになり抽出できない。
➌ スマートフォン用アクセサリが “Lightning 端子” 固定 ケーブル・ドック・マイク…すべて Lightning でしか給電・通信できない設計。 端末が USB-C に変わった瞬間、全部使えなくなる。

「特定の電池・カプセル・コネクタ = 具体的実装」にロックインされていると 道具が増える or 仕様が変わるたびに本体側を作り直さなければならない

これが、具象に依存している例です。


2️⃣ 抽象(共通ルール・インターフェース)に依存している例

イメージとしては、 “ルールさえ守れば何でも OK” な状態です。

シーン 説明 うれしいポイント
➊ コンセント(AC100 V 50/60 Hz)を使う家電 ドライヤー・炊飯器・PC などは「家庭用 100 V の壁コンセント」というルールだけを前提に作る。 メーカーも形も違う家電を自由に差し替えられる。将来、AI 搭載炊飯器が出てもコンセントはそのまま。
➋ “交通系 IC カード” 対応改札機 Suica、PASMO、ICOCA… すべて “FeliCa タッチで残高を引く” というインターフェースを共有。 新しい地域カードが増えても改札機はソフト追加だけで対応。ハードを一から作り替えなくて済む。
➌ 汎用サイズのネジ穴(1/4-20 UNC)の三脚 カメラでもスマホホルダーでも、同じネジ規格を守れば載せ替え可能。 新しいカメラが発売されても三脚本体は継続利用。周辺機器のバリエーションが豊富に。

「壁コンセント・IC 乗車券・ネジ規格 = 抽象インターフェース」にロックインされていると 新しい製品が増えても “共通ルール” さえ守れば双方そのまま使える

これが、抽象に依存している例です。


違いを一言で

観点 具象依存 抽象依存
依存先 “単三電池” のように 特定品そのもの “乾電池 1.5 V” のような 共通仕様
変化への強さ 容易に壊れる・作り直しが必要 追加・交換が容易
開発者/利用者の自由度 限定的(ロックイン) 拡張しやすくイノベーションが起きやすい

DIP を意識すると日常でもソフトウェアでも「長く使える・後で組み替えられる」設計ができ、“選択肢が増えても主役(高レベル)は迷わない” 世界が実現します。


なぜ「逆転」なのか

項目 ふつう(逆転前) 依存関係“逆転”後
コードの層 高レベル(ビジネスロジック)が ↓ 低レベル(具体的処理)を直接呼ぶ 低レベルが ↑ 共通の“ルール(抽象)”に合わせる
物理的な依存(import / include) 高レベル → 低レベル 低レベル → 抽象 ← 高レベル

つまり、本来“上”が“下”を見に行っていた依存の向きを、“下”が“上のルール”に合わせに行く形へひっくり返すからです。


まとめ

  • 依存関係逆転の法則とは、「具体的な実装」ではなく「抽象(インターフェース)」に依存することで、柔軟で変更に強い設計を実現する原則です。
  • これにより、上位モジュールも下位モジュールも抽象に依存し、実装の入れ替えや拡張が容易になります。

参考文献

1. https://qiita.com/k2491p/items/686ee5dd72b4baf9a81a
2. https://ja.wikibooks.org/wiki/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/%E4%BE%9D%E5%AD%98%E6%80%A7%E9%80%86%E8%BB%A2%E3%81%AE%E5%8E%9F%E5%89%87
3. https://qiita.com/hirodragon/items/090467284d19a4cbafa0
4. https://qiita.com/phenan/items/a8030b164d95a035a5dc
5. https://ja.wikipedia.org/wiki/%E4%BE%9D%E5%AD%98%E6%80%A7%E9%80%86%E8%BB%A2%E3%81%AE%E5%8E%9F%E5%89%87
6. https://zenn.dev/yoshinani_dev/articles/c743a3d046fa78
7. https://emb-sw-eng.com/solid_d/
8. https://zenn.dev/chida/articles/e46a66cd9d89d1
9. https://blog.openreplay.com/ja/%E4%BE%9D%E5%AD%98%E6%80%A7%E9%80%86%E8%BB%A2%E3%81%AE%E5%8E%9F%E5%89%87-%E8%AA%AC%E6%98%8E/
10. https://rookie-programmer.jp/?p=75
11. https://qiita.com/marienplatz/items/1e3fe7597afc75b22399
12. https://qiita.com/zizynonno/items/6bf71d73d790e27fb9ee
13. https://yuru-uni.com/2023/04/23/solid_principle_d/
14. https://blog.chiyuu.co.jp/2024/06/06/dependency-inversion-principle/
15. https://plainprogram.com/dependency-inversion-principle/

よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

はじめまして、「知識を愛する者」愛知郎です。

五反田でエンジニアとして活動してます。

こちらでは、私が最近学んだこと発信しています。

目次