nuits.jp blog

C#, Xamarin, WPFを中心に書いています。Microsoft MVP for Development Technologies。

Prism for Xamarin.Forms入門 コラム01:PrismとDIコンテナ

さて、前回はPrism.FormsのHello, Worldの記事を書かせていただきました。

nuits.hatenadiary.jp

今回以降は、いよいよ個々のエッセンスを掘り下げて行きたかったのですが...どの話をするにしても、「なぜその機能が必要なのか?」を説明しようとすると、どうしてもDIコンテナやSoCの理解無く説明するのは困難です。

特に、Xamarinで開発するという事は、マルチプラットフォームで開発するという事がほぼ必然的に含まれてきます。
また、スマートデバイス開発を行うということは、位置情報やセンサー、カメラといった一般的にテストが難しい要素が多くなりがちです。
そういった関係上、Xamarinで開発するといった場合、関心事を分離し、それぞれのクラス間を疎結合に保つという事は、通常以上に重要なことになります。

そこで今回は、少し実装から離れて、DIコンテナとは何か?SoCとは何か? そしてなぜそれらが重要なのか?を理解する手助けをさせていただきたいと思います。

そんなん聞くまでもないよって、強者の方々は読み飛ばしていただいて結構です。
逆に、DIやSoCがどういうもので、なぜ必要なのか?にピンと来ない点がある方にはぜひ目を通していただきたいです。

なお本エントリーは連載記事「Prism for Xamarin.Forms入門」の一部となっております。
以下に目次がありますので、他のエントリーもご覧いただけると嬉しいです。 【Xamarin】Prism.Forms入門 目次 - nuits.jp blog

というわけで、本編に入りたいと思います。

はじめに

さて、アプリケーションを開発する際、少しでも開発経験のある方は
「アプリケーションは小さく分割して実装したほうがよい」
という事は、なんとなく理解されているかと思います。

これは古くから共有されている考え方で、堅苦しく言うと「関心(事)の分離」と言います。
英語で言えば「Separation of Concerns:SoC」ですね。
分離したい「関心」が何なのか?によってSoCを実現する手段は様々です。

皆さんもご存じのMVVMパターンもPresentation Domain Separation(PDS)という、プレゼンテーションという「関心」をドメインから切り離す(逆でもいいですが)具体的な手段の一つです。
プレゼンテーションというのは、プラットフォームへの依存度が非常に高いレイヤーで、求められる専門性もプレゼンテーション以外とは大きく異なりますし、テストも非常に難しい領域です。
このため、MVVMに限らずMVCなどのプレゼンテーションという「関心」を分離する手段が広く重宝がられてきたわけです。

ところで、Xamarinでアプリケーションを開発しようとした場合

  • 専門性が高い領域
  • テストが難しい領域

っていっぱいありますよね?

  • 個々のプラットフォーム依存の領域
  • 位置情報(の移動による影響テストとか)
  • 時間
  • 非同期処理
  • プッシュ通知
  • 加速度やカメラなどのセンサー類

などなど。
Xamarinがというのではなく、クロスプラットフォームやモバイル開発しようというのは、通常以上に「関心の分離」が重要な領域なのです。
これらの「関心事」を分離する手段が、レイヤーアーキテクチャだったり、MVVMパターンだったりするわけです。

そして実のところ、もう一つ大切なことがあります。
それは、「分離された関心の結合度を低く保つこと」つまり疎結合です。

「関心」を分離しただけで、「関心」間が密に結合した状態だと、結局のところテスタビリティも上がりませんし、専門性のある人材を適材適所に配置することも難しいですし、再利用性も高まりません。

「関心の分離」と「疎結合」はセットで満たす必要があるわけです。

なお、本エントリーはあくまでDIとか「疎結合」を保つことが主題なので、どう「関心」を分離するか?というお話し(つまりMVVMパターンとかレイヤーモデルとか)は、またの機会に譲りたいと思います。
またがあるかは、保証しかねますw

疎結合とDependency Injection(DI)

さて、さらっと「疎結合を保つべし!」と言いましたが、そもそも疎結合ってなんやねん?って話を少ししたいと思います。
疎結合を説明するために、まずは

  • 「関心」は分離されているけど、「関心」同士が密結合の状態

を説明したいと思います。
また、疎結合がどういう状態かも明確に定義しておいたほうが良いでしょう。

  • 分離された「関心(実装クラス)」間が、直接依存関係を持たない

という事にしましょう。

