ViewModel設計の原則 その4


□導入

前回までは比較的ViewModel設計の位置づけ的な説明を行ってきました。今回からはとうとうViewModel設計の方法に関する原則について説明していきます。

今回のブログでは、4~6番目の原則として「あらゆる画面の状態をViewModelでの状態としてモデル化すること」、「イベントではなく、ViewModelの状態によって画面を制御すること」、「いつでもViewを再構成できるだけの情報をViewModelに保持させること」をそれぞれ順番に説明します。

なお、ここでの説明がどのプロジェクトにも適用可能な正しい内容であるかは分からないことをあらかじめご承知おきください。ここでの内容をそれぞれのプロジェクトで使用するのはもちろん構いませんが、すべて自己責任でお願いします。



□ViewModel設計ではオブジェクト指向モデリングの知識が生かせる

ViewModel設計の特徴は、一般的なモデリングの多くがビジネスドメインを対象としているのに対して、そのモデリング対象が画面自体であるということです。

実のところモデリング対象が画面自体であるということを除けば、他のオブジェクト指向モデリングと大きく異なる点はそれほどなく、ビジネスドメインやその他のクラス設計でのオブジェクト指向設計の開発経験をほぼそのまま生かすことができます。ある程度の設計経験者であれば、若干のコツをつかむだけでViewModel設計を行えるようになるようです。

リッチなUIでは複雑な画面状態や振る舞い、そしてオブジェクト間の協調が必要とされます。このためむしろ、ビジネスドメインの設計よりも、カプセル化、継承、ポリモーフィズムといったオブジェクト指向の技術をフルに生かすことができる場でもあります。

オブジェクト指向の技術はViewModel設計のための基礎であり、とても重要ではありますが、本ブログでの範疇を超える内容ですので、その説明については割愛します。既に多くの良書(※1)が存在しますので、詳細はそちらをご覧ください。

それでは、ViewModel設計の原則について見ていきましょう。

※1:ViewModel設計で役立つ良書としてひとつだけ紹介すると、「実践UML」が挙げられます。この中で紹介されているGRASPパターンは、実際のプロジェクトでうまく適用できる状況というのは少ないと思うのですが、面白いことにViewModelの設計では参考になります。


□MVVMではViewModelを通して画面をコントロールする

ある画面、特にリッチなUIを思い浮かべたとき、画面の中にはあらゆる要素が含まれます。具体的には、ラベルやテキストボックスといったUIコントロール、表示データ、入力データ、表示ページ、タブ、ダイアログ、そしてフォント、色彩、アニメーションといったものです。そしてこれらが、ユーザの操作やその他によって変化しながら全体として画面が動作します。

MVVMでは、こうした刻々と変化する、画面のあらゆる状態を満たすようにViewModelを設計します。そしてさらにいえば、フォントや色彩といったデザインの部分は切り捨て、プレゼンテーションロジックとも呼ばれる画面の本質のみがViewModelに含まれるようにします。

この画面の本質であるViewModelから、フォントや色彩を含む実際の画面を構築するのはViewの役割となります。この意味で、Viewは「ViewModelから画面を生成する変換器」のようなものともいえます(具体的にはデータバインドや、データテンプレート、データトリガ―などで実現します)。

さらにViewは、アプリケーションの実行時にViewModelの状態を常に監視しているため、ViewModelへの変更は即座に画面へと同期します。

こうしてMVVMでは、ViewModelのプロパティや関連の変更すれば、画面をコントロールすることができます。このため些末なことに煩わされずに画面の本質に対してプログラミングできるのです。


□[原則4]あらゆる画面の状態をViewModelでの状態としてモデル化すること

このようにMVVMでは、画面の本質であるViewModelに対してプログラミングすることによって画面をコントロールできます。しかし逆に言えば、ViewModelを通してしか画面をコントロールすることが(基本的には)できないということでもあります。ですから、MVVMではあらゆる画面の状態をViewModelの状態としてモデル化することが求められます。

実は画面の状態は、とりわけViewModelのデータ構造(プロパティや関連)によって定義されます。つまり、「あらゆる画面の状態を満たすように、ViewModelのプロパティや関連といったデータ構造が設計されなければならない」ということになります。

ちなみに、処理を司るViewModelのメソッド(より正確にはコマンド)内には画面の状態というものは存在しません。ViewModelのメソッド は、ある画面の状態を別の画面の状態に変更、あるいは遷移させる役割を持ちます。

それでは、具体的にどういったものがViewModelのプロパティや関連となるのかについて、以下にいくつかの例を列挙します。

<ViewModelのプロパティ>
・画面表示項目の内容(ラベルの内容など)
・画面入力項目の内容(テキストボックスの内容など)
・画面項目のEnable,Disable,ReadOnly
・画面項目を強調するかどうかのフラグ

<ViewModelの関連(関連の先にViewModelのオブジェクトがある)>
・子供のページやタブViewModel
・子供の画面部品ViewModel
・他のViewModelへの参照

ViewがUIコントロール、フォント、色彩、アニメーションなどを保持し、ViewModelではプレゼンテーションロジックへと抽象化された分だけ、シンプルになっていることが読み取れると思います。また、ViewModelは関連を通して子供のViewModelを保持したり、他のViewModelを参照したりすることで、全体としてグラフ構造を構築することもあることに注意してください。

そのほか、 例えば郵便番号、都道府県、市区町村、番地などのプロパティを持った住所ViewModelのような、共通的な画面部品ともなるようなViewModelを関連に保持するようなケースもあります。

