原文「Managing Dependencies Between Components Using the Prism Library for WPF」
Prismライブラリに配置されるアプリケーションは、潜在的に、多くの疎く結合した型とサービスから構成されている、複合アプリケーションです。それらは、内容を提供するために相互作用する必要があります。そして、ユーザーアクションに基づく通知を受けます。それらが、疎く結合しているため、それらは、必要なビジネス機能を提供するために、互いに相互作用と情報交換するための方法が必要です。これら、さまざまな部分を互いに結びつけるために、Prismライブラリに配置されるアプリケーションは、依存関係注入コンテナに依存しています。
依存関係注入コンテナは、クラスのインスタンスのインスタンスを生成するための能力を提供することによって、オブジェクトの間で依存関係の結合を減少させます。そして、コンテナの設定に基づいて、それらの寿命を管理します。オブジェクトの作成時に、コンテナは、オブジェクトがその中に必要な、どんな依存関係でも注入します。それらの依存関係が、まだ作成されていない場合、コンテナは、はじめに、それらの依存関係を作成し、解決します。場合によっては、コンテナそのものは、依存関係として解決されます。例えば、コンテナとして、Unityアプリケーション・ブロック(Unity)を使用するとき、モジュールが、コンテナを注入するため、それらは、そのコンテナで、それらのビューとサービスを登録することができます。
コンテナを使用することのいくつかの利点があります。:
- コンテナは、その依存関係を配置するための、それらの寿命を管理するための、コンポーネントの必要性を取り払います。
- コンテナは、コンポーネントに影響を及ぼすことなく、実装された依存関係の交換を提供します。
- コンテナは、モックアップが作成された依存関係を提供することで、検査を容易にします。
- コンテナは、システムに簡単に追加される、新しいコンポーネントを提供することで、保守性を向上します。
Prismライブラリに基づくアプリケーションのコンテクストでは、コンテナには、特有の利点があります。
- それが、読み込まれるとき、コンテナは、モジュール依存関係を、モジュールに注入します。
- コンテナは、登録とView ModelとViewを解決するために、使用されます。
- コンテナは、View Modelを作成し、ビューを注入することができます。
- コンテナは、領域マネージャとイベント・アグリゲータのような、構成サービスを注入します。
- コンテナは、モジュール固有の機能をもつサービスのモジュール固有のサービスを登録するために使用されます。
備考: Prismの手引きのいくつかのサンプルは、コンテナとして、Unityアプリケーション・ブロック(Unity)に依存しています。その他のコードサンプル、例えば、モジュール方式クイックスタートのために、Managed Add-in Framework(MAF)を使用します。そのものPrismライブラリは、コンテナ固有でありません。そして、あなたは、Castle Windsor、StructureMapとSpring.NETのような、そのサービスと他のコンテナによるパターンを使用することができます。
重要な決定:依存関係注入コンテナを選択する
Key Decision: Choosing a Dependency Injection Container
Prism Libraryは、依存関係注入コンテナのために、UnityやMEFの2つのオプションを提供します。: Prismは拡張可能なため、少し操作で、他のコンテナを代わりに使用できます。 UnityとMEFは、依存関係注入のために、同じ基本的な機能を提供するにもかかわらず、それらは極めて異なる動作をします。 両方のコンテナで提供されるいくつかの機能には、次のものが含まれています。:
- それら両方は、コンテナを持つ型を登録します。
- それら両方は、コンテナでインスタンスを登録します。
- それら両方は、命令的に、登録された型のインスタンスを作成します。
- それら両方は、コンストラクタに登録された型のインスタンスを注入します。
- それら両方は、プロパティに登録された型のインスタンスを注入します。
- それら両方は、管理する必要がある型と依存関係を記録するための、宣言型の属性を持ちます。
- それら両方は、オブジェクト・グラフ内の依存関係を解決します。
Unityは、MEFにはない、いくつかの機能を提供します。:
- それは、登録なしで、具体的な型を解決します。
- それは、開いたジェネリックを解決します。
- それは、呼び出すオブジェクトを捕えるため、ターゲットオブジェクトに追加の機能を追加するために、横取りしています。
MEFは、Unityにはない、いくつかの機能を提供します。:
- それは、ディレクトリ内のアセンブリを検出します。
- それは、XAPファイルのダウンロードとアセンブリの検出を使用しています。
- それは、新しい型が発見されるように、プロパティとコレクションを再構成します。
- それは、自動的に、派生型をエクスポートします。
- それは、.NET Frameworkで配布されます。
コンテナは、機能に違いがあり、異なる動作をしますが、Prismライブラリは、どちらのコンテナでも動作し、同じような機能を提供するでしょう。どのコンテナを使用するべきか、考えるとき、あなたの筋書きに、うまく収まる、先程の機能と発見を覚えておいてください。
コンテナを使用するための注意事項
Considerations for Using the Container
あなたは、コンテナを使用する前に、次の事を考慮する必要があります。:
- あなたの筋書きで、コンテナとインスタンスを解決することで、登録の効率に影響を受け入れることができるか考えてみてください。例えば、あなたが、レンダリング・メソッドのローカル・スコープ内で、表面を描画するために、10,000ポリゴンを作成する必要がある場合、それぞれの実体を作成するのために、リフレクションのコンテナが使用するため、コンテナを通して、それらのポリゴンのインスタンスを全て解像するコストは、かなりの処理能力のコストがかかるかもしれません。
- 多くのあるいは深い依存関係がある場合、作成コストが、大幅に増加する可能性があります。
- コンポーネントが、少しの依存関係も備えていない、あるいは、他の種類の依存関係ではない場合、それは、それをコンテナに入れる意味がないかもしれません。
- コンポーネントが、型が必要で、決して変更されない1つの依存関係の設定している場合、それは、それをコンテナに入れる意味がないかもしれません。
コンポーネントの寿命が、シングルトン、あるいは、インスタンスとして登録する必要があるか考えてみてください。:
- コンポーネントが、グローバル・サービスの場合、それは、一つのリソースのためのリソースマネージャとして機能します。ログ記録サービスのような、シングルトンとして、それを登録するといいかもしれません。
- コンポーネントが、複数の利用者に共用状態を提供する場合、シングルトンとして、それを登録するといいかもしれません。
- 注入されたオブジェクトが、依存したオブジェクトが必要となるたびに、それを注入した、新しいインスタンスを持っておく必要がある場合、非シングルトンとして、それを登録します。例えば、それぞれのビューは、おそらく、View Modelの新しいインスタンスを必要とします。
あなたが、コードや設定を通して、コンテナを設定したいかどうかについて考えてみてください。:
- あなたが、すべての異なるサービスを一元的に管理したい場合、設定を通してコンテナを設定します。
- あなたが、条件つきで特定のサービスを登録したい場合、コードを通してコンテナを設定します。
- あなたが、モジュール・レベルのサービスを使用している場合、コードを通じてコンテナを設定することについて考えてみてください。それで、それらのサービスは、モジュールが読み込まれる場合だけ、登録されています。
備考:MEFのような、いくつかのコンテナでは、設定ファイルで設定することはできません。そして、コードによって設定する必要があります。
中心となる筋書き
Core Scenarios
コンテナは、すなわち、登録と解決すること、2つの主要な目的のために使用されています。
登録
Registering
あなたが、オブジェクトに依存関係を注入する前に、依存関係の種類は、コンテナで登録されている必要があります。一般的に種類を登録することは、そのインターフェイスを実装しているコンテナにインターフェースと具体的な種類を渡すことが含まれています。登録する種類とオブジェクトのために、コードを通して、または、設定を通しての主に2つの手段があります。:具体的な手段は、コンテナからコンテナに変化します。
通常、コードを通してコンテナに登録する種類とオブジェクトの2つの方法があります。:
- あなたは、コンテナで種類やマッピングを登録することができます。適切なタイミングで、コンテナは、あなたが指定する種類のインスタンスを構築します。
- あなたは、シングルトンとして、コンテナで既存のオブジェクト・インスタンスを登録することができます。コンテナは、既存のオブジェクトに参照を返します。
Unityコンテナによる型の登録
Registering Types with the Unity Container
初期化の間、型は、ビューとサービスのような、他の型を登録することができます。登録は、コンテナを通して、それらの依存関係を提供し、そして、それらは、他の型からアクセスできます。これを実行するには、型は、コンテナをモジュール・コンストラクタに注入しておく必要があります。次のコードは、どのように、CommandingクイックスタートのOrderModule型が形式を登録するかを示します。
// OrderModule.cs
public class OrderModule : IModule
{
public void Initialize()
{
this.container.RegisterType<IOrdersRepository, OrdersRepository>(new ContainerControlledLifetimeManager());
...
}
...
}
あなたが、どのコンテナを使用するかに応じて、また、登録は、設定を通して、コードの外側で、実行されることができます。この例については、以下で確かめて下さい。
メモ: 設定と比較して、コードの中で登録する利点は、モジュールが読み込まれる場合にだけ、登録が発生するところにあります。
MEFに型を登録する
Registering Types with MEF
MEFは、コンテナと一緒に、登録する型のために、属性に基づくシステムを使用します。その結果、コンテナに、型の登録を追加することは、単純です:それは、次のコードの例で示す型へ[Export] 属性の追加が必要です。
[Export(typeof(ILoggerFacade))]
public class CallbackLogger: ILoggerFacade
{
}
MEFを使用するときの他のオプションは、クラスのインスタンスを作成することです。そして、その特定のインスタンスをコンテナに登録します。ここに、示されるように、MEFクイックスタートによるモジュラー性のQuickStartBootstrapperは、ConfigureContainerメソッドで、この例を示します。:
protected override void ConfigureContainer()
{
base.ConfigureContainer();
// Because we created the CallbackLogger and it needs to
// be used immediately, we compose it to satisfy any imports it has.
// 私たちは、すぐに使用される必要がある、CallbackLoggerを作成するため、
// 私たちは、それがもつ、あらゆるもののインポートを満足させるために、それを構成します。
this.Container.ComposeExportedValue(this.callbackLogger);
}
メモ: あなたのコンテナとして、MEFを使用するとき、あなたが、型を登録するために、属性を使用することをお勧めします。
解決
Resolving
型が登録されたあと、それは、依存関係として解決、あるいは、注入されることができます。型が解決されている、そして、コンテナが、新しいインスタンスを作成する必要があるとき、それは、これらのインスタンスに依存関係を注入します。
一般に、型が解決されたとき、3つのうち、1つが起こります。:
- 型が登録されなかった場合、コンテナは、例外を投げます。
備考:Unityが含まれているいくつかのコンテナでは、あなたが、登録されなかった、具体的な型を解決することができます。
- 型が、シングルトンとして登録された場合、コンテナは、シングルトン・インスタンスを返します。初めて型が呼び出された場合、コンテナは、それを作成し、そして、今後の呼び出しのために、それを保ち続けます。
- 型が、シングルトンとして登録されなかった場合、コンテナは、新しいインスタンスを返します。
備考:既定では、MEFで登録される型は、シングルトンで、そして、コンテナは、オブジェクトに参照を格納します。Unityでは、オブジェクトの新しいインスタンスは、デフォルトで返され、そして、コンテナは、オブジェクトに参照を保持しません。
Unityでインスタンスを解決する
Resolving Instances with Unity
Commanding QuickStartのための次のコード例は、対応する領域に関連付けるためのコンテナから、OrdersEditorViewとOrdersToolBarのビューをどこで解決するかを紹介します。
// OrderModule.cs
public class OrderModule : IModule
{
public void Initialize()
{
this.container.RegisterType<IOrdersRepository, OrdersRepository>(new ContainerControlledLifetimeManager());
// Show the Orders Editor view in the shell's main region.
// シェルの主な領域内の、Orders Editorビューを表示する
this.regionManager.RegisterViewWithRegion("MainRegion",
() => this.container.Resolve<OrdersEditorView>());
// Show the Orders Toolbar view in the shell's toolbar region.
// シェルのツールバー領域内の、Orders Toolbarビューを表示する
this.regionManager.RegisterViewWithRegion("GlobalCommandsRegion",
() => this.container.Resolve<OrdersToolBar>());
}
...
}
OrdersEditorViewModelコンストラクタには、解決されるとき、注入される、次に示す依存関係(注文リポジトリと注文コマンド・プロキシ)が含まれています。
// OrdersEditorViewModel.cs
public OrdersEditorViewModel(IOrdersRepository ordersRepository, OrdersCommandProxy commandProxy)
{
this.ordersRepository = ordersRepository;
this.commandProxy = commandProxy;
// Create dummy order data.
// ダミー注文データを作成します。
this.PopulateOrders();
// Initialize a CollectionView for the underlying Orders collection.
// 基盤となるOrdersコレクションのためのCollectionViewを初期化します。
this.Orders = new ListCollectionView( _orders );
// Track the current selection.
// 現在の選択した内容を追跡します。
this.Orders.CurrentChanged += SelectedOrderChanged;
this.Orders.MoveCurrentTo(null);
}
前述のコードで示される、コンストラクタ注入に加えて、また、Unityは、プロパティ注入のための提供します。オブジェクトが解決されるとき、[Dependency] 属性を適用する、どんなプロパティも、自動的に解決され、注入されます。
MEFでインスタンスを解決する
Resolving Instances with MEF
次に示すコードの例は、モジュラー性のMEFクイックスタート内のブートストラッパーが、どのように、シェルのインスタンスを得るかを示します。具体的な型を要求する代わりに、コードは、インターフェースのインスタンスを要求することができました。
protected override DependencyObject CreateShell()
{
return this.Container.GetExportedValue<Shell>();
}
MEFで、解決されるどんなクラスでも、次のコードの例で示すように、注入されたILoggerFacadeとIModuleTrackerを持っている、MEFクイックスタートのモジュラー性のModuleAから、また、あなたは、コンストラクタ注入を使用することができます。
[ImportingConstructor]
public ModuleA(ILoggerFacade logger, IModuleTracker moduleTracker)
{
if (logger == null)
{
throw new ArgumentNullException("logger");
}
if (moduleTracker == null)
{
throw new ArgumentNullException("moduleTracker");
}
this.logger = logger;
this.moduleTracker = moduleTracker;
this.moduleTracker.RecordModuleConstructed(WellKnownModuleNames.ModuleA);
}
他のオプションは、注入されたILoggerFacadeのインスタンスを持っている、MEFクイックスタートとモジュラー性から、ModuleTrackerクラスで示すように、プロパティ注入を使用することです。
[Export(typeof(IModuleTracker))]
public class ModuleTracker : IModuleTracker
{
[Import] private ILoggerFacade Logger;
}
Prismで依存関係注入コンテナとサービスを使用する
Using Dependency Injection Containers and Services in Prism
多くの場合、適切な「コンテナ」として参照される、依存関係注入コンテナは、コンポーネントの間で、依存関係を満たすために使用されます。; 一般的に、これらの依存関係を満足させることは、登録と解決が含まれています。Prismライブラリは、Unityコンテナのための、そして、MEFのためのサポートを提供しますが、それは、コンテナ固有でありません。ライブラリが、IServiceLocatorインターフェースにより、コンテナにアクセスするため、 コンテナは、置き換えることができます。これを実行するには、あなたのコンテナは、IServiceLocatorインターフェースを実装している必要があります。通常、あなたが、コンテナを置き換えている場合、また、あなたは、あなた独自のコンテナ固有のブートストラッパーを提供する必要があります。IServiceLocatorインターフェースは、Common Service Locator Libraryで定義されます。これは、依存関係注入コンテナやサービス・ロケーターのような、IoC(制御の反転)コンテナの上に抽象化を提供するための、オープンソースの取り組みです。このライブラリを使用する目的は、特定の実装に結びつけずに、IoCとサービスの場所を活用することです。
Prismライブラリは、UnityServiceLocatorAdapterとMefServiceLocatorAdapterを提供します。両方のアダプタは、ServiceLocatorImplBase型を拡張することによって、ISeviceLocatorインターフェースを実装します。次に示す図は、クラス階層を示しています。
Prismライブラリは、特定のコンテナを参照したり、依存したりしませんが、アプリケーションが、特定のコンテナに頼ることが典型的です。 これは、特定のアプリケーションが、コンテナを参照することが、合理的なことを示しています。しかし、Prismライブラリは、直接、コンテナを参照しません。例えば、株トレーダーRIといくつかのクイックスタートは、Prismは、Unityを頼るコンテナとして、含まれています。他のサンプルとクイックスタートは、MEFをあてにしています。
IServiceLocator
IServiceLocator
次に示すコードは、IServiceLocatorインターフェースを示しています。
public interface IServiceLocator : IServiceProvider
{
object GetInstance(Type serviceType);
object GetInstance(Type serviceType, string key);
IEnumerable<object> GetAllInstances(Type serviceType);
TService GetInstance<TService>();
TService GetInstance<TService>(string key);
IEnumerable<TService> GetAllInstances<TService>();
}
サービス・ロケーターは、次のコードで拡張メソッドを示し、Prismライブラリで拡張されます。あなたは、IServiceLocatorを解決するために、一つだけ使用されることを確かめることができます。インスタンスを取得するために、使用されることを示しています。;それは登録には、使用されません。
// ServiceLocatorExtensions
public static class ServiceLocatorExtensions
{
public static object TryResolve(this IServiceLocator locator, Type type)
{
try
{
return locator.GetInstance(type);
}
catch (ActivationException)
{
return null;
}
}
public static T TryResolve<T>(this IServiceLocator locator) where T: class
{
return locator.TryResolve(typeof(T)) as T;
}
}
UnityコンテナがサポートしないTryResolve拡張メソッドは、登録されている場合、解決されている型のインスタンスを返します。;それ以外の場合には、それはnullを返します。
次のコードの例に示すように、ModuleInitializerは、モジュールを読み込む間、モジュールを解決するために、IServiceLocatorを使用します。
// ModuleInitializer.cs - Initialize()
IModule moduleInstance = null;
try
{
moduleInstance = this.CreateModule(moduleInfo);
moduleInstance.Initialize();
}
...
// ModuleInitializer.cs - CreateModule()
protected virtual IModule CreateModule(string typeName)
{
Type moduleType = Type.GetType(typeName);
if (moduleType == null)
{
throw new ModuleInitializeException(string.Format(CultureInfo.CurrentCulture, Properties.Resources.FailedToGetType, typeName));
}
return (IModule)this.serviceLocator.GetInstance(moduleType);
}
IServiceLocatorを使用するための注意事項
Considerations for Using IServiceLocator
IServiceLocatorは、汎用コンテナであることを示すものではありません。コンテナは、使用の異なる意味を持っています。それは、多くの場合、そのコンテナが選ばれる理由のために、決定を駆動します。この考えを持ち込み、株トレーダーRIは、IServiceLocatorを使用する代わりに、依存関係注入コンテナ・ディレクトリを使用します。これは、あなたのアプリケーションの開発のために推薦される方法です。
次の状況では、あなたは、IServiceLocatorを使用するために、適切な場合があります。:
あなたは、複数のコンテナをサポートする必要がある、サードパーティー・サービスを設計している、独立したソフトウェア・ベンダー(ISV)です。
あなたは、複数のコンテナを使用する場所で、組織で使用するサービスを設計しています。
詳細情報
More Information
コンテナに関連する情報については、次を参照してください。:
- Unity Application Block on MSDN.
- Unity community site on CodePlex.
- Managed Extensibility Framework Overview on MSDN.
- MEF community site on CodePlex.
- Inversion of Control containers and the Dependency Injection pattern on Martin Fowler's website.
- Design Patterns: Dependency Injection in MSDN Magazine.
- Loosen Up: Tame Your Software Dependencies for More Flexible Apps in MSDN Magazine.
- Castle Project
- StructureMap
- Spring.NET