私は長い質問をお詫びします。それは怒りの言葉として少し読みますが、そうではないことを約束します!私は以下の私の質問を要約しました
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キーワードを使用することもできません。もう少し理由があると思います。_myInjectedProductDetailsViewModelAbstractFactory.Create(productDTO);
したがって、これは通常、このタイプの問題に推奨されるパターン(抽象ファクトリ)です。静的型付けへの渇望を満たすので、実際に使い始めるまでは天才でした。ボイラープレートコードの量は多すぎると思います(私が使用するとんでもない変数名は別として)。ランタイムパラメータを必要とする各ViewModelに対して、2つの追加ファイル(ファクトリインターフェースと実装)を取得し、4つの追加時間などの非ランタイム依存関係を入力する必要があります。そして、依存関係が変更されるたびに、工場でもそれを変更できます。 DIコンテナさえもう使っていないような気がします。 (IthinkCastle Windsorには、これに対する何らかの解決策があります[独自の欠点がありますが、間違っている場合は修正してください])。そう、そうです。このように状態と動作を混在させると、MVCにはまったく存在しない問題が発生します。そして、私は現在、この問題に対する本当に適切な解決策はないように感じます。今、私はいくつかのことを観察したいと思います:
要約するには
新しいビューモデルを開始するときの依存関係の問題は、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);
質問に対する短い回答:
ロングバージョン:
私たちは同じ問題に直面しており、あなたに役立つかもしれないいくつかのことがわかりました。私は「魔法の」解決策を知りませんが、それらは少し苦痛を和らげています。
変更の追跡と検証のために、DTOからバインド可能なモデルを実装します。これらの「データ」-ViewModelはサービスに依存してはならず、コンテナからのものではありません。それらは単に「新しく」なり、受け渡され、DTOから派生することさえあります。結論は、アプリケーションに固有のモデルを実装することです(MVCなど)。
ViewModelを分離します。 Caliburnを使用すると、ViewModelを簡単に組み合わせることができます。 Screen/Conductorモデルを介してそれを提案します。ただし、この結合により、ViewModelの単体テストが困難になり、多くの依存関係が作成されます。最も重要なのは、ViewModelのライフサイクルを管理する負担を課すことです。それらを分離する1つの方法は、ナビゲーションサービスやViewModelコントローラなどを使用することです。例えば。
パブリックインターフェイスIShowViewModels {void Show(object inlineArgumentsAsAnonymousType、string regionId); }
さらに良いのは、何らかの形のメッセージングによってこれを行うことです。ただし、重要なことは、他のViewModelからのViewModelライフサイクルを処理しないことです。 MVCでは、コントローラーは相互に依存せず、MVVMではViewModelは相互に依存しません。他のいくつかの方法でそれらを統合します。
INeedData<T1,T2,...>
のようなものを作成し、タイプセーフな作成パラメーターを適用することは可能ですが、それだけの価値はありません。また、各ViewModelタイプのファクトリを作成することは価値がありません。ほとんどのIoCコンテナはこれに対するソリューションを提供します。実行時にエラーが発生しますが、分離とユニットテストの価値はそれだけの価値があります。あなたはまだ何らかの統合テストを行っており、それらのエラーは簡単に見つけられます。私は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;
}
}
この場合の例として、更新されるユーザーに固有のビューモデルを作成するのではなく、モデルにはビューモデルの呼び出しによって読み込まれるユーザー固有のデータが含まれます。
私が通常これを行う方法(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
ではなく、すべてのビューモデルを解決する習慣に慣れている限り、すべての依存関係の解決がはるかに簡単になります。
本格的な例ではなく、最も単純な定義に移動したほうがよい場合もあります。 http://en.wikipedia.org/wiki/Model_View_ViewModel おそらくZK Javaの例は、C#の例よりも優れています。
他の場合には、あなたの腸の本能に耳を傾けます...
状態/動作の両方を持つViewModelがたくさんあるのはデザインの匂いですか?
モデルはオブジェクトごとのテーブルマッピングですか?おそらくORMは、ビジネスを処理したり、複数のテーブルを更新したりしながら、ドメインオブジェクトへのマッピングを支援します。