実行時にビューモデルを作成する際の痛みを軽減する方法


17

私は長い質問に謝罪します、それは少し暴言として読みますが、そうではないと約束します!私の質問を以下にまとめました

MVCの世界では、物事は簡単です。モデルには状態があり、ビューにモデルが表示され、コントローラーモデルとのやり取り(基本的に)を行い、コントローラーには状態がありません。作業行うために、コントローラーはWebサービス、リポジトリー、その他多くに依存しています。コントローラーをインスタンス化するとき、これらの依存関係を提供することに関心があります。アクション(コントローラーのメソッド)を実行するとき、これらの依存関係を使用してモデルを取得または更新するか、他のドメインサービスを呼び出します。一部のユーザーが特定のアイテムの詳細を表示する場合など、コンテキストがある場合、そのアイテムのIDをパラメーターとしてアクションに渡します。コントローラーのどこにも、状態への参照はありません。ここまでは順調ですね。

MVVMと入力します。WPFが大好きで、データバインディングが大好きです。ViewModelsへのデータバインディングをさらに簡単にするフレームワークが大好きです(Caliburn Micro atmを使用)。しかし、この世界では物事はそれほど簡単ではないと感じています。もう一度練習してみましょう。モデルには状態があり、ビューに ViewModel が表示され、ViewModelに(基本的に)モデルとのやり取りあり、ViewModelに状態あります。するには、(1つ以上のモデルに、多分それ委譲し、すべてのプロパティを、それ手段は、それ自体の状態をあるモデルの1の方法または別の、への参照を持っている必要があります明確にするために)行いますViewModelには、Webサービス、リポジトリ、その他多くの依存関係があります。ViewModelをインスタンス化するとき、それらの依存関係だけでなく状態も指定する必要があります。そして、これは、紳士gentle女の皆さん、私をいらいらさせます。

あなたはインスタンス化する必要があるときProductDetailsViewModelからProductSearchViewModel(そこからあなたが呼び出されるProductSearchWebService順番に返されIEnumerable<ProductDTO>、あなたはこれらの事の1行うことができますか?私と一緒に、まだ、誰も):

  • call new ProductDetailsViewModel(productDTO, _shoppingCartWebService /* dependcy */);、これは悪いです、さらに3つの依存関係を想像してください。これは、ProductSearchViewModelそれらの依存関係も同様に引き受ける必要があることを意味します。また、コンストラクタを変更するのは苦痛です。
  • call _myInjectedProductDetailsViewModelFactory.Create().Initialize(productDTO);、ファクトリは単なるFuncであり、ほとんどのIoCフレームワークによって簡単に生成されます。Initメソッドは漏れやすい抽象化であるため、これは悪いと思います。また、Initメソッドで設定されているフィールドにreadonlyキーワードを使用することもできません。他にもいくつかの理由があると思います。
  • call _myInjectedProductDetailsViewModelAbstractFactory.Create(productDTO);So ...これは、このタイプの問題に通常推奨されるパターン(抽象ファクトリー)です。でも、実際に使い始めるまでは、静的型付けへの渇望を満たすため、天才でした。定型コードの量は多すぎると思います(私が使用するとんでもない変数名は別として)。ランタイムパラメーターを必要とする各ViewModelについて、2つの追加ファイル(ファクトリーインターフェースと実装)を取得し、4回の追加時間などの非ランタイム依存関係を入力する必要があります。また、依存関係が変更されるたびに、ファクトリーでも依存関係を変更できます。私はもうDIコンテナさえ使用していないように感じます。(Castle Windsorにはこれに対する何らかの解決策があると思います(それ自体の欠点があるため、間違っている場合は修正してください))。
  • 匿名型または辞書で何かをする。私は静的型付けが好きです。

