導入
Dependency Inversion Principle——「具体ではなく抽象に依存せよ」。上位のロジックが、下位の具体的な実装(DBやメール送信など)に直接依存すると、差し替えやテストが難しくなります。間にインターフェースを挟むのが解決策です。
図解
flowchart TB
subgraph Before["密結合"]
A1["OrderService"] -->|直接 new| B1["EmailSender(具体)"]
end
subgraph After["疎結合"]
A2["OrderService"] --> I["INotifier(抽象)"]
I --> B2["EmailSender"]
I --> B3["SmsSender(差し替え可)"]
end
Before -->|抽象に依存| After
サンプル
// ✅ OrderService は「具体」ではなく「抽象(INotifier)」に依存する
INotifier notifier = new EmailSender(); // 実装を外から注入(DI)
var service = new OrderService(notifier);
service.Complete(); // 注文完了 → メールで通知
// SmsSender に差し替えても OrderService は無変更
service = new OrderService(new SmsSender());
service.Complete(); // 注文完了 → SMSで通知
interface INotifier { void Notify(string message); }
class EmailSender : INotifier
{
public void Notify(string message) => Console.WriteLine($"{message} → メールで通知");
}
class SmsSender : INotifier
{
public void Notify(string message) => Console.WriteLine($"{message} → SMSで通知");
}
class OrderService
{
private readonly INotifier _notifier; // 抽象に依存
public OrderService(INotifier notifier) => _notifier = notifier; // 外から注入
public void Complete() => _notifier.Notify("注文完了");
}
- 上位ロジックは具体クラスでなく**インターフェース(抽象)**に依存する
- 実装は外から渡す(依存性注入 / DI)ことで差し替え可能になる
- テスト時はモック実装を注入できる(TDDの章で活用する)
演習
var repo = new InMemoryRepository();
var app = new UserService(repo);
app.Register("Taro");
Console.WriteLine(repo.LastSaved); // Taro
// TODO: IRepository(Save(string))に依存する UserService を定義してください。
// Register(name) で _repo.Save(name) を呼びます
interface IRepository { void Save(string name); }
class InMemoryRepository : IRepository
{
public string LastSaved = "";
public void Save(string name) => LastSaved = name;
}
___
- 期待される出力:
Taro
ヒント1を見る
コンストラクタでIRepositoryを受け取り、フィールドに保持します
ヒント2を見る
class UserService { private readonly IRepository _repo; public UserService(IRepository repo) => _repo = repo; public void Register(string name) => _repo.Save(name); }
まとめ
- 依存性逆転の原則:具体ではなく抽象に依存する
- 実装は外から注入(DI)し、差し替え可能にする
- テスト容易性・拡張性が大きく向上する(次章以降の設計の土台)
次章: 型と処理を抽象化する「ジェネリクスとデリゲート」へ進みます。