MVVMとサービスパターン


13

MVVMパターンを使用してWPFアプリケーションを構築しています。現在、私のビューモデルは、サービスレイヤーを呼び出してモデルを取得し(ビューモデルとは無関係です)、モデルをビューモデルに変換します。コンストラクター注入を使用して、必要なサービスをビューモデルに渡します。

簡単にテストでき、依存関係の少ないビューモデルでうまく機能しますが、複雑なモデルのviewModelを作成しようとするとすぐに、多くのサービスが挿入されたコンストラクターがあります(各依存関係と使用可能なすべての値のリストを取得するためのもの)たとえば、itemsSourceにバインドします)。そのような複数のサービスをどのように処理し、簡単にユニットテストできるビューモデルがまだあるのか疑問に思っています。

私はいくつかの解決策を考えています:

  1. 使用可能なすべてのサービスをインターフェイスとして含むサービスシングルトン(IServices)を作成します。例:Services.Current.XXXService.Retrieve()、Services.Current.YYYService.Retrieve()。そうすれば、大量のサービスパラメータを含む巨大なコンストラクタはありません。

  2. viewModelによって使用されるサービスのファサードを作成し、このオブジェクトを私のviewmodelのctorに渡します。しかし、その後、複雑なビューモデルごとにファサードを作成する必要があります。

この種のアーキテクチャを実装する「正しい」方法は何だと思いますか?


それを行う「正しい」方法は、サービスを呼び出し、ViewModelの作成に必要なキャストを行う別のレイヤーを作成することだと思います。ViewModelが自分自身を作成する責任を負わないようにする必要があります。
エイミーブランケンシップ

@AmyBlankenship:ビューモデルは自分で作成する必要はありません(または、必ずしもできるようにする必要はありません)が、他のビューモデルを作成することは避けられない場合があります。ここでは、自動ファクトリサポートを備えたIoCコンテナが非常に役立ちます。
アーロンノート

「時々」と「すべき」は2つの異なる動物です;)
エイミーブランケンシップ

@AmyBlankenship:あなたは、ビューモデルが他のビューモデルを作成してはならないことを提案していますか?それは飲み込むのが難しい薬です。ビューモデルを使用newして他のビューモデルを作成するべきではないと言うことは理解できますが、「新しいドキュメント」ボタンまたはメニューをクリックすると新しいタブが追加されるか、新しいウィンドウが開くMDIアプリケーションのような単純なものを考えてください。シェル/コンダクター、間接的な1つまたはいくつかのレイヤーの後ろに隠れていても、何かの新しいインスタンスを作成できる必要あります。
アーロンノート

確かに、ビューをどこかで作成するように要求する機能が必要です。しかし、それ自体を作るには?私の世界ではありません:)。しかし、私が住んでいる世界では、VMを「プレゼンテーションモデル」と呼びます。
エイミーブランケンシップ

回答:


22

実際、これらのソリューションはどちらも悪いです。

使用可能なすべてのサービスをインターフェイスとして含むサービスシングルトン(IServices)を作成します。例:Services.Current.XXXService.Retrieve()、Services.Current.YYYService.Retrieve()。そうすれば、大量のサービスパラメータを含む巨大なコンストラクタはありません。

これは、本質的にはService Locator Patternであり、アンチパターンです。これを行うと、プライベート実装を確認しないと、ビューモデルが実際に依存するものを理解できなくなり、テストやリファクタリングが非常に困難になります。

viewModelによって使用されるサービスのファサードを作成し、このオブジェクトを私のviewmodelのctorに渡します。しかし、その後、複雑なビューモデルごとにファサードを作成する必要があります。

これはそれほどアンチパターンではありませんが、コードの匂いです。本質的にはパラメーターオブジェクトを作成しますが、POリファクタリングパターンのポイントは、頻繁に多くの異なる場所使用されるパラメーターセットを処理することです、このパラメーターは一度しか使用されません。あなたが言及したように、それは実際の利益のために多くのコードの膨張を作成し、多くのIoCコンテナでうまく動作しません。

実際、上記の両方の戦略は全体的な問題を見落としています。つまり、ビューモデルとサービスの間の結合が高すぎるということです。単純に隠れサービスロケータでこれらの依存関係を、パラメータオブジェクトが、実際にはありません変更ビューモデルが依存するどのように多くの他のオブジェクト。

これらのビューモデルの1つを単体テストする方法を考えてください。セットアップコードはどのくらいの大きさになりますか?動作させるために初期化する必要があるものはいくつですか?

MVVMを開始する多くの人は、画面全体のビューモデルを作成しようとしますが、これは根本的に間違ったアプローチです。MVVMはすべて構成です。多くの機能を備えた画面は、それぞれ1つまたは少数の内部モデル/サービスに依存する複数の異なるビューモデルで構成する必要があります。相互に通信する必要がある場合は、pub / sub(メッセージブローカー、イベントバスなど)を介して通信します。