説明にあたって、ここでは、現在の位置情報をもとに周辺の店舗をDBから検索するH○tPepper的なアプリを例に考えます。
商標とかよくわからんし、怖いのでHatPepperにしときますw
以下のクラス図を見てください。

f:id:nuitsjp:20160812162830p:plain

位置情報の提供するGeolocationServiceクラスがあって、それを利用するHatPepperクラスがあります。
HatPepperクラスからは、GeolocationServiceのインスタンス化と呼び出しが行われます。

コードで書くとこんな感じです。

それではこれを徐々に疎結合にしていきましょう。
疎結合=インターフェース!ってことでインターフェースを導入します。

f:id:nuitsjp:20160812162914p:plain

コードで書くとこんな感じ。

全然疎結合になってないですねw
たしかに利用個所はInterfaceの利用に置き換わりましたが、直接実装クラスをインスタンス化しています。
これではHatPepperクラスを実装する際に、GeolocationServiceクラスが必要になってしまいますので、例えば作業分担して平行に開発するとかできません。
また、仮に先にGeolocationServiceクラスを使ってから、HatPepperクラスを作るとした場合、どうやって移動したときのテストをしたらいいのでしょうか?
まさかPC担いで山手線でグルグルしながら開発するわけにもいきませんし。。。

疎結合の状態は以下のように定義しましたよね?

  • 分離された「関心(実装クラス)」間が、直接依存関係を持たない

HatPepperクラスは、IGeolocationServiceだけ知っていれば良いという形が「疎結合」であると言える状態のはずです。

ではどうしたらいいのでしょうか?
そこでDipendency Injectionですよ!(某エヴァンジェリストさん風
ちなみに似たような目的を達成する方法に、Service Locatorパターンという別解もありますが、今回は説明を割愛いたします。

以下がDipendency Injection Container を適用したケースのクラス図です。

f:id:nuitsjp:20160812163007p:plain

そしてこちらがソースコードの例です。

GeolocationServiceクラスのインスタンス化をDIコンテナにお任せし、HatPepperクラスのコンストラクタ呼び出し時に、引数としてDIコンテナから渡してもらいます。
これによってHatPepperクラスはGeolocationServiceクラスから完全に自由になり、疎結合が達成されました。

こうすることで、HatPepperの実装者は、欲しい座標を返すIGeolocationServiceをHatPepperクラスに渡すことで、実際には仕事場に引きこもったまま、世界各地に移動した場合のテストケースを書けるようになるわけです。
やったね!

勿論位置情報だけでなく、プラットフォームの依存性や、時間や通知なんかの「関心」も分離してあげることで、それらを利用するクラスも容易に実装・テストが行えるようになる。。。というわけです。

PrismとDIコンテナ

さて、いよいよ本題に到達しました。。。が、その前にちょっとだけ。
先ほどの疎結合のモデルですが
「HatPepper使いたい場合、どうやってHatPepperクラスのインスタンス生成したらいいの?」
という課題が積み残されたままです。

どうしたらいいのでしょうか?
もうお気づきですよね?
HatPepperクラスを使うクラスも、もちろんDIコンテナからHatPepperクラスをInjection(注入)してもらって利用するわけです。
そしてそのクラスも。。。
どこまで続くのか??

そう、ここでViewModelLocatorが登場するわけです。
長かったあ。

前回のエントリーで
「ViewにViewModelを結びつけるのはViewModelLocatorを利用しましょう。
大したことじゃないように思えますが、重要なことです。」
と、記載した理由がここにあるわけです。

PrismではViewの一番上からModelの一番下まで、各クラス間の結合は一貫してDIコンテナにお任せする方針になっています。(実際には「ゲームじゃないUnity」などを使っているわけですが)
下図のようなイメージです。

f:id:nuitsjp:20160812163114p:plain

そして起点となるViewへViewModelLocatorがViewModelをインジェクションする意味がここにあるわけです。
こう言うところが、前々回のエントリーでお話しした

だからこそ、「Guidance」であり、「Patterns & Practices」であり、「Testable & Maintainable」なのです。

という点につながっているのを、ご理解いただけたでしょうか?
しかもこれが使わないより、使った方が楽なレベルで実現できてしまいます。(Prism難しくないよ!簡単ですよ!)
実際に仕事でも使っていますが、今年配属された新人君も何の疑問も不自由もなく使いこなせています。

こういう部分が私がPrismのファンとなった要因の一つだと思っています。

というわけで、長くなりましたが今日はここまでにして、次回はもう少し具体的な実装の話に移っていきたいと思います。
あ~つかれた。
そして今回はマサカリが怖い。。。w

それではまた!