MVVMは、設計が不十分なデータバインディングレイヤーのバンドエイドです。特に、WPF / XAMLのデータバインディングの制限のため、WPF / silverlight / WP7の世界で多くの使用が見られています。
これからは、WPF / XAMLについて話していると仮定します。これにより、物事がより明確になるからです。MVVMがWPF / XAMLで解決するために設定した欠点のいくつかを見てみましょう。
データ形状とUI形状
MVVMの「VM」は、C#で定義されたオブジェクトのセットを作成し、XAMLで定義されたプレゼンテーションオブジェクトのセットにマップします。これらのC#オブジェクトは通常、プレゼンテーションオブジェクトのDataContextプロパティを介してXAMLに接続されます。
その結果、viewmodelオブジェクトグラフは、アプリケーションのプレゼンテーションオブジェクトグラフにマッピングする必要があります。それは、マッピングが1対1である必要があるということではありませんが、リストコントロールがウィンドウコントロールに含まれている場合、ウィンドウのDataContextオブジェクトからそのリストのデータを記述するオブジェクトに取得する方法が必要です。
ビューモデルオブジェクトグラフは、UIオブジェクトグラフからモデルオブジェクトグラフを正常に分離しますが、構築および維持する必要がある追加のビューモデルレイヤーを犠牲にします。
画面Aから画面Bにデータを移動する場合は、ビューモデルをいじる必要があります。ビジネスマンの心では、これはUIの変更です。それはすべきである XAMLの世界では、純粋に場所を取ります。悲しいことに、それはめったにできません。さらに悪いことに、ビューモデルがどのように構造化されているか、データがどの程度アクティブに変更されるかに応じて、この変更を実現するためにかなりのデータの再ルーティングが必要になる場合があります。
非表現的なデータバインディングの回避
WPF / XAMLバインディングは表現力が不十分です。基本的に、オブジェクトに到達する方法、通過するプロパティパス、およびプレゼンテーションオブジェクトが必要とするものにデータプロパティの値を適合させるためのコンバータをバインドする方法を提供します。
C#のプロパティをそれより複雑なものにバインドする必要がある場合、基本的には運が悪いです。True / FalseをVisible / Collapsedに変換するバインディングコンバーターのないWPFアプリを見たことはありません。多くのWPFアプリには、NegatingVisibilityConverterと呼ばれる、極性を反転させるようなものが含まれる傾向があります。これにより、警告音が鳴ります。
MVVMは、この制限を緩和するために使用できるC#コードを構造化するためのガイドラインを提供します。SomeButtonVisibilityと呼ばれるビューモデルのプロパティを公開し、そのボタンの可視性にバインドすることができます。XAMLは素晴らしく、きれいになりました...しかし、あなたは店員になりました。今、UIが進化するとき、2つの場所(UIとC#のコード)でバインディングを公開+更新する必要があります。同じボタンを別の画面に配置する必要がある場合は、その画面からアクセスできるビューモデルで同様のプロパティを公開する必要があります。さらに悪いことに、XAMLを見て、ボタンがいつ表示されるかを確認することはできません。バインディングが少し自明ではなくなるとすぐに、C#コードで検出作業を行う必要があります。
データへのアクセスは積極的にスコープされます
通常、データはDataContextプロパティを介してUIに入力されるため、アプリ全体でグローバルデータまたはセッションデータを一貫して表すことは困難です。
「現在ログインしているユーザー」というアイデアは素晴らしい例です。これは、多くの場合、アプリのインスタンス内で本当にグローバルなものです。WPF / XAMLでは、現在のユーザーに一貫した方法でグローバルアクセスを保証することは非常に困難です。
私がやりたいのは、データバインディングで「CurrentUser」という単語を自由に使用して、現在ログインしているユーザーを指すことです。代わりに、すべての DataContextが現在のユーザーオブジェクトに到達する方法を提供することを確認する必要があります。MVVMはこれに対応できますが、ビューモデルはすべてこのグローバルデータへのアクセスを提供する必要があるため、混乱するでしょう。
MVVMが倒れる例
ユーザーのリストがあるとします。現在ログインしているユーザーが管理者である場合にのみ、各ユーザーの横に「ユーザーの削除」ボタンを表示します。また、ユーザーは自分自身を削除することはできません。
モデルオブジェクトは、現在ログインしているユーザーを認識してはなりません。データベースのユーザーレコードを表すだけですが、何らかの理由で、現在ログインしているユーザーは、リスト行内のデータバインディングに公開する必要があります。MVVMは、現在ログインしているユーザーとそのリスト行で表されるユーザーを構成する各リスト行のビューモデルオブジェクトを作成し、そのビューモデルオブジェクトで「DeleteButtonVisibility」または「CanDelete」というプロパティを公開する必要があることを指示しますコンバーターのバインドについて)。
このオブジェクトは、他のほとんどの点でユーザーオブジェクトに非常によく似ています。ユーザーモデルオブジェクトのすべてのプロパティを反映し、データの変更に応じてそのデータを更新する必要がある場合があります。これは本当に気分が悪いと感じます-繰り返しますが、MVVMは、このユーザーに似たオブジェクトを維持することを強制することで、あなたを店員にします。
考慮してください。おそらく、データベース、モデル、ビューでユーザーのプロパティを表す必要があります。自分とデータベースの間にAPIがある場合、さらに悪いことになります。それらは、データベース、APIサーバー、APIクライアント、モデル、およびビューに表示されます。プロパティが追加または変更されるたびにタッチする必要がある別のレイヤーを追加するデザインパターンを採用することを本当にためらいます。
さらに悪いことに、このレイヤーは、データモデルの複雑さではなく、UIの複雑さに合わせて調整されます。多くの場合、同じデータが多くの場所とUIに表示されます。これにより、レイヤーが追加されるだけでなく、多くの余分な表面積を持つレイヤーが追加されます。
物事はどうだったか
上記のケースでは、私は言いたいです:
<Button Visibility="{CurrentUser.IsAdmin && CurrentUser.Id != Id}" ... />
CurrentUserは、アプリ内のすべてのXAMLにグローバルに公開されます。Idは、リスト行のDataContextのプロパティを参照します。可視性はブール値から自動的に変換されます。Id、CurrentUser.IsAdmin、CurrentUser、またはCurrentUser.Idを更新すると、このボタンの可視性が更新されます。簡単です。
代わりに、WPF / XAMLはユーザーに完全な混乱を作成させる。私の知る限り、一部の創造的なブロガーはその混乱に名前を平手打ちし、その名前はMVVMでした。だまされてはいけません。GoFのデザインパターンと同じクラスではありません。これは、いデータバインディングシステムを回避するためのugいハックです。
(このアプローチは、さらに読みたい場合に「機能的リアクティブプログラミング」と呼ばれることもあります)。
結論として
WPF / XAMLで作業する必要がある場合、MVVMはお勧めしません。
上記の「方法」の例のようにコードを構造化します。モデルは、ビューに直接公開され、複雑なデータバインディング式+柔軟な値強制を使用します。より読みやすく、書きやすく、保守しやすい方法です。
MVVMは、より冗長で保守性の低い方法でコードを構造化するよう指示します。
MVVMの代わりに、優れたエクスペリエンスに近づけるためにいくつかのものを構築します。グローバル状態を一貫してUIに公開するための規則を開発します。より複雑なバインディング式を表現できるバインディングコンバータ、MultiBindingなどからツールを作成します。バインディングコンバーターのライブラリを作成して、一般的な強制の場合の痛みを軽減します。
さらに良いこと-XAMLをより表現力豊かなものに置き換えます。XAMLは、C#オブジェクトをインスタンス化するための非常にシンプルなXML形式です。表現力豊かなバリアントを作成するのは難しくありません。
私の他の推奨事項:この種の妥協を強制するツールキットを使用しないでください。問題領域に集中するのではなく、MVVMのようなくだらないものにあなたを押しやることで、最終製品の品質を損ないます。