ところで、あくまで上記のものは代表的な例だけであることにご注意ください。これ以外のものであっても理由があれば追加することはまったく問題ありません。何を画面の状態としてモデル化するかは、ViewModelの設計者が画面の本質にとって何が重要と考えるかに依るのです。

[原則4]あらゆる画面の状態をViewModelの状態としてモデル化すること


□[原則5]イベントではなく、ViewModelの状態によって画面を制御すること

MVVMでは、画面の状態がViewModelの状態としてモデル化されます。このため、画面の状態を変えるためには、ViewModelの状態を変更すればよいのでした。

具体的には、MVVMのアプリケーションがボタン押下などのイベントを受けると、以下のような順番をとおして画面に反映されます。

1.ボタンが押下される。
2.ボタン押下に対応したViewModelのメソッド(正確にはコマンド)が呼び出される
3.(必要に応じて、ViewModelのメソッド内で、Modelへのデータ変更や、データ取得が行われる)
4.ViewModelのメソッド内で、ViewModelの状態(プロパティや関連)が変更される
5.Viewの機能(データバインド、データテンプレート、データトリガ―など)によって、ViewModelの状態変更に対応して画面の状態が変更される

ここで重要なのは、4.にあるように、ViewModelのメソッドでは、ViewModelの状態(プロパティや関連)を変更するまでしか行なわないことです。必ずViewModelの状態を通して画面の状態を変更するようにします。

MVVMの学びたての時には、ViewModelの状態変更を通さずに、イベント発生後のViewModelのメソッドから直接画面(つまりView)を変更する方法はないのかと、そればかり試みようとしてしまうようです。しかし、これは多くの場合には誤りです。

実はViewModelの状態変更を通さずに、ViewModelのメソッドから画面を直接操作することは、メッセンジャーと呼ばれる方法やその他の方法を使用すれば不可能ではありません。しかしそれでは、ViewModelからその画面の状態を把握する方法がなくなってしまいます。そしてその画面の状態を制御できなくなってしまうのです。

つまり、ViewModelのメソッド内では、ViewModelのプロパティや関連を変更するだけであり、Viewを直接変更するようなことは基本的に行いません。ViewModelの状態から実際の画面の生成を行い、その状態を同期し続けるのはViewの役割となります。

[原則5]イベントではなく、ViewModelの状態によって画面を制御すること


□[原則6]いつでもViewを再構成できるだけの情報をViewModelに保持させること

原則として、「あらゆる画面の状態をViewModelでの状態としてモデル化すること」と、「イベントではなく、ViewModelの状態によって画面を制御すること」をみてきました。そして、少し前に説明したように、ViewはViewModelから画面を生成する変換器の役割を持つにすぎません。

これらのことを合わせて考えてみると実は、Viewがいついかなるタイミングでなくなってしまったとしても、「View再構成できるだけの十分な情報をViewModelに保持させ、常に同期させておく」必要があることが分かります。

これは原則というよりも、折に触れて振り返るべきテストとでもいうべきものです。ViewModelのモデル化の際に、これを自問自答することで、とある画面の状態をViewModelのプロパティや関連として定義しておくべきなのかどうかの判断基準となります。
 
なお、Viewが仮になくなってしまっても再構成する必要がない状態もあるでしょう。

例えば、カーソルに応じて表示されるツールチップの表示・非表示の状態や、ちょっとしたデザインのためのアニメーション実行中の画面の状態(揮発性の状態)であれば、Viewとしてあらためて再構成したいなどと思わない状態ですし、それをViewModelで制御したいとも思わないでしょう。それは、それだけ些細で重要ではない画面の状態だということです。そうしたものをViewModelの状態として定義する必要はないのです。

画面の状態として制御したいものをViewModelでの状態として定義してください。また、ViewModelの状態として定義を行わないことで、それは些細で重要ではない画面の状態であるということを表現してください。

[原則6]いつでもViewを再構成できるだけの情報をViewModelに保持させること


□まとめ

今回のブログでは、ViewModelのデータ構造(プロパティや関連)によって画面の状態をモデル化し、ViewModelのメソッドではViewModelのデータ構造に対して変更を行うことで画面を制御するということを説明しました。

そして、「いつでもViewを再構成できるだけの情報がViewModelに保持されているかどうか」という、ViewModel設計の妥当性をチェックする簡単なテストについても説明しました。

実はこれらの原則は、言うは易く行うは難しです。これらの原則に「完全に」沿うことは、実際にやってみるとメンドクサイですし、場合によっては実現が困難なこともあります。
  
私自身、ひとつのアプリケーションの中でも、重要な箇所では、可能な限り上記の原則に沿うように開発を行いますが(場合により自作のビヘイビアを作成することもあります)、それ以外の部分ではコストとメリットを天秤にかけて、原則に沿わないで開発することもしばしばです。ですから、あまりにも厳格に上記の原則を守る必要がないことについては覚えておいてください。

なお、こうしたことが、MVVMのライブラリやフレームワークの成熟によって解決されるのか、そもそもMVVMの完全な適用など目指すべきものではなく、コストとメリットを天秤にかけて、部分部分にMVVMを適用していくのが適切なのかは、私自身まだ判断できていません。

私が今回紹介した原則は、MVVMが目指すべき理想にすぎないともいえます。しかし、目指すべき原則があることで、何を例外として回避し、何が解決すべき課題であるのかがハッキリと見えるようになるはずです。

MVVMでの開発に困難がないわけでもありませんが、あまりストイックに考えずに、時にはMVVMの課題をさらりと避けて、楽しいMVVMライフをお過ごしください。

長文お疲れ様でした。

以上。

人気の投稿