ええ このように状態と動作を混在させると、MVCにはまったく存在しない問題が発生します。そして、私は現在、この問題に対して本当に適切な解決策がないと感じています。今、私はいくつかのことを観察したいと思います:

  • 人々は実際にMVVMを使用しています。したがって、彼らは上記のすべてを気にしないか、他の素晴らしい解決策を持っています。
  • WPFを使用したMVVMの詳細な例は見つかりませんでした。たとえば、NDDDサンプルプロジェクトは、DDDの概念を理解するのに非常に役立ちました。誰かがMVVM / WPFの似たような方向性を教えてくれたら、とても気に入っています。
  • MVVMをすべて間違っているのかもしれませんが、デザインを上下逆にする必要があります。たぶん、私はこの問題を全く抱えるべきではないでしょう。他の人が同じ質問をしているのは知っているので、私だけではないと思います。

要約する

  • ViewModelが状態と動作の両方の統合ポイントであることは、MVVMパターン全体のいくつかの問題の原因であると結論付けるのは正しいですか?
  • 静的に型付けされた方法でViewModelをインスタンス化するための唯一/​​最良の方法は、抽象ファクトリパターンを使用していますか?
  • 詳細なリファレンス実装のようなものはありますか?
  • 状態/動作の両方を備えたViewModelがたくさんあるのは、デザインの匂いですか?

10
これは読むには長すぎて、修正を検討してください。そこには多くの無関係なものがあります。人々はそれをすべて読むことを気にしないので、良い答えを見逃すかもしれません。
ヤニス

Caliburn.Microが好きだと言っていましたが、このフレームワークが新しいビューモデルのインスタンス化にどのように役立つかわかりませんか?いくつかの例を確認してください。
陶酔

@Euphoricもう少し具体的に言えば、Googleはここで私を助けてくれないようです。検索できるキーワードがありますか?
-dvdvorle

3
MVCを少し単純化していると思います。ビューには最初はモデルが表示されますが、動作中は状態が変化します。私の意見では、この変化する状態は「モデルの編集」です。つまり、一貫性の制限が緩和されたモデルのフラットバージョンです。実際、私がモデルの編集と呼ぶものはMVVM ViewModelです。遷移中の状態を保持します。これは、以前はMVCのビューによって保持されていたか、モデルのコミットされていないバージョンにプッシュバックされたため、所属しているとは思われません。したがって、以前は「流動的」な状態でした。これですべてViewModelになりました。
スコットホイットロック

@ScottWhitlock私は実際にMVCを単純化しています。しかし、 "流動的"な状態がViewModelにあるのは間違っていると言っているのではなく、そこに動作を詰め込むと、別のViewModelなどからViewModelを使用可能な状態に初期化するのが難しくなります。MVCの「モデルの編集」は、それ自体を保存する方法を知りません(Saveメソッドがありません)。しかし、コントローラーはこれを知っており、そのために必要なすべての依存関係を持っています。
dvdvorle

回答:


2

新しいビューモデルを開始するときの依存関係の問題は、IOCで処理できます。

public class MyCustomViewModel{
  private readonly IShoppingCartWebService _cartService;

  private readonly ITimeService _timeService;

  public ProductDTO ProductDTO { get; set; }

  public ProductDetailsViewModel(IShoppingCartWebService cartService, ITimeService timeService){
    _cartService = cartService;
    _timeService = timeService;
  }
}

コンテナをセットアップするとき...

Container.Register<IShoppingCartWebService,ShoppingCartWebSerivce>().As.Singleton();
Container.Register<ITimeService,TimeService>().As.Singleton();
Container.Register<ProductDetailsViewModel>();

ビューモデルが必要な場合:

var viewmodel = Container.Resolve<ProductDetailsViewModel>();
viewmodel.ProductDTO = myProductDTO;

caliburn microなどのフレームワークを使用する場合、多くの場合、すでに何らかの形式のIOCコンテナーが存在しています。

SomeCompositionView view = new SomeCompositionView();
ISomeCompositionViewModel viewModel = IoC.Get<ISomeCompositionViewModel>();
ViewModelBinder.Bind(viewModel, view, null);

1

私は毎日ASP.NET MVCで仕事をしており、1年以上WPFに取り組んできました。

MVC

コントローラーはアクションを調整することになっています(これを取得し、追加します)。

ビューは、モデルの表示を担当します。

モデルには通常、データ(例:UserId、FirstName)と状態(例:タイトル)が含まれ、通常はビュー固有です。

