概要
**依存性注入(Dependency Injection, DI)**は、
オブジェクトの依存対象(サービス、設定、外部リソースなど)を
内部で生成せず、外部から注入する設計手法である。
これは「疎結合」「テスタビリティ」「再利用性」「設定柔軟性」など、
ソフトウェア品質において不可欠な設計戦略であり、
Pythonでも実用的に導入・応用可能である。
1. なぜDIが必要か?
❌ 依存を内部でハードコード
class Service:
def __init__(self):
self.repo = FileRepository()
→ 再利用不可、モック困難、変更不能な密結合コード
✅ 外部から依存を注入
class Service:
def __init__(self, repo):
self.repo = repo
→ 依存対象の制御権が呼び出し元に移る
→ 柔軟性・テスト容易性・動的切替が可能
2. DIの3つの形態
タイプ | 概要 |
---|---|
コンストラクタ注入 |
__init__() に依存を渡す |
セッター注入 | プロパティやメソッド経由で依存を注入 |
インタフェース注入 | オブジェクトにインジェクションを要求させる |
Pythonでは主にコンストラクタ注入が使われる
3. DIを使ったテスト容易な設計
class UserService:
def __init__(self, repository):
self.repository = repository
def get_user(self, id):
return self.repository.find(id)
✅ テスト時
class MockRepository:
def find(self, id):
return {"id": id, "name": "Mock User"}
service = UserService(MockRepository())
assert service.get_user(1)["name"] == "Mock User"
→ 外部依存を切り離すことで、テストをシンプルかつ高速に保てる
4. DIコンテナの導入(例:injector
)
pip install injector
from injector import Module, Injector, singleton, inject
class Repository:
def get(self): return "RealData"
class AppModule(Module):
def configure(self, binder):
binder.bind(Repository, to=Repository, scope=singleton)
class Service:
@inject
def __init__(self, repo: Repository):
self.repo = repo
injector = Injector([AppModule()])
svc = injector.get(Service)
→ DIコンテナにより 依存の自動解決とスコープ管理 が可能
5. DIを活かす設計のポイント
- 明示的な依存関係の記述:依存を隠蔽しない
- インターフェースによる抽象化:具象型に縛られない
- 呼び出し側が注入責任を持つ:構成の柔軟性を高める
6. 実務的ユースケース
✅ リポジトリの切替(DB vs メモリ)
class MemoryRepo:
...
class DBRepo:
...
→ 実行環境ごとに UserService(repo=...)
で切り替え
✅ APIクライアントのMock化
class APIClient:
def fetch(): ...
class MockClient:
def fetch(): return {"mock": True}
svc = SomeService(client=MockClient()) # テストで注入
7. よくある誤用と対策
❌ シングルトン依存を直接呼ぶ
class Service:
def __init__(self):
self.config = Config.get_instance() # 💥 密結合
→ ✅ Service(config)
と注入可能にすべき
❌ コンストラクタが複雑すぎる
→ ✅ 依存は集約しすぎず分割して管理する(ファサード/ドメインごと)
結語
DIとは“設計における依存の制御権を呼び出し側に戻す”ことであり、
それは単なる技術的選択ではなく、責務分離・可読性・柔軟性を実現する設計原則である。
- 密結合を解消し、変化に強い構造を構築
- テストしやすいコードベースを標準化
- 明示的で動的な依存制御が拡張性を担保する
Pythonicとは、“動的で柔軟な言語特性の中に、明確な設計の意志を持ち込む”ことであり、
DIはその意志を、構造として体現する最適な手段である。