はじめに
Xamarinでスマートフォン向けのアプリケーションを開発しているとAndroidとiOSで異なる動作をさせたくなることがあります。当然、プリプロセッサを使用してOS毎に異なる動作をするように書くことはできます。
しかし、その方法ではOS毎に異なる設定とコンパイルが必要であり、同じ場所に複数のOS向けのコードを書くことになりコードが長くなってしまいます。
このような問題に対して、DependencyInjectionという手法を使用すると解決しやすくなります。
DependencyInjectionを用いると複数の環境(OSやセンサー、ネットワーク)等への対応や自動テストがやりやすくなります。
DependencyInjectionの方法は以下のようになります。
差し替えたい動作をMVやVとは異なるクラスに記述して、MVやVでその動作を行う時にクラスを渡すようにします。環境や条件ごとに渡すクラスを変更することで異なる動作を実現します。
例えば、iOSとAndroidで音声入力をしようとします。
OS固有のAPIを使用して1つのソースコードに記述する場合、以下のように書けるでしょう。
しかし、このような書き方だとソースコードが冗長になり、メンテナンス性も低くなってしまいます。
#if ANDROID
xxx... // なんらかの処理
#elif iOS
xxx... // なんらかの処理
#endif
そこでDependencyInjectionを使用します。
例えば、ITextSpeechというインターフェースを用意します。このインターフェースはGetTextというメソッドを持ち、GetTextを使用すると音声からテキストを取得できるとします。
そして、AndroidとiOSでTextSpeech_Android と TextSpeech_iOS というクラスを用意します。GetTextをOS固有のAPIで実装します。
今までと同じようにコンストラクタでクラスのインスタンスを受け取って、使用する時にインスタンスのメソッドを実行します。
ITextSpeech textSpeech;
ModelView(ITextSpeech textSpeech)
{
this.textSpeech = textSpeech;
}
string GetText()
{
return textSpeech.GetText();
}
こうすると、ITextSpeechに渡すクラスを変更すれば、MVの変更なしに複数のOSに対応できます。
OSの違いだけでなく、自動テストでは扱いに困るセンサーや通信も外部から渡すようにするとテストしやすくなります。
そして、このように記述するための方法がPrismには存在します。
それについて説明します。
RegisterTypes
PrismではRegisterTypesを使用してインターフェースとクラスの結びつけをします。
AndroidとiOSでそれぞれクラスを定義します。
class TextSpeech_Android : ITextSpeech { ...
class TextSpeech_iOS : ITextSpeech { ...
iOSではテンプレートを使用すると予め生成されているAppDelegate.cs
でインターフェースとクラスを結びつけます。結びつけるには、IPlatformInitializerのRegisterTypesメソッドの中でcontainer.Registerを使用します。
public class iOSInitializer : IPlatformInitializer
{
public void RegisterTypes(IContainerRegistry container)
{
container.Register<ITextSpeech, TextSpeech_iOS>();
}
}
Androidでは予め生成されているMainActivity.cs
に記述します。
public class AndroidInitializer : IPlatformInitializer
{
public void RegisterTypes(IContainerRegistry container)
{
container.Register<ITextSpeech, TextSpeech_Android>();
}
}
結びつけた後、クラスのコンストラクタにインターフェースを記述すると自動的に取得できるようになります。
ModelView(ITextSpeech textSpeech)
{
XamarinにはDependencyServiceがありますが、これと違って
アプリケーションの起動時に使用するクラスを切り替えれます。
今回は説明を簡単にするために、テンプレートに記述されているクラスを変更しただけで、当然、自分でクラスを記述しても問題ありません。
DependencyServiceで定義するだけでも同じようにコンストラクタの引数で取得できるようになるという資料もありますが、7.0で試したところできませんでした。
最後に
日本語では依存性の注入と呼ばれますが、他の人も指摘しているように英語のWikipediaを見ると、「A dependency is an object that can be used」と記述されており、「使用されるオブジェクトの投入」のような訳の方が正しい気がします。