コンピューターサイエンス入門:理論編
疎結合と密結合 — 依存性注入で設計を変える
疎結合と密結合 とは
疎結合と密結合の違いを比喩・図・コードで理解する。依存性注入(DI)で結合度を下げると、テスト容易性と変更耐性が同時に上がる。
疎結合 / 密結合 — 「依存の強さ」を設計する
疎結合(loose coupling) と 密結合(tight coupling) は、モジュール同士が「どれくらい強く依存しあっているか」を表す設計用語です。結合度が高い(密結合)ほど一方を変えるともう一方も壊れやすく、低い(疎結合)ほど独立して差し替え・テスト・再利用ができます。
結合度の低さは、ソフトウェアが「変更に強い」かどうかを決める一番大きな要因です。実務では「結合度を下げて、凝集度を上げる」が合言葉になります。
結論先取り(このレッスンで分かること)
- 密結合 は具体クラスや内部実装に直接依存している状態
- 疎結合 はインターフェース(抽象)や契約だけに依存している状態
- 依存性注入(DI) で具象の生成を外に追い出すと、疎結合に寄せられる
- 結合度を下げると テスト容易性 × 再利用性 × 変更耐性 が同時に向上する
比喩で押さえる — 電車の連結と新幹線の編成
密結合は 「溶接で繋がった鉄道車両」、疎結合は 「規格化された連結器で繋がった車両」 に例えられます。溶接でつないだ車両は強固ですが、1 両だけ交換することはできません。連結器なら、車両を切り離して別の車両に差し替えられます。これがソフトウェアでも同じで、new ConcreteClass() をコード中に直接書く(=溶接)か、interface 越しに受け取る(=連結器)かの違いです。
左の密結合では OrderService が MySQLRepo も SmtpSender も具象として知っているため、DB を Postgres に変えたい・テストでメール送信を抑止したい、というたびに OrderService の中身を書き換える必要があります。右の疎結合では OrderService は interface しか知らない ため、実装を InMemoryRepo や MockSender に差し替えても本体は無傷です。
コードで比較 — 密結合 vs 疎結合(依存性注入)
下のコードは「注文を保存してメール送信する」処理を、密結合版と疎結合版で並べたものです。
Python
# 密結合 — 具象クラスを内部で new している
class OrderServiceTight:
def __init__(self):
self.repo = MySQLRepo() # ここで具象に依存
self.sender = SmtpSender() # ここでも具象に依存
def place(self, order):
self.repo.save(order)
self.sender.send(order.email, "thanks")Python
# 疎結合 — 抽象(interface 相当)を外から受け取る
class OrderServiceLoose:
def __init__(self, repo, sender): # ← 依存性注入
self.repo = repo
self.sender = sender
def place(self, order):
self.repo.save(order)
self.sender.send(order.email, "thanks")呼び出し側はこう書きます。
Python
# 本番
service = OrderServiceLoose(MySQLRepo(), SmtpSender())
# テスト — 具象を差し替えるだけで「本体」は再利用できる
service = OrderServiceLoose(InMemoryRepo(), MockSender())密結合版は OrderServiceTight 自体を書き換えないと DB やメール経路を差し替えられません。疎結合版は コンストラクタに渡すオブジェクトを変えるだけ で挙動を切り替えられます。これがいわゆる 依存性注入(Dependency Injection、DI) です。
DI は「new を呼ぶ責任を外に押し出すパターン」。呼び出し側(main や DI コンテナ)が具象を作って渡すと、本体は抽象だけを知っている状態を保てます。
トレードオフ — 疎結合は無料ではない
疎結合は万能薬ではありません。設計判断のための比較表は下記のとおりです。
| 観点 | 密結合 | 疎結合 |
|---|---|---|
| コード量 | 少ない | interface / DI 設定が増える |
| 読解難易度 | 直感的(具象が見える) | 抽象を辿る必要がある |
| テスト容易性 | 低い(外部依存をモックしづらい) | 高い(fake を注入できる) |
| 変更耐性 | 低い(連鎖変更が多い) | 高い(実装差し替えで完結) |
| 再利用性 | 低い | 高い |
| 起動コスト / 性能 | 速い | DI コンテナ等で初期化が重くなる場合あり |
「小さなスクリプト」「使い捨てのプロトタイプ」「明確に 1 つの実装しか想定しない部分」は 密結合のままで十分 です。逆に、外部 I/O・将来差し替えが見えている依存・ユニットテストの境界、は疎結合に寄せる価値が大きいです。
「すべて疎結合にする」は過剰設計。境界(DB / 外部 API / 時刻 / ファイル IO など、副作用を持つ箇所)を中心に疎結合化するのが現実的です。
よくある誤解
1 つ目は 「疎結合 = interface を切ること」と思い込む こと。interface だけ切っても、生成箇所が本体に書いてあれば密結合のままです。重要なのは 「生成(new)の責任を外に出すこと」。2 つ目は 「DI = フレームワークが必要」 という誤解。コンストラクタ引数で渡すだけでも立派な DI です。Spring / NestJS のようなコンテナは省力化のための道具にすぎません。3 つ目は 「結合度と凝集度を混同する」 こと。結合度は「モジュール間の依存の強さ」、凝集度は「1 モジュール内の責務のまとまり具合」で別軸です。結合度は低く、凝集度は高く が原則です。
やってみよう(思考演習)
- 手元のコードで
new ConcreteRepo()やimport mysqlを 本体の中で直接呼んでいる箇所 を 1 つ挙げ、それをコンストラクタ引数に追い出すリファクタを書いてみる - 「ロガー」「時刻取得」「外部 API クライアント」のうち、どれを最初に疎結合化すると一番テストが楽になるか考える(多くの場合は 時刻取得 から始めるのが効果が大きい)
- 自プロジェクトで疎結合にしなくてもいい部分(例: ピュアな関数、純粋なデータ変換)を 3 つ挙げてみる
よくある質問
Q. このトピックを覚える意味は何ですか?
A. 結合度の高い低いは、コードを触ったときに「どこまで影響が波及するか」を決める一番の要因です。密結合のまま設計を続けると、1箇所の変更が芋づる式に他の場所を壊すようになり、変更コストが雪だるま式に増えていきます。疎結合の考え方を早く身につけておくと、規模が大きくなっても壊れにくい設計を選べるようになります。
Q. 実務でこの知識を使う場面は?
A. DB接続・外部API・メール送信・現在時刻の取得など、副作用を伴う依存をテストしたい場面で頻出します。依存性注入(DI)でこれらをinterface越しに受け取るようにしておけば、テストではモックやフェイク実装に差し替えるだけでよく、実際にDBやメールサーバーに接続せずにユニットテストが書けます。フレームワークのDIコンテナ(Spring, NestJS等)の設計思想もこの延長線上にあります。
Q. 他の章とどう繋がりますか?
A. 前のレッスンで扱ったMVCのルーティングでは、ControllerがModelやViewとどう連携するかを見ましたが、その連携部分の依存関係を疎結合にできるかどうかが、この章のテーマと直結します。また、コース冒頭で扱ったデータ構造やアルゴリズムを部品として組み合わせるとき、部品同士の依存を疎結合にしておくと差し替えやテストがしやすくなります。
次のレッスン
次は コース総まとめクイズ に進みましょう。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- 疎結合と密結合 の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. 疎結合と密結合 とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン