静電気が問題
コンソール プログラムの主な問題は、メインの 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());
}
}
この方法では、コンテナーがすべてを処理し、コードを記述する必要はなく、型とインターフェイスを登録するだけです。