de:code 2019 で登壇してきました。
ここでは、そこでの登壇内容のうち設計フェーズの内容についてまとめて書きたいと思います。登壇資料は SlideShare に上がっています。
https://www.slideshare.net/okazuki0130/xamarinforms-147881590
設計フェーズで考えること
まず、このセッションはトラックオーナーのちゃっくさんこと井上 章さん(@chack441)に Enterprise Application Patterns using Xamarin.Forms eBook のような内容を話してくれと言われたのが始まりでした。
なので、これを読んで、この中で触れられているサンプルの eShopContainers のサンプルを読んで自分ならこうかなぁというのを伝えようと考えてセッション内容をくみたてました。
そこで何故わざわざこんな単純に見るとイチイチめんどくさいと思えるようなクラスの分割、インターフェースに対するプログラミング、DIなどの定番パターンが何故使われているのだろう?ということをわかりやすく伝えるということを念頭にお話しをしました。
まず、アプリケーションを作るということは変化との闘いであるという共通認識を全員に対して共有します。
このスライドを書いていて "天の声" の部分でメテオフォール型開発のことを思い出しました。ラグナロクのフェーズまで進むと、ここで話してることをすべてしてても焼け石に水な気もしますがラグナロクが起きないことを祈りながらプロジェクトを進めていく、もしくは利害関係者には可能な限り合意をとっておくということで、リスクを減らすことをしていかないといけないですが、そこはまた別のお話し。(神は突然やってくるから神なので、根回ししてもダメなのかも?)
ということで、天の声を置いておいてもアプリケーションの仕様は変わっていきます。
あの機能いらない。あの機能ちょっと変えてほしいんだけど…などなど。ということで、アプリケーションは変化に弱く作るよりは強く作った方がいいよね?ということになります。
では、変化に強いということはどういうことなのか?というのを考えたときに色々なことが考えられますが、とりあえず個人的に一番重要だと思う 2 つのトピックをあげました。
見た目とロジックが混ざってるクリックイベントに数千行のコードが書かれてるコードは、もう何が変化に強い要素なのかわからない状態になると思うので見た目とロジックは少なくとも分離したほうがよいというのは、なんとなく共通認識なのかなと思うのと、密結合なコンポーネントという単語からは、そこはかとなくきな臭い感じがするので疎結合なコンポーネントが、雰囲気変化に強そうな感じを醸し出してて個人的に好きです。
ということで、この 2 つを軸に話を進めていきます。
見た目とロジックの分離
さて、ということで見た目とロジックの分離ですが今回主題の Xamarin.Forms のほかにも WPF や UWP などの XAML 系テクノロジーにおいて見た目とはなんだろう?と考えると XAML ファイルと、それのコードビハインドクラスということになります。
つまり見た目とロジックの分離ということを素直にすると以下のように XAML + コードビハインドとは別の場所にロジックを書くということになります。以下のように。
ただ、ロジック寄りの中身を、そのまま見た目に反映させるとギャップがあったりするのと、XAML 系プラットフォームのデータバインディングの仕組みに乗る場合には、データバインドのソース(Xamarin.Forms では BindingContext, WPF や UWP では DataContext) に何らかのクラスを入れる必要があります。これはロジックか??というと違うので、大体間にもう 1 クラス入ります。
つまり、この形が XAML 系界隈でうまれて今ではモバイルアプリ開発や Web アプリ開発でもよく聞くようになった MVVM パターンということになります。下のようなイメージですね。
ということで、XAML 系プラットフォームで変化に強いアプリケーションを作ろうとして見た目とロジックの分離を進めていくと、ここまで説明したような流れで MVVM パターンに行きつきます。
なので見た目とロジックの分離がしたい場合には MVVM をすると XAML 系プラットフォームでは、割とすぽっといい感じにはまります。
疎結合なコンポーネント
見た目とロジックの分離が出来たので変化に強いアプリケーションだ!!やったね!!
ということで見た目とロジックの分離がされたので以下のように見た目だけ差し替えるということや、見た目に対して変更が入ってもロジックには無影響で対応できる可能性が非常に高くなりました。
設計の話だけだとイメージがつきにくいので、少しコードを見てみましょう。とある ViewModel のコードで、Model のクラスを使っている様子です。
ViewModel から Model を使うので ViewModel のクラスで Model のクラスを new して使ってます。普通ですね。分割したとしても、しょせんは 1 つのアプリケーションです。最終的には、くっついて動きます。つまりこういうことです。
まぁ、そうですね。じゃぁこの状態で何が起きるか見てみましょう。例えば以下のようにクラス間で依存関係があるようなもので考えてみます。
そして、ある ViewModel のクラスが出来たのでテストしてみたいと思います。そうすると、モデルの実装をがっちりつかんでるので当然テストするにはモデルのクラスの実装が必要になります。おそらく一般的なプログラムだとモデルの中で別のクラスに依存しているということが起きていて、一般的に依存をたどっていくと最終的には Web API を呼んでいたりデータベースにアクセスしていたりといったことが起きます。
悲しいことに ViewModel のクラスをテストしようとすると Web API の先まで完成していないとテストが出来ません。同じプロジェクトで作ってるにしても、サーバー側担当の人に進捗どうですか?とか色々聞いて調整しないといけなかったり、さらに最悪なことに自分たちではどうしようも出来ない外部のものだったりしたときには、どうすればいいんだろう?ということになります。
もちろん同じ Web API を呼ぶクラスに依存してる別のクラスでも同じことがおきますね。
なので、結局は最終的に画面がちゃんと出来上がって全部作って通しでテストしてみるという感じになります。
これは恐らく多くの人が体験していることではないかと思います。イケイケの設計を目指したけど、なんか分割したぶん管理が大変だとか分散してわかりにくいというレイヤーわけしてしまったことによるデメリットが勝っているような状態になります。
じゃぁ、これに対してどういう解決策があるの?ということで「インターフェースに対してプログラミングをする」と「基本的にクラス内で別のクラスを new しない」というのがあります。
つまり、以下のようにクラスの実装に対して依存関係を持たせずにインターフェースのみに依存関係を持たせるということです。
これを実現するには、どういう風にコードを書けばいいのか?ということになりますが、これはインターフェースを外部から受け取るようにして、クラス内では受け取ったインターフェースを使って処理をするということになります。以下のような感じに。
こういうのが依存性注入(Dependency Injection)や制御の反転(Inversion of Control)と言ったりします。
ここまでの流れを見ると、あぁ外から依存関係入れてもらうのね!とか、あぁ自分で制御するんじゃなくて外から制御してもらうってことで制御の反転なのかなぁっていうイメージがわくと思います。依存性注入という単語だけ見て、なるほど!こういうことか!って私はなりませんでした。
この依存性注入をする前と、した後でどういう違いがあるのか?どんないいことがあるのか?という点についてですが、これは、もうシンプルにクラス単体でテストが出来るようになる!ということに尽きます。インターフェースにしか依存してないので、適当なテスト用のインターフェースを渡してやれば動かしてテスト出来ます。素敵。こんな感じで。
これで見た目とロジックの分離が出来て、なおかつ分離した部品単位でのテストによって、その部分のロジックに集中したテストが出来るので全部繋いでやる打検テストよりも、密度の高いテストが出来るようになりました!!これで勝つる…という感じですけど、これはこれでまだめんどくさいことが残ってます。
部品単位でバラバラな疎結合な状態にしても、最終的には 1 つのアプリケーションになるので、どこかでバラバラにした部品をくみたてないといけません。
分離したほうがいい依存関係はインターフェースをコンストラクターなどで受け取るように作っているので実際には上記のような単純な形ではなく、以下のように複数のクラスの依存関係のツリーをくみたてないといけません。
上記の例では全部 new してますが、例えばアプリでオンメモリキャッシュしてるようなクラスを考えると毎回 new してるとキャッシュよさようならみたいな感じになりますよね。static な領域にキャッシュ残しててもいいけど、それはそれでキャッシュを複数管理したいとか色々考えだすとめんどくさそう。ということでキャッシュクラスはシングルトンでとりあえず管理したいんだ!ってなったり色々考えだすと、さらにめんどくささが倍増していきます。
そう、分離して作った部品を皆が正しく組み立てて使ってくれないと、それはそれでバグの温床になったりしますし、何よりもめんどくさいです。なので、世の中にはオブジェクトの組み立てを専門にやってくれるライブラリーがあったりします。
そういうのを DI コンテナーと呼んでたりします。
DI コンテナーに、このインターフェースが要求されたら、この実装クラスのインスタンスを渡して!!このクラスのインスタンスのライフサイクルは毎回新しいのを使って!とかシングルトンで管理して!といったように、ルールを定義しておくと、そのルールに従ってよしなにクラスのインスタンスをくみたててくれます。
色々な DI コンテナーのライブラリーがありますが大体以下のような使用感です。
組み立てルールの登録は一度どこかでやっておけばいいので、全員がルールを細部まで把握しなくてよくなり、とても便利です。
MVVM パターンで DI コンテナーを使う時に、何処のインスタンスまで DI コンテナーで管理するのか?といったことを考えないといけません。Model だけ?とか ViewModel と Model ?とか、View と ViewModel と Model 全部?とか。
世間一般を見渡してみると、大体のサンプルやライブラリーが ViewModel まで DI コンテナーでインスタンス管理をしていて、View の BindingContext や DataContext に設定するインスタンスを DI コンテナーから取得するといった形で作られています。
例えば eShopContainers のサンプルも ViewModelLocator というクラス内で ViewModel と Model のインスタンスを DI コンテナーに登録していて、View のクラス名から自動的に ViewModel のクラスを予測して、DI コンテナーから取り出して View に設定するといったことをやっています。
その他にも Prism for Xamarin.Forms というライブラリーでも、MVVM Light Toolkit でも同じように ViewModel のインスタンスを DI コンテナーから取得するといったアプローチがとられています。
ここまでやって、やっと MVVM パターンを適用して見た目とロジックを分離しつつ、現実的なめんどくささで、個別の部品単位のテストが出来る。さらにはインターフェースさえ守ってれば別の部品にさくっと差し替えるといったことが出来るようになりました。
こんな感じで作っておくと便利です。例えば私は、昔にデモ用アプリを WPF で作ってるときに、こんな感じで作っていて、終盤になって「やっぱり、この機能不安定だからデモでは使わないから消しといて」と言われた時に、DI コンテナーへの該当処理を 1 行コメントアウトすることで対応完了ということがありました。
まぁ、運が悪いとそれだけで終わるといった綺麗に解決できることは、なかなか無いのですが、そうなる可能性を上げておくというのを現実的な労力で行えるのでやっておいて損はないです。綺麗にすぱっと変更が適用できなくても密結合なものよりは楽なはず…!
ということで基本的な MVVM を適用した場合のお話しでした。
こんな機能も欲しい
実際にアプリケーションを作ってると、こんな機能欲しいな…と思うことがあります。
全体の面倒を見る人は、ここらへんくらいの仕組みは追加で用意してあげると、幸せになれます。
さて、MVVM はわかったけど Model の中ってどうなるの?といったこともよく聞きます。正直これといった銀の弾丸はないのですが、個人的には Clean Architecture などの有名どころを読んでみて自分たちに必要な部分をピックアップして小さく試してみるというフェーズをやってみて、足りないと思ったものを足すという事をするのがいいと思ってます。
私は大体以下のような形をスタート地点として考えて、こういうレイヤーもあったほうがいいなとか、今回の規模ならここは手を抜いても大丈夫だろうとか考えたりします。(手を抜くと後で後悔することもありますが…)
そして、実際に作るにあたって MVVM を支援するライブラリーの使用を検討してみることを個人的にはお勧めします。
Xamarin.Forms の場合は機能が多いということで Prism for Xamarin.Forms をお勧めします。
実際に使わないにしても、使わない選択をするということは、ライブラリーが提供しているような機能を自分たちで作るという事です。その時に、どんな機能があればいいのか?どういう風に使えるようにすると便利なのか?といった参考にとてもなります。
例えば MVVM パターンでコマンドの活性化、非活性化を特定のプロパティの変更を監視して変更するといったような処理をする場合ですが、プロパティのセッターでコマンドに対して変更通知を行うような処理を書いたりします。
private DelegateCommand _fooCommand; public DelegateCommand FooCommand { get { if (_fooCommand != null) { return _fooCommand; } _fooCommand = new DelegateCommand(....); } } private string _someProperty; public string SomeProperty { get => _someProperty; set { SetProperty(ref _someProperty, value); // 値の更新とPropertyChangedイベントは発行してくれるような便利なメソッド FooCommand.RaiseCanExecuteChanged(); // ここでコマンドの活性化・非活性化の再評価を依頼する } }
つまり、コマンドを見ただけでは、このコマンドが何のプロパティに依存して変化するのかということがわかりにくくなります。依存してるプロパティが 1 つならまだいいですが複数になってきたりすると色々めんどくさいです。
Prism の場合はコマンドのクラスに ObservesProperty というメソッドがあって、プロパティを監視して活性化・非活性化をコマンド自身が行うようにする仕組みがあります。とても便利。
private DelegateCommand _fooCommand; public DelegateCommand FooCommand { get { if (_fooCommand != null) { return _fooCommand; } _fooCommand = new DelegateCommand(....) .ObservesProperty(() => SomeProperty); // 活性化・非活性化の状態変更は、このプロパティの値の変更を監視して自動で行う } } private string _someProperty; public string SomeProperty { get => _someProperty; set { SetProperty(ref _someProperty, value); // 値の更新とPropertyChangedイベントは発行してくれるような便利なメソッド } }
といったように、色々普通にやっているとめんどくさい部分などを面倒見てくれる機能もあったりするので、自分でやるときも、こういう機能があると便利なんだなというのを事前に把握しておくためにも既存のライブラリーを使ってみたり、調査してみることはお勧めです。
まとめ
ということでまとめです。見た目とロジックの分離、疎結合なコンポーネントを意識して単体テストしやすいように作りましょう。そしてライブラリーは先人の知恵が詰まってるので是非チェックしてみてください。
ここでやってる施策などは、辛さを減らすためのものです。
逆につらくなるなら何か間違ってたり、楽する方法を見逃してる可能性があるので楽しくない辛い場合には、世間ではどうやってるんだろう?とか強そうな人に聞いてみるのがいいと思います。
今のプロジェクトでは適用できなくても、次や、また次で楽するためにも。ということで、是非楽しいアプリ開発をしてください。
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.