Menu
Coddy logo textTech

依存性の注入 (DI) の基礎

CoddyのC#ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 46/70。

依存性の注入 (Dependency Injection: DI) は、クラスが自身で依存関係を作成するのではなく、外部からそれらを受け取る手法です。これにより、コードはより柔軟で、テスト可能になり、疎結合になります。

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、またはテスト用のモック送信者のどれを使用しているかを関知しません。依存関係は、オブジェクトの作成時に「注入」されます:

IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder();  // 電子メール: 注文が完了しました

このパターンは、具象的な実装ではなく抽象(インターフェース)に依存するという原則に従います。これにより、それらを使用するクラスを変更することなく実装を入れ替えることが可能になり、ユニットテストや柔軟なアーキテクチャの維持に不可欠です。

challenge icon

チャレンジ

簡単

依存関係の注入(Dependency Injection)の威力を示すロギングシステムを構築しましょう。ログの書き込み方法をハードコーディングする代わりに、コンソール、ファイルシミュレーション、あるいはその他の出力先であっても、その詳細を知ることなく動作する柔軟な Logger クラスを作成します。

コードを以下の3つのファイルに整理します:

  • ILogWriter.cs: Logging 名前空間に ILogWriter というインターフェースを定義します。このインターフェースは、あらゆるログ出力先のコントラクト(契約)を表し、メッセージがどこに書き込まれたかを説明する文字列を返す単一のメソッド Write(string message) を持つ必要があります。
  • LogWriters.cs: Logging 名前空間に ILogWriter の2つの具体的な実装を作成します:
    • ConsoleLogWriter - その Write メソッドは "Console: " + message を返します。
    • FileLogWriter - コンストラクタを通じてファイル名を受け取り、その Write メソッドは "File[" + filename + "]: " + message を返します。
  • Logger.cs: コンストラクタ注入(Constructor Injection)を通じて依存関係を受け取る Logger クラスを Logging 名前空間に作成します。Logger は以下の条件を満たす必要があります:
    • コンストラクタを通じて ILogWriter を受け取り、それをプライベートフィールドに保存する。
    • 注入されたライターに処理を委譲し、その結果を返す Log(string message) メソッドを持つ。
  • Program.cs: ConsoleLogWriter を持つロガーと FileLogWriter を持つロガーの2つを作成することで、依存関係の注入を実演します。注入される依存関係によって、同じ Logger クラスがどのように異なる動作をするかを示してください。

2つの入力を受け取ります:

  • ファイルロガー用のファイル名
  • ログに記録するメッセージ

ConsoleLogWriter を使用して Logger を作成し、メッセージをログに記録します。次に、FileLogWriter(入力されたファイル名を使用)を使用して別の Logger を作成し、同じメッセージをログに記録します。それぞれの結果を個別の行に出力してください。

例えば、入力が app.logSystem started の場合、出力は以下のようになります:

Console: System started
File[app.log]: System started

Logger クラスが一切変更されていないことに注目してください。これはあらゆる ILogWriter の実装で動作します。これが依存関係の注入の本質です。Logger は抽象(インターフェース)に依存しており、具体的な実装は外部から「注入」されます。後で Logger クラスを全く修正することなく、DatabaseLogWriterCloudLogWriter を簡単に追加することができます!

チートシート

依存性の注入(DI)は、クラスが自身で依存関係を作成するのではなく、外部からそれらを受け取る手法であり、コードをより柔軟でテスト可能にし、疎結合にします。

DIなし(密結合):

public class OrderService
{
    private EmailSender sender = new EmailSender();  // Tightly coupled
    
    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");
}

依存関係の注入:

IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder();  // Email: Order placed

DIは、具体的な実装ではなく抽象(インターフェース)に依存するという原則に従っており、それを利用するクラスを修正することなく実装を入れ替えることを可能にします。

自分で試してみよう

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 を作成し、同じメッセージをログに記録する
        // 結果を出力する
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン