依存性の注入 (DI) の基礎
CoddyのC#ジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 46/70。
Dependency Injection(DI)とは、class 自身が依存関係を作成するのではなく、外部から受け取る技法です。これにより、コードの柔軟性とテスト容易性が向上し、疎結合になります。
DI を使わない場合、クラスは独自の依存関係を作成するため、変更やテストが困難になります。
public class OrderService
{
private EmailSender sender = new EmailSender(); // 密結合
public void PlaceOrder() => sender.Send("Order placed");
}DIでは、依存関係はコンストラクターを通じて渡されます。
public interface IMessageSender
{
void Send(string message);
}
public class EmailSender : IMessageSender
{
public void Send(string message) => Console.WriteLine("Email: " + message);
}
public class OrderService
{
private IMessageSender sender;
public OrderService(IMessageSender sender)
{
this.sender = sender;
}
public void PlaceOrder() => sender.Send("Order placed");
}これで、OrderServiceはメール、SMS、テスト用のモック送信者のいずれを使用しているかを知る必要も、気にする必要もありません。オブジェクトが作成されるときに依存関係が「injected」されます。
IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder(); // メール: 注文が完了しましたこのパターンは、具象実装ではなく抽象化(インターフェース)に依存するという原則に従います。これにより、それを使用するclassを変更せずに実装を入れ替えられるため、単体テストの実施や柔軟なアーキテクチャの維持に不可欠です。
チャレンジ
簡単依存性注入の力を示すロギングシステムを構築しましょう。ログの書き込み方法をハードコーディングする代わりに、具体的な仕組みを知らなくても、コンソール、ファイルのシミュレーション、その他あらゆるログ出力先で動作できる柔軟な Logger class を作成します。
コードを3つのファイルに分けて整理します。
ILogWriter.cs:Loggingnamespace にILogWriterという名前の interface をDefineします。この interface は、あらゆるログ出力先に共通する契約を表し、メッセージがどこに書き込まれたかを説明する string を返す単一の methodWrite(string message)を持つ必要があります。LogWriters.cs:Loggingnamespace にILogWriterの2つの具体的な実装をCreateします。ConsoleLogWriter- itsWritemethod はConsole: {message}を返しますFileLogWriter- constructor を通じて filename を受け取り、itsWritemethod はFile[{filename}]: {message}を返します
Logger.cs:Loggingnamespace に、constructor injection を通じて dependency を受け取るLoggerclass をCreateします。Loggerは次のようにします。- constructor を通じて
ILogWriterを受け取り、private field に格納する - 注入された writer に処理をdelegatesし、結果を返す
Log(string message)method をhaveする
- constructor を通じて
Program.cs:異なる2つの logger(1つはConsoleLogWriter、もう1つはFileLogWriter)を作成して dependency injection を実演します。どの dependency が注入されるかによって、同じLoggerclass の動作がどのように変わるかを示します。
2つの入力を受け取ります。
- file logger 用の filename
- ログに記録する message
ConsoleLogWriter を使用して Logger をCreateし、message をログに記録します。次に、FileLogWriter を使用して別の Logger をCreateし(入力の filename を使用)、同じ message をログに記録します。それぞれの結果を独自の行にPrintします。
たとえば、入力が app.log と System started の場合、出力は次のようになります。
Console: System started
File[app.log]: System startedLogger class 自体は決して変更されないことに注目してください。あらゆる ILogWriter 実装で動作します。これが dependency injection の本質です。Logger は抽象(interface)に依存し、具体的な実装は外部から "injected" されます。後から Logger class をまったく変更せずに、DatabaseLogWriter や CloudLogWriter を簡単にAddできます。
自分で試してみよう
using System;
using Logging;
class Program
{
public static void Main(string[] args)
{
// 入力を読み取る
string filename = Console.ReadLine();
string message = Console.ReadLine();
// TODO: ConsoleLogWriterを使用してLoggerを作成し、メッセージをログに記録する
// 結果を出力する
// TODO: FileLogWriterを使用して(filenameを使用)別のLoggerを作成し、同じメッセージをログに記録する
// 結果を出力する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
5ポリモーフィズムとインターフェース
コンパイル時 vs 実行時ポリモーフィズムインターフェース vs 抽象クラス複数のインターフェース明示的なインターフェースアップキャストとダウンキャストまとめ - 図形計算機8オブジェクト指向の高度な概念
継承よりコンポジションジェネリクス (クラスとメソッド)デリゲートとイベント属性とリフレクションIDisposable と using ステートメント依存性の注入 (DI) の基礎自分で練習してみよう: C#オンラインコンパイラ