MVVMのModelにまつわる誤解
こちらに移転してきて初めての記事です。
最近たまに話題になるので書いておきます。MVVMのModelについて誤解されやすい部分のお話です。最近よく議論してるasync/awaitの話とは関係がありません。なおこの話は以下のスライドを理解している事が前提となります。
共有したい理解(ゴール)
- ViewModelはModelの影
- ModelについてViewModelが行うことは、イベントに対する反応と戻り値のないメソッドの呼び出ししかない事
これについての理解を共有できるよう説明していきます。
VIewModelはModelの影
スライドにもしつこく書きましたが、MV○(MVVMやMVC/MVP)のModelは大変分厚くなるし、アプリケーション間で使いまわすことなんてできません。ModelはUIを意識しない??いや、何度も言っていますが、意識はする必要があるんです。ただUI実装の知識が必要ない領域がModelというだけです。これはMVVMに限らずMV○と言われるものすべてで共通のお話です。
(もちろんこのModelというのはDomainModelやDataModelとは何の関連もありません。どちらもMV○のModelは内包しうるだけです)
MVVMにおいてはViewModel同士がお互いを操作する必要なんて基本的にはありません。所有関係はあることはありますがそもそもViewModelはModelの影なのです。そしてまたViewはViewModelの影でもあります。
そもそもModel + ○ + VIewに分ける動機はGUIプラットフォームの知識が必要だったり、GUIプラットフォーム固有の汎用言語に対する制約がある部分(たとえばUI側は単一スレッドからしか触れないとか、DSL使ってるとか)をその他の部分と分離することです。その他の部分こそがModelです。
そう、分離は当然です。しかしViewModelがModelの影に徹して主体的に動かない事には疑問を覚える方もいるかもしれません。しかしそれは当然のことです。ViewModel同士にお互いを操作させてみましょう。こうなります。
何も気にせず言語機能をフルに使えるModelの方で複雑な関係をせっかく表現したのに、さらにその責務を言語機能をフルに使えない制約があるViewModelにも漏らす意味がありません。まったく無駄に複雑化していくだけです。
ViewModelに対するModelのインターフェース
それを踏まえて考えれば、ViewModelに公開するModelのインタフェースは以下の二つしかありません。
- Modelのステートの公開とその変更通知
- Modelの操作のための戻り値のないメソッド
ステートの公開とその変更通知を行うのは簡単な話でしょう。リッチクライアントのModelはステートフルです。そのステートを公開しないとViewModelとViewは表示すべき情報がありません。そしてその変化をViewModelとViewに伝えるために変更通知を行うのも当然の事です。
Modelの操作のためのメソッドには戻り値がない・・これにはひっかかる方も多いかもしれません。しかしこれも難しい話ではありません。
Modelのメソッド呼び出しは何をもたらすのでしょうか?。それはModel内状態の変化(あるいは外部サービス呼び出しとそれに伴うModel内状態の変化)と、なんらかのイベント発生(通信エラー発生とか)しかないのです。ViewModelがModelの影であれば当然それしかないのです。ViewModelがModelを呼び出してModelから戻り値を受け取って何になるんでしょう?それはModel内のステートの不完全な意味のないコピーでしかありません。
したがって異常系も当然ViewModel側でハンドリングされることはありません。想定された例外はModel内のステートの変化をもたらすだけですし、想定されない例外は集約エラーハンドラ経由でModel内の異常通知サービスみたいなものを通って場合によってはそれ用のViewModelを経由し、Viewに表示されるだけです。異常系に対する対処ってもともとGUIプラットフォームの知識必要なく決定してるはずですよね?
ViewModelはModelの影。この考え方が徹底されていれば、
- Modelの呼び出し
- Modelの呼び出し結果によるModel内状態の変化とそれに伴う変更通知イベント
- Modelの呼び出しによる異常系発生が引き起こすModel内異常系通知イベント
- Modelが発生させるその他イベント
これらはみなModelの外からは切り離されて見える別々の事象なのです。Modelの外には異常系として意識しなければならない部分はどこにもなく、すべてはModelのイベントに対する反応があるだけです。
したがってModelのメソッドの呼び出しが戻り値を伴うことはなく、またModelのメソッド呼び出しに伴う例外ハンドリングをViewModelが行うなんてこともありません。