実際に行う必要があるのは、ビューモデルをリファクタリングして、依存関係少なくなるようにすることです。次に、集約「画面」が必要な場合は、別のビューモデルを作成して、より小さなビューモデルを集約します。この集計ビューモデルは、それ自体でそれほど多くのことを行う必要はありません。したがって、このモデルは、理解とテストもかなり簡単です。

これを適切に行った場合、コードを見るだけで明白になるはずです。なぜなら、短くて簡潔で、具体的で、テスト可能なビューモデルがあるからです。


ええ、それはおそらく私がやることになるでしょう!どうもありがとう、先生。
アルファアルファ

まあ、私は彼がすでにこれを試みたと思っていたが、成功しなかった。@アルファ-アルファ
陶酔

@Euphoric:これでどうやって「成功しない」のですか?ヨーダが言うように、「するかしないか」は試してみません。
アーロンノート

@Aaronaughtたとえば、彼は本当にすべてのデータを単一のビューモデルで必要とします。たぶん彼はグリッドを持っており、異なる列は異なるサービスから来ています。作曲ではできません。
陶酔

@Euphoric:実際には、コンポジションでそれを解決できます、それはビューモデルレベルの下で行うことができます。それは単に正しい抽象化を作成することの問題です。その場合、IDのリストを取得するための初期クエリを処理するサービスと、独自の情報を注釈する「エンリッチャー」のシーケンス/リスト/配列のみが必要です。グリッド自体を独自のビューモデルにし、事実上2つの依存関係で問題を解決しました。テストは非常に簡単です。
アーロンノート

1

私はこれについて本を書くことができます...実際に私は;)

まず、物事を行うための普遍的な「正しい」方法はありません。他の要因を考慮する必要があります。

サービスの粒度が細かすぎる可能性があります。特定のViewmodelまたは関連するViewModelのクラスターでさえ使用するインターフェースを提供するFacadeでサービスをラップすることは、より良いソリューションかもしれません。

さらにシンプルなのは、すべてのビューモデルが使用する単一のファサードにサービスをラップすることです。もちろん、これは平均的なシナリオでは多くの不要な機能を備えた非常に大きなインターフェイスになる可能性があります。しかし、システム内のすべてのメッセージを処理するメッセージルーターと同じです。

実際、多くのアーキテクチャが最終的に進化するのを見てきましたが、Event Aggregatorパターンのようなものを中心に構築されたメッセージバスです。テストクラスはリスナーをEAに登録し、それに応じて適切なイベントを発生させるだけなので、テストは簡単です。しかし、それは成長するのに時間がかかる高度なシナリオです。私は統一されたファサードから始めて、そこから行くと言います。


大規模なサービスファサードは、メッセージブローカーとは大きく異なります。依存関係スペクトルのほぼ反対側にあります。このアーキテクチャの特徴は、送信者が受信者について何も知らず、多くの受信者(pub / subまたはマルチキャスト)が存在する可能性があることです。おそらく、リモートプロトコル上で従来のサービスを公開するだけのRPCスタイルの「リモート処理」と混同している可能性があり、送信側は受信側に物理的(エンドポイントアドレス)および論理的(戻り値)に結合されたままです。
アーロンノート

類似点は、ファサードがルーターのように機能し、発信者は、メッセージを送信するクライアントがメッセージの処理者を知らないように、どのサービスが呼び出しを処理するのかを知らないことです。
マイケルブラウン

はい、しかし正面は神のオブジェクトです。ビューモデルにあるすべての依存関係があります。おそらく、複数のモデルで共有されるためです。実際には、最初は非常に熱心に働いていた疎結合の利点を排除しました。これは、何かがメガファサードに触れるたびに、実際にどの機能に依存するかわからないためです。ファサードを使用するクラスのユニットテストの記述。ファサードのモックを作成します。さて、どのメソッドをモックしますか?セットアップコードはどのように見えますか?
アーロンノート

ブローカはメッセージハンドラの実装についても何も知らないためこれはメッセージブローカとは大きく異なります。内部でIoCを使用します。ファサードは呼び出しを受信者に転送する必要があるため、受信者に関するすべてを認識しています。バスの結合はゼロです。ファサードには、わいせつなほど高い遠心性のカップリングがあります。あなたが変更するほとんどすべてのものは、どこでも、ファサードにも影響します。
アーロンノート

私はここでの混乱の一部-私はこれを非常によく見ます-ちょうど依存関係が意味するものだと思います。他の1つのクラスに依存しているが、そのクラスの4つのメソッドを呼び出すクラスがある場合、1ではなく4つの依存関係があります。 。
アーロンノート

0

両方を組み合わせてみませんか?

ファサードを作成し、ビューモデルが使用するすべてのサービスを配置します。その後、悪いSの単語なしで、すべてのビューモデルに単一のファサードを作成できます。

または、コンストラクター注入の代わりにプロパティ注入を使用できます。しかし、その後、それらが適切に注入されていることを確認する必要があります。


擬似C#で例を提供した場合、これはより良い答えになります。
ロバートハーベイ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.