web-dev-qa-db-ja.com

IoC / DI / Castle Windsorの(誤)理解に何が欠けていますか?

それでは、Castle Windsorを使用するときに動作するWeb APIのIoCとDIを理解する方法を説明します。

ただし、私が理解すべきであるという私の自信は、1対1のバスケットボールゲームでデニスロッドマンを最高にできるという自信と、彼の正気への自信との間のどこかにあることに注意してください。

別の言い方をすれば、過去数日間、IoC/DI/Castle WindsorをWeb APIで読んで実験してきたと言いますが、それでも、「9を除いてすべて落ち込んでいます。もう一方の路地にいる」

これ のように、Stack Overflowについてさらに具体的な質問がいくつかありますが、まだ明確ではありません。

それで、私は「大声で話します」、そして誰かが私の理解を洗練する何かで応答し、この不透明な(私にとって)主題にいくつかの光を当てるか、またはこのゴーディアンの結び目を解明するのを助けることを願っています。

ポイント:IoC/DIの基礎は、コンストラクターにインターフェイスを渡すことです。そのため、コンストラクターを持つクラスは、独自のオブジェクトをインスタンス化する必要がありません。実際、具体的なコンポーネント/クラスを知る必要はありません。

ポイント:これを設定するには、コントローラーに次のようなコードが必要です。

private readonly IDepartmentRepository _deptsRepository;

public DepartmentsController(IDepartmentRepository deptsRepository)
{
    if (deptsRepository == null)
    {
        throw new ArgumentNullException("deptsRepository is null");
    }
    _deptsRepository = deptsRepository;
}

ポイント:IDepartmentRepositoryを実装するいくつかのクラスは、DepartmentsControllerコンストラクターに渡されます

ポイント:コーダーはそれを明示的に実行しません-IOCコンテナー(この場合、Castle Windsor)は、コードと同様に、Web APIルーティングメカニズムを独自のメカニズムでインターセプトすることによって「自動的に」実行しますこのように、global.asax.csで:

GlobalConfiguration.Configuration.Services.Replace(
                typeof(IHttpControllerActivator), new WindsorCompositionRoot(_container));

ポイント:global.asax.csのApplication_Start()はボールを取得し、次のようなコードでインストーラー/レジストラーを呼び出します。

protected void Application_Start()
{            
    AreaRegistration.RegisterAllAreas();

    WebApiConfig.Register(GlobalConfiguration.Configuration);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);

    ConfigureWindsor(GlobalConfiguration.Configuration);
}

public static void ConfigureWindsor(HttpConfiguration configuration)
{
    _container = new WindsorContainer();
    _container.Install(FromAssembly.This());
    _container.Kernel.Resolver.AddSubResolver(new CollectionResolver(_container.Kernel, true));
    var dependencyResolver = new WindsorDependencyResolver(_container);
    configuration.DependencyResolver = dependencyResolver;
}   

ポイント:他の多くのCastle Windsor足場を設定する必要があります。具体的には、クラスを「インストール」する必要があります(登録済み-登録済みの方が、より良い/簡単に読み込める用語だったと思います)。

これは私の主要な「何を言っているのか?」/前向きな領域の1つです。その登録コードが次のようになるかどうかわかりません。

public object GetService(Type serviceType)
{
    return _container.Kernel.HasComponent(serviceType) ? _container.Resolve(serviceType) : null;
}

public IEnumerable<object> GetServices(Type serviceType)
{
    if (!_container.Kernel.HasComponent(serviceType))
    {
        return new object[0];
    }

    return _container.ResolveAll(serviceType).Cast<object>();
}

...またはこのように:

public class ApiControllersInstaller : IWindsorInstaller
{
    public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
    {
        container.Register(Classes.FromThisAssembly() // should it be Types instead of Classes?
         .BasedOn<ApiController>()
         .LifestylePerWebRequest());
    }
}

...またはこのように:

public class ServiceInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
            Component.For<IDeliveryItemRepository>().ImplementedBy<DeliveryItemRepository>().LifestylePerWebRequest(),
            Component.For<IDeliveryRepository>().ImplementedBy<DeliveryRepository>().LifestylePerWebRequest(),
            Component.For<IDepartmentRepository>().ImplementedBy<DepartmentRepository>().LifestylePerWebRequest(),
            Component.For<IExpenseRepository>().ImplementedBy<ExpenseRepository>().LifestylePerWebRequest(),
            Component.For<IInventoryItemRepository>().ImplementedBy<InventoryItemRepository>().LifestylePerWebRequest(),
            Component.For<IInventoryRepository>().ImplementedBy<InventoryRepository>().LifestylePerWebRequest(),
            Component.For<IItemGroupRepository>().ImplementedBy<ItemGroupRepository>().LifestylePerWebRequest());
    }
}

...またはそうでなければ...

ポイント:これがすべてセットアップされ、コントローラー(およびリポジトリー?CWに対して安全にするために、コントローラーに関して必要なことは他にもありますか?)が、Castle Windsorルーティングエンジンによって登録されました。これは、私のWebへの呼び出しです。次のようなAPIアプリ:

http://platypus:28642/api/Departments

... IDepartmentRepositoryを実装する「コンポーネント」(コンクリートクラス)に解決されます。 CWは、IDepartmentRepositoryを実装するクラスが1つある限り、それをIDepartmentRepositoryを実装するクラスの引数をとるコントローラーのコンストラクターにサイレントに渡すクラスであると理解できます。

しかし... IDepartmentRepositoryを実装するNクラスがある場合はどうなりますか? CWは、コントローラーのコンストラクターに渡すものをどのようにして知るのですか?このシリコンとピンクのフワフワしたものの出会いの低い人間である私は、CWにコンストラクターに渡したいNをどのように指定できますか?

