web-dev-qa-db-ja.com

実行時にビューモデルを作成するのを簡単にする方法

私は長い質問をお詫びします。それは怒りの言葉として少し読みますが、そうではないことを約束します!私は以下の私の質問を要約しました

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

MVVMを入力します。私はWPFが大好きで、データバインディングが大好きです。 ViewModelへのデータバインディングをさらに簡単にするフレームワークが大好きです(Caliburn Micro a.t.mを使用)。でも、この世界では物事はそれほど単純ではないと感じています。演習をもう一度行ってみましょう。モデルには状態があり、ビューshowsViewModel、およびビューモデルdoesモデル(基本的に)との関係、ViewModel状態を持っています! (明確にするために、おそらくすべてのプロパティを1つ以上のモデルに委譲しますが、それは、モデルへの参照が何らかの方法であり、それ自体が状態である必要があることを意味します)TodoViewModelがWebサービス、リポジトリ、ロットなどに依存しているもの。 ViewModelをインスタンス化するときは、それらの依存関係だけでなく状態も指定することに注意してください。そして、これは、紳士淑女の皆さん、私を終わりまで悩ませます。

ProductDetailsViewModelからProductSearchViewModelをインスタンス化する必要があるときはいつでも(ここからProductSearchWebServiceが呼び出され、次に_IEnumerable<ProductDTO>_が返されましたが、誰もが私と一緒にいますか?)、次のいずれかを実行できます。

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

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

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

要約するには

  • ViewModelが状態と動作の両方の統合ポイントであることが、MVVMパターン全体でのいくつかの困難の理由であると結論付けるのは正しいですか?
  • 抽象ファクトリパターンを使用することは、静的に型指定された方法でViewModelをインスタンス化する唯一の/最良の方法ですか?
  • 利用可能な詳細なリファレンス実装のようなものはありますか?
  • 状態/動作の両方を持つViewModelがたくさんあるのはデザインの匂いですか?
18
dvdvorle

新しいビューモデルを開始するときの依存関係の問題は、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);
2
Mike

質問に対する短い回答:

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

ロングバージョン:

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

  1. 変更の追跡と検証のために、DTOからバインド可能なモデルを実装します。これらの「データ」-ViewModelはサービスに依存してはならず、コンテナからのものではありません。それらは単に「新しく」なり、受け渡され、DTOから派生することさえあります。結論は、アプリケーションに固有のモデルを実装することです(MVCなど)。

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

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

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

  1. コンテナを「文字列」型/動的機能で使用します。 INeedData<T1,T2,...>のようなものを作成し、タイプセーフな作成パラメーターを適用することは可能ですが、それだけの価値はありません。また、各ViewModelタイプのファクトリを作成することは価値がありません。ほとんどのIoCコンテナはこれに対するソリューションを提供します。実行時にエラーが発生しますが、分離とユニットテストの価値はそれだけの価値があります。あなたはまだ何らかの統合テストを行っており、それらのエラーは簡単に見つけられます。
1
sanosdole

私はASP.NET MVCで毎日働いており、1年以上にわたってWPFに取り組んできましたが、これが私の見方です。

[〜#〜] mvc [〜#〜]

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

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

モデルは通常、データ(例:UserId、FirstName)と状態(例:Titles)を含み、通常はビュー固有です。

[〜#〜] mvvm [〜#〜]

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

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

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


覚えておかなければならないのは、MVVMプレゼンテーションパターンは、データバインディングの性質上、WPF/Silverlightに固有のものであることです。

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

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

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

この場合の例として、更新されるユーザーに固有のビューモデルを作成するのではなく、モデルにはビューモデルの呼び出しによって読み込まれるユーザー固有のデータが含まれます。

1
Shelakel

私が通常これを行う方法(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
Martin Cooper

本格的な例ではなく、最も単純な定義に移動したほうがよい場合もあります。 http://en.wikipedia.org/wiki/Model_View_ViewModel おそらくZK Javaの例は、C#の例よりも優れています。

他の場合には、あなたの腸の本能に耳を傾けます...

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

モデルはオブジェクトごとのテーブルマッピングですか?おそらくORMは、ビジネスを処理したり、複数のテーブルを更新したりしながら、ドメインオブジェクトへのマッピングを支援します。

0
Gerry King