MVVM

モデルは通常、データ(例:UserId、FirstName)のみを保持し、通常は渡されます

ビューモデルには、ビューの動作(メソッド)、そのデータ(モデル)、および相互作用(コマンド)が含まれます。これは、プレゼンターがモデルを認識しているアクティブなMVPパターンに似ています。ビューモデルはビュー固有です(1ビュー= 1ビューモデル)。

ビューは、データの表示とビューモデルへのデータバインディングを担当します。ビューが作成されると、通常、それに関連付けられたビューモデルが作成されます。


MVVMのプレゼンテーションパターンは、データバインディングの性質により、WPF / Silverlightに固有のものであることに注意してください。

ビューは通常、どのビューモデルに関連付けられているか(または抽象化されているか)を認識しています。

ビューごとにインスタンス化されている場合でも、ビューモデルをシングルトンとして扱うことをお勧めします。言い換えれば、IOCコンテナを介してDI経由で作成し、適切なメソッドを呼び出して言うことができるはずです。パラメーターに基づいてモデルをロードします。このようなもの:

public partial class EditUserView
{
    public EditUserView(IContainer container, int userId) : this() {
        var viewModel = container.Resolve<EditUserViewModel>();
        viewModel.LoadModel(userId);
        DataContext = viewModel;
    }
}

この場合の例として、更新されるユーザーに固有のビューモデルを作成しません。代わりに、モデルには、ビューモデルの呼び出しを介して読み込まれるユーザー固有のデータが含まれます。


FirstNameが「Peter」で、Titlesが{「Rev」、「Dr」} *の場合、なぜFirstNameデータとTitleの状態を考慮するのですか?それとも、例を明確にできますか?*そうでもない
ピートカーカム

@PeteKirkham-コンボボックスという文脈で私が言及していた「タイトル」の例。一般に、永続化する情報を送信する場合、選択を行うために使用された状態(例:州/県/タイトルのリスト)は送信されません。データと一緒に転送する価値のある状態(使用中のユーザー名など)は、状態が古くなっている可能性があるため(メッセージキューなどの非同期パターンを使用している場合)、処理の時点で確認する必要があります。
シェラケル

この投稿から2年が経過しましたが、将来の視聴者に有利なコメントをする必要があります。ビューは1つのViewModelに対応する場合がありますが、ViewModelは複数のビューで表すことができます。次に、あなたが説明しているのは、Service Locatorアンチパターンです。私見では、ビューモデルをどこでも直接解決すべきではありません。それがDIの目的です。できる限り少ないポイントで解決を行います。たとえば、Caliburnにこの作業を行わせてください。
ジョニーアダミット

1

質問に対する簡単な回答:

  1. はいState + Behaviorはこれらの問題につながりますが、これはオブジェクト指向のすべてに当てはまります。実際の原因は、SRP違反の一種であるViewModelsのカップリングです。
  2. おそらく静的に型付けされます。ただし、他のViewModelからViewModelをインスタンス化する必要性を減らす/排除する必要があります。
  3. 私が気づいているわけではありません。
  4. いいえ、ただし、関係のない状態と動作を持つViewModelがあります(一部のモデル参照および一部のViewModel参照と同様)

ロングバージョン:

私たちは同じ問題に直面しており、あなたを助けるかもしれないものをいくつか見つけました。私は「魔法の」解決策を知りませんが、それらは少し痛みを和らげています。

  1. 変更の追跡と検証のために、DTOからバインド可能なモデルを実装します。これらの「データ」-ViewModelsは、サービスに依存してはならず、コンテナから来てはなりません。それらは単に「新規」に作成し、渡すことができ、DTOから派生する場合もあります。ボトムラインは、アプリケーション固有のモデルを実装することです(MVCと同様)。

  2. ViewModelsを分離します。Caliburnを使用すると、ViewModelを簡単に結合できます。Screen / Conductorモデルを通じて提案することもあります。しかし、この結合により、ViewModelの単体テストが難しくなり、多くの依存関係が作成され、最も重要になります。ViewModelにViewModelライフサイクルを管理する負担がかかります。それらを分離する1つの方法は、ナビゲーションサービスやViewModelコントローラーのようなものを使用することです。例えば

    パブリックインターフェイスIShowViewModels {void Show(object inlineArgumentsAsAnonymousType、string regionId); }

