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

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) 高レベル → 低レベル 低レベル → 抽象 ← 高レベル

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


まとめ

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

参考文献

【SOLID】依存関係逆転の原則を完全に理解したい - Qiita
SOLIDの原則とは? SOLIDは 変更に強い 理解しやすい などのソフトウェアを作ることを目的とした原則です。 次の5つの原則があります。 Single Responsibility Principle (単一責任の原則) Open-C...
プログラミング/依存性逆転の原則 - Wikibooks
初学者でも10分で理解できる依存性逆転の原則(Dependency inversion principle) - Qiita
この記事について この記事は YYPHPアドベントカレンダー22日目の記事となります。 内容としては私が主催しているPHPer向けの勉強会 ぺちオブにて開催した、初心者向けのオブジェクト指向勉強会にて説明した内容を加筆修正したものです。 勉...
令和版: 依存関係逆転の法則の実現方法 - Qiita
依存関係逆転の法則とは コアのロジックが実装の詳細に依存しないようにして、モジュール間を疎結合にしましょうという原則。 Wikipediaでは以下のように説明されている。 上位モジュールはいかなるものも下位モジュールから持ち込んではならない...
依存性逆転の原則 - Wikipedia
依存性の逆転のいちばんわかりやすい説明
SOLID原則とは?SOLIDのD、依存関係逆転(依存性逆転)の原則と得られるメリットをわかりやすく解説
現役車載組込みソフトエンジニアの竹です。今回はSOLID原則のD、依存関係逆転の原則についてまとめます。この記事は依存関係逆転の原則の概要を説明するものであり、具体的な実装例については触れない初心者向けの内容となっています。SOLID原則S
【SOLID原則】依存性逆転の原則 - DIP
依存性逆転の原則とは?簡単に説明
【SOLID原則】新人エンジニアが教える「依存関係逆転の原則」
こんにちは。新人プログラマーの岩本です。今回はSOLID原則の1つ「依存関係逆転の原則(Dependency Inversion Principle)」について、自分なりに調べたことをまとめます。プログラムを書く上で「依存関係逆転」と聞くと...
依存性逆転の原則って、結局何が逆転してるの? - Qiita
bitFlyer Advent Calendar 2022の2日目です。 こんにちは、私はフロントエンド開発部でAndroidエンジニアをしています。 タイトルは、依存性逆転の原則に対する私のファーストインプレッションです。 依存性逆転の原...
依存関係逆転の原則 - Qiita
依存関係逆転の原則(Dependency Inversion Principle、DIP)は、ソフトウェア設計の原則の一つ。高レベルモジュールが低レベルモジュールに直接依存しないように設計することを意味する。代わりに、両方が抽象化に依存する...
【C#設計】SOLID原則をUnity公式サンプルで学ぼう~D:依存性逆転の原則~
はじめに講座トップに戻るこの講座ではプログラミングの設計を勉強する際に避けては通れない「SOLID原則」について学ぶことができます。SOLID原則は有名ですので名前を知っている人も多いかもしれませんが、全然知らない人でも大丈夫です!初心者で
SOLID原則の依存性逆転の原則(DIP)についてRubyで解説 | 株式会社CHIYUU 公式ブログ
オブジェクト指向プログラミングにおいて、コードの保守性、可読性、再利用性を高めるために、SOLID原則と呼ばれ
【SOLID】依存性逆転原則~大事なのは、抽象にもとづくこと | プレイン・プログラム
依存関係逆転原則 Dependency Inversion Principle 上位モジュールは下位モジュールに依存してはならない。両者は抽象に依存すべき。 抽象は詳細に依存してはならない。詳細は抽象に依存すべき。 ソフトウェアの構造を強固
タイトルとURLをコピーしました