導入
クラスの中で new EmailSender() と書くと、その相手にがっちり固定されてしまい、テスト時に差し替えられません。**依存性注入(DI)**は「必要な相手を自分で作らず、外から受け取る」設計です。これが ASP.NET Core などのDIコンテナの土台になります。
図解
flowchart LR
subgraph before["直接 new(密結合)"]
A1["Notifier"] -->|new| B1["EmailSender 固定"]
end
subgraph after["注入(疎結合)"]
A2["Notifier"] -->|コンストラクタで受け取る| I["INotifier 抽象"]
I -.-> B2["Email / SMS / モック"]
end
style after fill:#e8f5e9
サンプル
// 依存を「外から」コンストラクタで受け取る
var service = new OrderService(new ConsoleSender());
service.Complete("A-001"); // [送信] 注文 A-001 が完了しました
interface IMessageSender
{
void Send(string message);
}
class ConsoleSender : IMessageSender
{
public void Send(string message) => Console.WriteLine($"[送信] {message}");
}
class OrderService
{
private readonly IMessageSender _sender; // 抽象に依存
public OrderService(IMessageSender sender) => _sender = sender; // 注入
public void Complete(string id) => _sender.Send($"注文 {id} が完了しました");
}
- クラスは具体クラスでなく**インターフェース(抽象)**に依存する(DIP/SOLIDのD)
- 依存は
newせず、コンストラクタの引数で外から渡す - テストでは
IMessageSenderのモックを渡せば、送信せずに検証できる
やってみよう
ConsoleSender とは別に、送信内容を溜め込むだけの FakeSender : IMessageSender を作って渡してみましょう。OrderService を一切変えずに振る舞いを差し替えられます。
演習
interface IGreeter { string Greet(); }
class JapaneseGreeter : IGreeter { public string Greet() => "こんにちは"; }
// TODO: IGreeter を受け取り、Run() で greeter.Greet() を出力する App を定義してください
___
new App(new JapaneseGreeter()).Run(); // こんにちは
- 期待される出力:
こんにちは
ヒント1を見る
class App { private readonly IGreeter _g; public App(IGreeter g) => _g = g; public void Run() => Console.WriteLine(_g.Greet()); }
ヒント2を見る
依存 IGreeter をコンストラクタで受け取り、フィールドに保持します
まとめ
- DIは「依存を自分で作らず外から受け取る」設計
- 具体でなく抽象(インターフェース)に依存する
- 差し替え・テストが容易になり、DIコンテナの土台になる
次回: データアクセスを抽象化する Repository パターンです。