C#で制御の反転を使用する方法

制御の反転と依存性注入の両方により、アプリケーション内のコンポーネント間の依存関係を解消し、アプリケーションのテストと保守を容易にすることができます。ただし、制御の反転と依存性注入は同じではありません。2つの間に微妙な違いがあります。

この記事では、制御パターンの反転を調べ、C#の関連するコード例を使用して依存性注入とどのように異なるかを理解します。

この記事で提供されているコード例を使用するには、システムにVisual Studio2019がインストールされている必要があります。まだコピーをお持ちでない場合は、こちらからVisual Studio2019をダウンロードできます。 

VisualStudioでコンソールアプリケーションプロジェクトを作成します

まず、VisualStudioで.NETCoreコンソールアプリケーションプロジェクトを作成しましょう。Visual Studio 2019がシステムにインストールされていると仮定して、以下に概説する手順に従って、VisualStudioで新しい.NETCoreコンソールアプリケーションプロジェクトを作成します。

  1. Visual StudioIDEを起動します。
  2. 「新しいプロジェクトの作成」をクリックします。
  3. 「新規プロジェクトの作成」ウィンドウで、表示されたテンプレートのリストから「コンソールアプリ(.NETCore)」を選択します。
  4. [次へ]をクリックします。 
  5. 次に表示される[新しいプロジェクトの構成]ウィンドウで、新しいプロジェクトの名前と場所を指定します。
  6. [作成]をクリックします。 

これにより、Visual Studio2019で新しい.NETCoreコンソールアプリケーションプロジェクトが作成されます。このプロジェクトを使用して、この記事の後続のセクションで制御の反転について説明します。

制御の反転とは何ですか?

制御の反転(IoC)は、プログラムの制御フローが反転するデザインパターンです。制御の反転を利用して、アプリケーションのコンポーネントを分離し、依存関係の実装を交換し、依存関係をモックし、アプリケーションをモジュール化してテスト可能にすることができます。

依存性注入は、制御の反転の原理のサブセットです。言い換えれば、依存性注入は、制御の反転を実装する1つの方法にすぎません。たとえば、イベント、デリゲート、テンプレートパターン、ファクトリメソッド、またはサービスロケーターを使用して制御の反転を実装することもできます。

コントロールデザインパターンの反転は、オブジェクトが何らかのアクティビティを実行するために依存するオブジェクトを作成してはならないことを示しています。代わりに、外部のサービスまたはコンテナからそれらのオブジェクトを取得する必要があります。この考え方は、「私たちに電話しないでください。私たちはあなたに電話します」というハリウッドの原則に類似しています。例として、アプリケーションがフレームワーク内のメソッドを呼び出す代わりに、フレームワークはアプリケーションによって提供された実装を呼び出します。 

C#での制御の反転