6
B. Clay Shannon

私はキャッスルウィンザーではなくNinjectの経験がありますが、原則は同じです。

依存性注入を理解するには、それなしでコードを作成した方法を思い出すと役立ちます。通常、DepartmentsControllerコンストラクターは次のようになります。

public DepartmentsController()
{
    _repository = new DepartmentRepository();
}

コントローラーはリポジトリーに依存し、それをコンストラクターに配置することで、すべてのDepartmentsControllerがリポジトリーでインスタンス化され、有効な状態でそのライフを開始することを保証します。ただし、これにより、後でDepartmentRepositoryのある実装を別の実装に交換することがより困難になります。特に、DepartmentsControllerを分離して個別にテストする場合、問題が発生します。 DepartmentRepositoryを介してデータベースへの実際の呼び出しを呼び出さずにこのクラスをテストするにはどうすればよいですか?

最初の例は、オブジェクト間に「継ぎ目」を作成することで、このシナリオを改善します。特に、テストの場合、2つが接着またはスティッチされ、簡単に分離できるポイントです。 DepartmentRepository自体を作成する代わりに、コントローラーはそれを要求できます。

public object GetService(Type serviceType)
{
    return _container.Kernel.HasComponent(serviceType) ? _container.Resolve(serviceType) : null;
}

これはService Locatorパターンと呼ばれ、悪い習慣だと考える人もいます。この方法でキャッスルウィンザーを公開することで、実際にその利点の一部が失われました。特に、DepartmentsControllerがサービスロケーターとそのGetServiceメソッドに依存するようになりました。サービスロケーターの想定される欠点のすべてを取り上げるわけではありませんが、アプリケーションが大きくなり、この単一のクラスに依存する多数のクラス(潜在的にはすべてのクラス)がある場合に何が起こるかを検討します。

3番目の例では、コンテナをクラスから見えなくすることで、Service Locatorパターンを改善しています。

public DepartmentsController(IDepartmentRepository repository)
{
    if (repository == null)
        throw new ArgumentNullException("repository");

    _repository = repository;
}

このバージョンでは、カーネル/コンテナーまたはサービスロケーターの知識がないことに注意してください。代わりに、それらの依存関係の手段ではなく、コンストラクターでその真の依存関係を明示的にしました。実際、この方法でクラスを記述することで、Castle Windsorを認識しないクラスライブラリ全体を構築でき、コードを変更しなくても、Ninject、別のDIフレームワーク、またはコンテナなしで完全に正常に動作します。

コンポーネントを実際に登録する方法(例2と3の違い)は、部分的にはスケールの問題です。

container.Register(
        Component.For<IDeliveryItemRepository>().ImplementedBy<DeliveryItemRepository>().LifestylePerWebRequest(),
        Component.For<IDeliveryRepository>().ImplementedBy<DeliveryRepository>().LifestylePerWebRequest(),
        Component.For<IDepartmentRepository>().ImplementedBy<DepartmentRepository>().LifestylePerWebRequest(),
...

小さなアプリケーションには問題ありません。クラスの数が増えると、バインドの終わりのないリストを使い果たすか、より良いメソッドを見つける必要があります。改善点の1つは、構成よりも慣例を優先することです。CastleWindorがすべてのIDeliverItemRepositoryDeliveryItemRepositoryの実装があると想定している場合は、上記のバインディングをスキップできます。ただし、ほとんどのクラスで例外的な構成が必要な場合は、何も解決していません。あなたはまだ長く全知のカーネル/コンテナー登録方法を持っています。

代わりに、おそらく2番目の例のようなものを探索する必要があります。このように、ConfigureWindsorメソッドはリフレクションを使用して、特定のインターフェースや実装について知らなくても、アプリケーションに存在するIWindsorInstallersを見つけることができます。すべてのバインディングを、実行時に検出される個別のモジュール(この概念のNinjectの名前)に分割できます。

最後の質問に対処するには、Castle Windsorは、ユーザーに指示しないと、N実装のどれを選択するかを認識しません。これは、主に慣例によるものですが、ライフサイクルや 条件付きバインディング などの重要な詳細を指定する必要がある場合はIWindsorInstallersを使用して、上記で説明した概念を組み合わせることで実現できます。

9
Joshua Dutton

CWは、リフレクションを介してIDepartmentRepositoryを実装するNクラスを認識しています。それはあなたがそれを登録したのであなたが欲しいものを知っています-それが登録のポイントです。それは一種のスマートですが、それはあなたが登録したもののコンストラクターを見て、あなたが持っているものが登録したものに基づいて具体的な型を推測しようとします。したがって、依存関係ツリーのトップを登録するだけで、残りを理解できる場合があります。

あなたの便宜のために(そして混乱のために)登録するには複数の方法があります。利便性(および混乱)のためにFluent APIを使用する。依存関係のライフサイクルに関するものを制御することもできます(依存関係がdispose()を呼び出す必要があるが、依存関係を取得するクラスはそれを作成せず、ライフサイクルについて知ることができないと想像してください-より高度なトピック) 。

必要な具象クラスを登録し、Mainメソッドで解決する必要があります(この場合、Application_Startがこの目的を果たします)。これにより、依存関係グラフにすべてのオブジェクトがすぐに作成されます。完了したら、コンテナのリリースを呼び出します。アプリの他の場所でコンテナを呼び出さないでください。

お役に立てば幸いです-多くの質問がありました。

私は個人的に 。NETでの依存性注入 を参考にしています(私とは関係ありません)が、それは無料ではありません。

4
psr