依存関係の挿入
Note
この電子ブックは 2017 年春に発行されて以降、改訂されていません。 このブックには今なお価値のある内容が多く含まれていますが、一部の記載内容は古くなっています。
通常、クラス コンストラクターは、オブジェクトをインスタンス化するときに呼び出され、オブジェクトに必要な値はすべて引数としてコンストラクターに渡されます。 これは依存関係の挿入の一例であり、具体的には "コンストラクターの挿入" と呼ばれます。 オブジェクトに必要な依存関係は、コンストラクターに挿入されます。
依存関係をインターフェイス型として指定すると、依存関係の挿入により、これらの型に依存するコードから具象型を分離できます。 通常、インターフェイスと抽象型の登録とそれらの間のマッピングの一覧、およびこれらの型を実装または拡張する具象型を保持するコンテナーが使用されます。
依存関係の挿入には、他にも "プロパティ セッター挿入" や "メソッド呼び出し挿入" などの種類がありますが、あまり一般的ではありません。 このため、この章では、依存関係の挿入コンテナーを使用したコンストラクター挿入の実行にのみ焦点を当てます。
依存関係の挿入の概要
依存関係の挿入は、制御の反転 (IoC) パターンの特殊なバージョンであり、反転される懸念事項は、必要な依存関係を取得するプロセスです。 依存関係の挿入を使用すると、実行時に依存関係をオブジェクトに挿入する役割を別のクラスが担います。 次のコード例は、依存関係の挿入を使用する場合に ProfileViewModel
クラスを構造化する方法を示しています。
public class ProfileViewModel : ViewModelBase
{
private IOrderService _orderService;
public ProfileViewModel(IOrderService orderService)
{
_orderService = orderService;
}
...
}
ProfileViewModel
コンストラクターは、別のクラスによって挿入された IOrderService
インスタンスを引数として受け取ります。 ProfileViewModel
クラスの唯一の依存関係は、インターフェイス型に対するものです。 このため、ProfileViewModel
クラスは、IOrderService
オブジェクトのインスタンス化を担当するクラスを認識しません。 IOrderService
オブジェクトをインスタンス化し、それを ProfileViewModel
クラスに挿入する役割を担うクラスは、"依存関係の挿入コンテナー" と呼ばれます。
依存関係の挿入コンテナーにより、クラス インスタンスをインスタンス化し、コンテナーの構成に基づいてインスタンスの有効期間を管理するための機能が提供され、オブジェクト間の結合が削減されます。 オブジェクトの作成時に、コンテナーで、オブジェクトに必要なすべての依存関係がそこに挿入されます。 これらの依存関係はまだ作成されていない場合、コンテナーでは最初に依存関係を作成して解決します。
Note
依存関係の挿入は、ファクトリを使って手動で実装することもできます。 ただし、コンテナーを使うと、有効期間管理やアセンブリ スキャンによる登録などの機能が追加されます。
依存関係の挿入コンテナーの使用には、いくつかの利点があります。
- コンテナーを使うと、クラスが依存関係を見つけてその有効期間を管理する必要がなくなります。
- コンテナーを使うと、クラスに影響を与えることなく、実装された依存関係をマッピングできます。
- コンテナーを使用すると、依存関係をモックできるようになり、テストの容易性が向上します。
- コンテナーを使用すると、新しいクラスをアプリに簡単に追加できるようになり、保守性が向上します。
MVVM を使う Xamarin.Forms アプリのコンテキストでは、依存関係の挿入コンテナーは、通常、ビュー モデルの登録と解決、サービスの登録とビュー モデルへのそれらの挿入を行うために使われます。
使用できる依存関係の挿入コンテナーが多数あり、eShopOnContainers モバイル アプリでは TinyIoC を使ってアプリ内のビュー モデルとサービス クラスのインスタンス化を管理します。 TinyIoC は、さまざまなコンテナーを評価した後に選択されており、既知のコンテナーの大部分と比較して、モバイル プラットフォーム上で優れたパフォーマンスを発揮します。 疎結合アプリの構築を容易にし、依存関係の挿入コンテナーで一般的に見られるすべての機能 (型マッピングの登録、オブジェクトの解決、オブジェクトの有効期間の管理、解決するオブジェクトのコンストラクターへの依存オブジェクトの挿入を行うメソッドなど) が含まれます。 TinyIoC の詳細については、github.com の TinyIoC に関する記事を参照してください。
TinyIoC の TinyIoCContainer
型には依存関係の挿入コンテナーが用意されています。 図 3-1 は、このコンテナーを使うときの依存関係を示しています。これにより、IOrderService
オブジェクトのインスタンスが作成され、それが ProfileViewModel
クラスに挿入されます。
図 3-1: 依存関係の挿入を使う場合の依存関係
実行時、ProfileViewModel
オブジェクトのインスタンスを作成する前に、IOrderService
インターフェイスのどの実装のインスタンスを作成する必要があるかをコンテナーが認識している必要があります。 次が含まれます。
IOrderService
インターフェイスを実装するオブジェクトのインスタンスを作成する方法を決定するコンテナー。 これは "登録" と呼ばれます。IOrderService
インターフェイスとProfileViewModel
オブジェクトを実装するオブジェクトのインスタンスを作成するコンテナー。 これは "解決" と呼ばれます。
最終的に、アプリは ProfileViewModel
オブジェクトの使用を終了するため、ガベージ コレクションで使用できるようになります。 この時点で、他のクラスが同じインスタンスを共有していない場合、ガベージ コレクターは IOrderService
インスタンスを破棄する必要があります。
ヒント
コンテナーに依存しないコードを記述します。 使われている特定の依存関係コンテナーからアプリを分離するために、常にコンテナーに依存しないコードを記述するようにしてください。
登録
依存関係をオブジェクトに挿入するには、最初に依存関係の型をコンテナーに登録する必要があります。 通常、型を登録するには、コンテナーにインターフェイスと、インターフェイスを実装する具象型を渡す必要があります。
コードを使用して型とオブジェクトをコンテナーに登録するには、次の 2 つの方法があります。
- 型またはマッピングをコンテナーに登録します。 必要に応じて、コンテナーにより、指定された型のインスタンスが構築されます。
- コンテナー内の既存のオブジェクトをシングルトンとして登録します。 必要に応じて、コンテナーにより、既存のオブジェクトへの参照が返されます。
ヒント
依存関係の挿入コンテナーがいつでも適しているとは限りません。 依存関係の挿入によって複雑さが増し、要件が増えるため、小さなアプリには適さない、または役に立たない可能性があります。 クラスに依存関係がない場合、または他の型の依存関係ではない場合、それをコンテナーに格納しても意味がない可能性があります。 さらに、型に不可欠な単一の依存関係セットがあり、変更されることがない場合も、それをコンテナーに格納しても意味がない可能性があります。
依存関係の挿入が必要な型の登録は、アプリ内の 1 つのメソッドで実行する必要があります。また、このメソッドはアプリのライフサイクルの早い段階で呼び出して、アプリがクラス間の依存関係を確実に認識できるようにする必要があります。 eShopOnContainers モバイル アプリでは、これは ViewModelLocator
クラスによって実行されます。これにより、TinyIoCContainer
オブジェクトが構築されます。また、これはそのオブジェクトへの参照を保持するアプリ内の唯一のクラスです。 次のコード例は、eShopOnContainers モバイル アプリが ViewModelLocator
クラスで TinyIoCContainer
オブジェクトを宣言する方法を示しています。
private static TinyIoCContainer _container;
型は ViewModelLocator
コンストラクターに登録されます。 これを実行するには、まず TinyIoCContainer
インスタンスを作成します。この方法を次のコード例に示します。
_container = new TinyIoCContainer();
その後、型は TinyIoCContainer
オブジェクトに登録されます。次のコード例は、型登録の最も一般的な形式を示しています。
_container.Register<IRequestProvider, RequestProvider>();
ここで示す Register
メソッドは、インターフェイスの型を具象型にマップします。 既定では、各インターフェイスの登録はシングルトンとして構成され、すべての依存オブジェクトが同じ共有インスタンスを受け取るようになります。 そのため、コンテナー内には 1 つの RequestProvider
インスタンスのみが存在します。これは、コンストラクターを介した IRequestProvider
の挿入を必要とするオブジェクトによって共有されます。
次のコード例に示すように、インターフェイスの型からのマッピングを使わずに具象型を直接登録することもできます。
_container.Register<ProfileViewModel>();
既定では、各具象クラスの登録はマルチインスタンスとして構成され、すべての依存オブジェクトが新しいインスタンスを受け取るようになります。 そのため、ProfileViewModel
が解決されると、新しいインスタンスが作成され、コンテナーによって必要な依存関係が挿入されます。
解決方法
型は、登録された後、依存関係として解決または挿入できます。 型が解決され、コンテナーで新しいインスタンスを作成する必要がある場合、そのインスタンスに依存関係が挿入されます。
通常、型が解決されると、次の 3 つのいずれかが発生します。
- 型が登録されていない場合、コンテナーによって例外がスローされます。
- 型がシングルトンとして登録されていない場合、コンテナーによってシングルトン インスタンスが返されます。 型が呼び出されるのがこれが初めてである場合、コンテナーでは必要に応じてそれを作成し、それへの参照を保持します。
- 型がシングルトンとして登録されていない場合、コンテナーによって新しいインスタンスが返され、それに対する参照は保持されません。
次のコード例は、以前に TinyIoC に登録された RequestProvider
型を解決する方法を示しています。
var requestProvider = _container.Resolve<IRequestProvider>();
この例では、TinyIoC は、依存関係と共に、IRequestProvider
型の具象型を解決するように求められます。 通常、Resolve
メソッドは、特定の型のインスタンスが必要な場合に呼び出されます。 解決されたオブジェクトの有効期間の制御については、「解決されたオブジェクトの有効期間の管理」を参照してください。
次のコード例は、eShopOnContainers モバイル アプリがビュー モデル型とその依存関係のインスタンスを作成する方法を示しています。
var viewModel = _container.Resolve(viewModelType);
この例で、TinyIoC は、要求されたビュー モデルのビュー モデル型を解決するように求められており、コンテナーは依存関係も解決します。 ProfileViewModel
型を解決する場合、解決する依存関係は ISettingsService
オブジェクトと IOrderService
オブジェクトです。 SettingsService
クラスと OrderService
クラスを登録するときにインターフェイス登録が使われたため、TinyIoC は SettingsService
クラスと OrderService
クラスのシングルトン インスタンスを返してから、それらを ProfileViewModel
クラスのコンストラクターに渡します。 eShopOnContainers モバイル アプリがどのようにビュー モデルを構築してビューに関連付けるかについては、「ビュー モデル ロケーターを使用してビュー モデルを自動的に作成する」を参照してください。
Note
コンテナーで型を登録し、解決すると、パフォーマンス コストが発生します。特に、アプリのページ ナビゲーションごとに依存関係が再構築される場合には、各型を作成するために、コンテナーでリフレクションが使用されるからです。 依存関係が多いか深い場合、作成コストが大幅に増加する可能性があります。
解決されたオブジェクトの有効期間の管理
具象クラス登録を使って型を登録した後、TinyIoC の既定の動作では、型が解決されるたび、または依存関係メカニズムによってインスタンスが他のクラスに挿入されたときに、登録された型の新しいインスタンスが作成されます。 このシナリオでは、コンテナーは解決されたオブジェクトへの参照を保持しません。 ただし、インターフェイス登録を使って型を登録する場合、TinyIoC の既定の動作では、オブジェクトの有効期間がシングルトンとして管理されます。 そのため、インスタンスは、コンテナーがスコープ内にある間はスコープ内に残り、コンテナーがスコープ外になり、ガベージ コレクションが行われたとき、またはコードによって明示的にコンテナーが破棄されたときに破棄されます。
既定の TinyIoC 登録動作は、fluent AsSingleton
および AsMultiInstance
API メソッドを使ってオーバーライドできます。 たとえば、AsSingleton
メソッドを Register
メソッドと共に使うと、コンテナーは Resolve
メソッドを呼び出すときに型のシングルトン インスタンスを作成するか返します。 次のコード例では、LoginViewModel
クラスのシングルトン インスタンスを作成するように TinyIoC に指示しています。
_container.Register<LoginViewModel>().AsSingleton();
初めて LoginViewModel
型が解決されると、コンテナーは新しい LoginViewModel
オブジェクトを作成し、それに対する参照を保持します。 その後、LoginViewModel
が解決されると、コンテナーは以前に作成された LoginViewModel
オブジェクトへの参照を返します。
Note
シングルトンとして登録された型は、コンテナーが破棄されるときに破棄されます。
まとめ
依存関係の挿入を利用すると、具象型を、それらの型に依存するコードから分離できます。 通常、インターフェイスと抽象型の登録とそれらの間のマッピングの一覧、およびこれらの型を実装または拡張する具象型を保持するコンテナーが使用されます。
TinyIoC は、既知のコンテナーの大部分と比較して、モバイル プラットフォーム上で優れたパフォーマンスを発揮する軽量のコンテナーです。 疎結合アプリの構築を容易にし、依存関係の挿入コンテナーで一般的に見られるすべての機能 (型マッピングの登録、オブジェクトの解決、オブジェクトの有効期間の管理、解決するオブジェクトのコンストラクターへの依存オブジェクトの挿入を行うメソッドなど) が含まれます。