C# コンソール アプリケーションでの Autofac の正しい使用

静電気が問題

コンソール プログラムの主な問題は、メインの Program クラスはほとんど静的です。これは単体テストには適しておらず、IoC にも適していません。たとえば、静的クラスは構築されないため、コンストラクター注入の可能性はありません。その結果、 new を使用することになります メイン コード ベースで、または IoC コンテナーからインスタンスをプルします。これは、パターンに違反しています (その時点ではサービス ロケーター パターンに近いです)。コードをインスタンス メソッドに入れるという慣習に戻ることで、この混乱から抜け出すことができます。これは、何かのオブジェクト インスタンスが必要であることを意味します。しかし、何か?

2 クラス パターン

コンソール アプリを作成するときは、特定の軽量パターンに従います。私にとって非常にうまく機能するこのパターンに従うことを歓迎します。

このパターンには 2 つのクラスが含まれます:

<オール>
  • 元の Program クラスは静的で、非常に簡潔で、コード カバレッジから除外されています。このクラスは、O/S 呼び出しから適切なアプリケーションの呼び出しまでの「パススルー」として機能します。
  • インスタンス化された Application クラスは完全に注入され、単体テスト可能です。これは、実際のコードが存在する場所です。
  • プログラム クラス

    O/S には Main が必要です エントリ ポイントであり、静的である必要があります。 Program クラスはこの要件を満たすためだけに存在します。

    静的プログラムをきれいに保ちます。これには、(1) コンポジション ルートと、(2) 実際のアプリケーション (後で説明するようにインスタンス化されます) を呼び出す単純な「パススルー」エントリ ポイントが含まれている必要があります。

    Program のコードはありません これは単体テストに値するものです。オブジェクト グラフを作成し (テスト中はいずれにしても異なります)、アプリケーションのメイン エントリ ポイントを呼び出すだけだからです。また、単体テストが不可能なコードを隔離することで、クラス全体をコード カバレッジから除外できるようになりました (ExcludeFromCodeCoverageAttribute を使用)。

    以下に例を示します:

    [ExcludeFromCodeCoverage]
    static class Program
    {
        private static IContainer CompositionRoot()
        {
            var builder = new ContainerBuilder();
            builder.RegisterType<Application>();
            builder.RegisterType<EmployeeService>().As<IEmployeeService>();
            builder.RegisterType<PrintService>().As<IPrintService>();
            return builder.Build();
        }
    
        public static void Main()  //Main entry point
        {
            CompositionRoot().Resolve<Application>().Run();
        }
    }
    

    ご覧のとおり、非常にシンプルです。

    アプリケーション クラス

    Application を実装します ワン・アンド・オンリー・プログラムのようなクラス。インスタンス化されているため、通常のパターンで依存関係を注入できるのは今だけです。

    class Application
    {
        protected readonly IEmployeeService _employeeService;
        protected readonly IPrintService _printService;
    
        public Application(IEmployeeService employeeService, IPrintService printService)
        {
            _employeeService = employeeService; //Injected
            _printService = printService; //Injected
        }
    
        public void Run()
        {
            var employee = _employeeService.GetEmployee();
            _printService.Print(employee);
        }
    }
    

    このアプローチは、懸念の分離を維持し、あまりにも多くの静的な「もの」を回避し、あまり気にせずに IC パターンに従うことができます。お気づきでしょう -- 私のコード例には new のインスタンスが 1 つも含まれていません。 ただし、ContainerBuilder をインスタンス化する場合を除きます。

    依存関係に独自の依存関係がある場合はどうなりますか?

    このパターンに従うため、PrintService の場合 または EmployeeService 独自の依存関係があるため、コンテナーがすべてを処理します。コンポジション ルートの適切なインターフェイスにサービスを登録する限り、これらのサービスを挿入するためのコードをインスタンス化したり、コードを記述したりする必要はありません。

    class EmployeeService : IEmployeeService
    {
        protected readonly IPrintService _printService;
    
        public EmployeeService(IPrintService printService)
        {
            _printService = printService; //injected
        }
    
        public void Print(Employee employee)
        {
            _printService.Print(employee.ToString());
        }
    }
    

    この方法では、コンテナーがすべてを処理し、コードを記述する必要はなく、型とインターフェイスを登録するだけです。