さらに良いのは、何らかの形のメッセージングによってこれを行うことです。しかし、重要なことは、他のViewModelからViewModelライフサイクルを処理しないことです。MVCコントローラーでは相互に依存せず、MVVMではViewModelは相互に依存すべきではありません。他の方法でそれらを統合します。

  1. コンテナを「文字列」タイプの動的機能を使用します。INeedData<T1,T2,...>タイプセーフな作成パラメーターのようなものを作成し、強制することは可能ですが、その価値はありません。また、ViewModelタイプごとにファクトリを作成する価値はありません。ほとんどのIoCコンテナーは、これに対するソリューションを提供します。実行時にエラーが発生しますが、デカップリングと単体テストは価値があります。あなたはまだある種の統合テストを行い、それらのエラーは簡単に発見されます。

0

私が通常これを行う方法(PRISMを使用)は、各アセンブリにコンテナ初期化モジュールが含まれ、すべてのインターフェイス、インスタンスが起動時に登録されるということです。

private void RegisterResources()
{
    Container.RegisterType<IDataService, DataService>();
    Container.RegisterType<IProductSearchViewModel, ProductSearchViewModel>();
    Container.RegisterType<IProductDetailsViewModel, ProductDetailsViewModel>();
}

そして、例のクラスが与えられると、このように実装され、コンテナはずっと通過します。この方法により、コンテナに既にアクセスできるため、新しい依存関係を簡単に追加できます。

/// <summary>
/// IDataService Interface
/// </summary>
public interface IDataService
{
    DataTable GetSomeData();
}

public class DataService : IDataService
{
    public DataTable GetSomeData()
    {
        MessageBox.Show("This is a call to the GetSomeData() method.");

        var someData = new DataTable("SomeData");
        return someData;
    }
}

public interface IProductSearchViewModel
{
}

public class ProductSearchViewModel : IProductSearchViewModel
{
    private readonly IUnityContainer _container;

    /// <summary>
    /// This will get resolved if it's been added to the container.
    /// Or alternately you could use constructor resolution. 
    /// </summary>
    [Dependency]
    public IDataService DataService { get; set; }

    public ProductSearchViewModel(IUnityContainer container)
    {
        _container = container;
    }

    public void SearchAndDisplay()
    {
        DataTable results = DataService.GetSomeData();

        var detailsViewModel = _container.Resolve<IProductDetailsViewModel>();
        detailsViewModel.DisplaySomeDataInView(results);

        // Create the view, usually resolve using region manager etc.
        var detailsView = new DetailsView() { DataContext = detailsViewModel };
    }
}

public interface IProductDetailsViewModel
{
    void DisplaySomeDataInView(DataTable dataTable);
}

public class ProductDetailsViewModel : IProductDetailsViewModel
{
    private readonly IUnityContainer _container;

    public ProductDetailsViewModel(IUnityContainer container)
    {
        _container = container;
    }

    public void DisplaySomeDataInView(DataTable dataTable)
    {
    }
}

すべてのビューモデルの派生元であり、コンテナへの参照を含むViewModelBaseクラスを持つことは非常に一般的です。ビューモデルの代わりにすべてのビューモデルを解決する習慣を身に付けている限り、new()'ingすべての依存関係の解決がはるかに簡単になるはずです。


0

本格的な例ではなく、最も単純な定義に進むとよい場合があります。http//en.wikipedia.org/wiki/Model_View_ViewModel ZK Javaの例を読むと、C#の例よりもわかりやすくなります。

他の時間はあなたの腸の本能に耳を傾けます...

状態/動作の両方を備えたViewModelがたくさんあるのは、デザインの匂いですか?

モデルはオブジェクトごとのマッピングですか?おそらく、ORMは、ビジネスの処理中または複数のテーブルの更新中にドメインオブジェクトへのマッピングに役立ちます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.