MVVM パターンでリモート操作の設計を考察

以前から考えていることに、MVVM パターンの ViewModel を使った遠隔操作がある。そもそも MV* パターン(MVVM とか MVC とか)の場合、View を独立させることができるので、View から離れている ViewModel や Model は複数の View に対応していてもよいだろう、という発想がある。Visual Studio のプロジェクトテンプレートだと、ひとつの View に対してひとつの ViewModel が対応するように作られるけど。まあ、それはそれとして。

image

MVVM パターンの View が GUI 以外にあるのだろうか?と考えてみたのだが、そのほかはないだろう。MVC パターンを使って Web API を作ることはよくあるけど、Web API や RESTful 程度に整理されてしまうと、XAML の入れ子の部分とか複雑怪奇なリストが作れるとかの意味がなくなってしまう。データストアとしての XAML と使う以外の場合、データバインドやイベント/Command パターンを使う場合は、もっとランダムにアクターからのイベントが発生するときに XAML の構造が力を発揮する。アクセス方法が簡単あるいは整理されているならば、わざわざ View からイベントを取る必要がない(イベント駆動ではないのだから)。

というわけで、ひとまずランダムかつインタラクティブな入力としての View/XAML を考えるうえで、GUI を対象にする(インタラクティブなものとして、複数のセンサー等も考えてみたけど、それも XAML で扱うほど複雑ではない気がする)。

image

逆に言えば、人が手入力をして、人の目で確認して、再び人が手入力するという手順は、きわめて「遅い」手段である。例えば、ロボット入力(RPAを含む)ならば、多少回りくどい手順を追加しても確実に入力できる方法を取ることができる。だが、人力の場合は、間違いがあり間違いを目で確かめて、再入力するという手間がかかる。これこそが GUI の特徴でもある…ということにしておこう。

XAML を動的入れ替え&VMのリモート化

動的に XAML が入れ替えられると仮定しよう(実際、WPFの場合、XamlReaderを使うと入れ替えられる)。動的に XAML を生成しなくても、画面遷移の中で View を切り替える場合も同じ方法になる。

image

ひとつの VM に対して、複数の View を持つ。ここの利点は、

  • 後から XAML を入れ替えることができる(動的に更新が可能)
  • 画面遷移のときに、複数の View のメモリを共有している

というものがある。動的入れ替えのほうは、ブラウザから Webサーバーにアクセスする Web アプリケーションに似ている。ただし、Web アプリの場合は、内部データをサーバー側のセッションに持つことが多いのだが、ここでは VM 上に持たせる。SignalR はこれを JavaScript で実現したものだ。表面上の View はいわゆる「ユーザーインターフェース」なので頻繁に変わることがある。ユーザーによるカスタマイズもあるし、見た目を変えたバージョンアップもある。View > ViewModel > Model の順に更新頻度が変わるのがベターだ。

そして、ViewModel 同士をインターネット回線で通信させる。従来ならば View にべったりくっついた ViewModel を作るところだが、本来の MVVM パターンを意識するならば、クライアント/サーバーの2つの ViewModel に分離することが可能なはずだ。通信回線が遅いという前提もあるのだが、ここではそれなりに早い回線であると仮定しよう。

幸いにしてかつここが MVVM パターンの最大の特徴なのだが、View と ViewModel とのインターフェースは、INotifyPropertyChanged と ICommand を実装する(面倒なので、ICommand のほうは省いてしまうことが多いけど)。この部分の実装は定型のパターンがあるのだけど、実際は独自に作っても良い。このインターフェースを利用して VM 同士の相互通信を可能にすればよい。アスペクト指向の変型判と言っても良い。

XAML の動的入れ替えのみ

話をもうちょっと簡単にするために、要素をひとつずつみてみよう。

image

たとえば、同じ ViewModel を複数の View/XAML で共有するパターンを考える。この場合、画面遷移をおこなっても同じ VM を共有するので内部的なコピーが発生しない。View のコンストラクタに対して ViewModel のオブジェクトを引き渡すだけでよい。通常は引き渡すときに ID のみとかを渡すところなのだが、List-Detail の画面遷移だと、List 側に既に Detail の情報を持っていることが多い。内部的に Detail の Item だけを渡してもいいのだけど、新規作成や削除などで元の List を変更することを考えれば、List 自体を引き渡してしまってもよい。当然、画面から戻ってきたときに追加/削除することもできるけど、Xamarin.Forms や UWP の場合にはダイアログ形式で遷移するか画面を切り替えるかで作りが違うので、ViewModel の動きを統一したいときには、モーダル/モードレス関係なく遷移先に子画面で List の更新をしてしまったほうが問題が少なかったりする。

となれば、動的に「未知な」 View/XAML を切り替えたときに、ひとつの ViewModel で対応するにはどうしたらいいだろうか?という問題がある。

  • 未知な View に対して、常に ViewModel を更新する
  • 未知な View のエラー部分は、ViewModel 側で無視する

という2つのパターンがある。前者のほうは正統ではあるのだが、頻繁に更新されうる View に対して頻繁に ViewModel を更新するのはあまり適切ではない。現在の ViewModel にあわせて View を構成するのも筋ではあるのだが(きちんと制御された View ならそうだろう)。

ならば、ちょっと緩い形で実装された View(間違いがある HTML 形式のようなものだ)に対して、緩く ViewModel が対応してくれてもよい。なので、例外が出てもフェールセーフで動いてくれる ViewModel が必要になるし、動的切り替えの XamlReader では例外が出ないことが望ましい。現状の XamlReader では、プロパティやイベント先がないと例外が発生してしまうので、この用途には向かない。

複数の VM を複数クライアントで共有する

MVVM パターンの ViewModel が、クライアントとサーバーで分離できると仮定するならば、別々の PC で動いている View を手軽に共有できるだろう。この共有度ぐあいはネットワークのスピードに依るのだが、プロパティの変更イベントと View への操作のコマンド制御が通れば、うまくいくと思われる。

image

このように分離していくと、View 内のコード(いわゆるコードビハイド)の意味が明確になってくる。

  • 特定の View 内で閉じるものは、コードビハイドでよい。
  • Model や他PC に影響を与えるもの/与えられるものは、ViewModel に記述する

という使い分けになる。

例えば、画面をクリックしたときに色を変えるとか、数値のフォーマットを変えるとかを XAML の Converter を作ることが多いのだが、これをコードビハイドで実装しても問題ないことがわかる。ひとつの PC 上のひとつの View で閉じているからだ。実装的には、XAML に付随させるほうがよいので JavaScript のような View に埋め込まれるスクリプト言語を使ったほうが便利だろう(アセンブリの配布は少々面倒なので)。逆に、Model に影響を与えるようなロジックは ViewModel に実装せざるを得ず、この部分は通信を含めたコードになる。

異なる PC で View を同期させるためには、サーバー側の VM にはブロードキャスト的な通信が必要になる。このブロードキャストは、INotifyPropertyChanged のインプリメントとして書かれるだろうから、利用時に強く意識されることはないはずだ。このあたりはアスペクト指向的に属性で記述することもできれば、なんらかの実装クラスを継承して使うことになる。少なくとも、本来の ViewModel の実装と、通信を行うための実装を分離する必要がある。

その先は

ひとまず、思考実験はここまで。XamlReader の再実装のほうは、.NET Core 化された WPF のコードを利用すればいいと思う。以前 Xamarin.Forms で試してみて例外をトラップするところまではできたので。リモート可能な ViewModel のほうは、アスペクト指向のように属性でやってみたいところだけど、このあたりは仮コードを作ってみることにしよう。

カテゴリー: 設計 パーマリンク