注文処理アプリケーションを構築していて、ロギングを実装したいとします。簡単にするために、ログターゲットがテキストファイルであると仮定しましょう。ソリューションエクスプローラーウィンドウで作成したコンソールアプリケーションプロジェクトを選択し、ProductService.csとFileLogger.csという名前の2つのファイルを作成します。

    パブリッククラスProductService

    {{

        プライベート読み取り専用FileLogger_fileLogger = new FileLogger();

        public void Log(文字列メッセージ)

        {{

            _fileLogger.Log(message);

        }

    }

    パブリッククラスFileLogger

    {{

        public void Log(文字列メッセージ)

        {{

            Console.WriteLine( "FileLoggerの内部ログメソッド。");

            LogToFile(メッセージ);

        }

        private void LogToFile(string message)

        {{

            Console.WriteLine( "メソッド:LogToFile、テキスト:{0}"、メッセージ);

        }

    }

上記のコードスニペットに示されている実装は正しいですが、制限があります。データをテキストファイルにのみ記録するように制限されています。他のデータソースや別のログターゲットにデータを記録することはできません。

ロギングの柔軟性のない実装

データをデータベーステーブルに記録したい場合はどうなりますか?既存の実装はこれをサポートしないため、実装を変更する必要があります。FileLoggerクラスの実装を変更することも、DatabaseLoggerなどの新しいクラスを作成することもできます。

    パブリッククラスDatabaseLogger

    {{

        public void Log(文字列メッセージ)

        {{

            Console.WriteLine( "DatabaseLoggerの内部ログメソッド。");

            LogToDatabase(メッセージ);

        }

        private void LogToDatabase(string message)

        {{

            Console.WriteLine( "メソッド:LogToDatabase、テキスト:{0}"、メッセージ);

        }

    }

以下のコードスニペットに示すように、ProductServiceクラス内にDatabaseLoggerクラスのインスタンスを作成することもできます。

パブリッククラスProductService

    {{

        プライベート読み取り専用FileLogger_fileLogger = new FileLogger();

        プライベート読み取り専用DatabaseLogger_databaseLogger =

         new DatabaseLogger();

        public void LogToFile(string message)

        {{

            _fileLogger.Log(message);

        }

        public void LogToDatabase(string message)

        {{

            _fileLogger.Log(message);

        }

    }

ただし、これは機能しますが、アプリケーションのデータをEventLogに記録する必要がある場合はどうでしょうか。設計は柔軟ではなく、新しいログターゲットにログを記録する必要があるたびにProductServiceクラスを変更する必要があります。これは面倒なだけでなく、ProductServiceクラスを長期にわたって管理することを非常に困難にします。

インターフェイスで柔軟性を追加 

この問題の解決策は、具象ロガークラスが実装するインターフェースを使用することです。次のコードスニペットは、ILoggerと呼ばれるインターフェイスを示しています。このインターフェイスは、FileLoggerとDatabaseLoggerの2つの具象クラスによって実装されます。

パブリックインターフェイスILogger

{{

    void Log(string message);

}

FileLoggerクラスとDatabaseLoggerクラスの更新バージョンを以下に示します。

パブリッククラスFileLogger:ILogger

    {{

        public void Log(文字列メッセージ)

        {{

            Console.WriteLine( "FileLoggerの内部ログメソッド。");

            LogToFile(メッセージ);

        }

        private void LogToFile(string message)

        {{

            Console.WriteLine( "メソッド:LogToFile、テキスト:{0}"、メッセージ);

        }

    }

パブリッククラスDatabaseLogger:ILogger

    {{

        public void Log(文字列メッセージ)

        {{

            Console.WriteLine( "DatabaseLoggerの内部ログメソッド。");

            LogToDatabase(メッセージ);

        }

        private void LogToDatabase(string message)

        {{

            Console.WriteLine( "メソッド:LogToDatabase、テキスト:{0}"、メッセージ);

        }

    }

これで、必要に応じてILoggerインターフェイスの具体的な実装を使用または変更できます。次のコードスニペットは、Logメソッドが実装されたProductServiceクラスを示しています。

パブリッククラスProductService

    {{

        public void Log(文字列メッセージ)

        {{

            ILoggerロガー= new FileLogger();

            logger.Log(メッセージ);

        }

    }

ここまでは順調ですね。ただし、ProductServiceクラスのLogメソッドでFileLoggerの代わりにDatabaseLoggerを使用する場合はどうなりますか?要件を満たすようにProductServiceクラスのLogメソッドの実装を変更できますが、それでは設計が柔軟になりません。制御の反転と依存性注入を使用して、設計をより柔軟にしましょう。

依存性注入を使用して制御を反転する

次のコードスニペットは、依存性注入を利用して、コンストラクター注入を使用して具象ロガークラスのインスタンスを渡す方法を示しています。

パブリッククラスProductService

    {{

        プライベート読み取り専用ILogger_logger;

        public ProductService(ILogger logger)

        {{

            _logger =ロガー;

        }

        public void Log(文字列メッセージ)

        {{

            _logger.Log(message);

        }

    }

最後に、ILoggerインターフェイスの実装をProductServiceクラスに渡す方法を見てみましょう。次のコードスニペットは、FileLoggerクラスのインスタンスを作成し、コンストラクターインジェクションを使用して依存関係を渡す方法を示しています。

static void Main(string [] args)

{{

    ILoggerロガー= new FileLogger();

    ProductService productService = new ProductService(logger);

    productService.Log( "Hello World!");

}

そうすることで、コントロールを反転しました。ProductServiceクラスは、ILoggerインターフェイスの実装のインスタンスを作成したり、ILoggerインターフェイスのどの実装を使用するかを決定したりする責任を負いません。

制御の反転と依存性注入は、オブジェクトの自動インスタンス化とライフサイクル管理に役立ちます。ASP.NET Coreには、機能の制限されたセットを備えた、制御コンテナーの単純な組み込み反転が含まれています。ニーズが単純な場合はこの組み込みのIoCコンテナーを使用でき、追加機能を活用したい場合はサードパーティのコンテナーを使用できます。

ASP.NET Coreで制御の反転と依存性注入を操作する方法の詳細については、以前の投稿をご覧ください。