8

WPF で Visual Studio のようなアプリケーションを構築していますが、コンポーネントの最適なアーキテクチャ設計構成を特定するのに問題があります。Unity を依存性注入コンテナーと Visual Studio ユニット テスト フレームワークとして使用し、おそらく moq ライブラリをモック化する予定です。

最初にソリューションの構造を説明し、次に質問を説明します。

以下を含むWPF プロジェクトがあります。

  • アプリケーションの起動時 (App.xaml.cs 内) での Unity コンテナーの初期化 (ブートストラップ)
  • すべてのアプリケーション ビュー (XAML)。

ViewModelと呼ばれる別のプロジェクトには、以下が含まれます。

  • すべてのアプリケーション ViewModels。すべての ViewModels は、ILogger プロパティを公開する ViewModelBase から継承します

私の初期化ロジックは次のとおりです。

  1. アプリケーションの起動
  2. Unity コンテナの作成とタイプの登録: MainView および MainViewModel
  3. MainView を解決して表示します。

var window = Container.Resolve<MainView>();

window.Show();

私の MainView コンストラクターは、そのコンストラクターで MainViewModel オブジェクトを受け取ります。

public MainView(MainViewModel _mvm)
  1. 私の MainViewModel には、そのパネルごとに子 ViewModel があります。

    public ToolboxViewModel ToolboxVM{get; set;}
    public SolutionExplorerViewModel SolutionExplorerVM { get; set; }
    public PropertiesViewModel PropertiesVM { get; set; }
    public MessagesViewModel MessagesVM { get; set; }
    

そして、各パネルを初期化する InitializePanels() メソッドを作成する予定です。

ここで私の質問: MainViewModel.InitializePanels() でこれらすべてのパネルを初期化するにはどうすればよいですか? 次のオプションを指定します。

オプション 1: ViewModel を手動で初期化します。

ToolboxVM = new ToolboxViewModel();
//Same for the rest of VM...

短所:

  • Unity コンテナーを使用していないため、依存関係 (ILogger など) が自動的に解決されません

オプション 2:プロパティに注釈を付けてセッター インジェクションを使用する:

[Dependency]
public ToolboxViewModel ToolboxVM{get; set;}
//... Same for rest of Panel VM's

短所:

  • この場合、Unity との依存関係が生成されるため、Unity Setter の依存関係を避ける必要があることを読みました。
  • また、単体テストに Unity を使用しないようにする必要があることも読みましたが、単体テストでこの依存関係を明確にするにはどうすればよいですか? 多くの依存プロパティを持つことは、構成するのに悪夢になる可能性があります。

オプション 3: Unity コンストラクター インジェクションを使用して、すべての Panel ViewModel を MainViewModel コンストラクターに渡し、Unity コンテナーによって自動的に解決されるようにします。

public MainViewModel(ToolboxViewModel _tbvm, SolutionExploerViewModel _sevm,....)

長所:

  • 依存関係は作成時に明らかであり、ViewModel UnitTests の構築に役立つ可能性があります。

短所:

  • 非常に多くのコンストラクターパラメーターがあると、すぐに醜くなる可能性があります

オプション 4:コンテナーの構築時にすべての VM タイプを登録する。次に、UnityContainer インスタンスをコンストラクター インジェクションを介して MainViewModel に渡します。

public MainViewModel(IUnityContainer _container)

そうすれば、次のようなことができます:

        Toolbox = _container.Resolve<ToolboxViewModel>();
        SolutionExplorer = _container.Resolve<SolutionExplorerViewModel>();
        Properties = _container.Resolve<PropertiesViewModel>();
        Messages = _container.Resolve<MessagesViewModel>();

短所:

  • 多くの人が示唆しているように、UnitTests に Unity を使用しないことに決めた場合、Panel ViewModel を解決して初期化することはできません。

その長い説明を考えると、依存性注入コンテナーを利用して単体テスト可能なソリューションになるようにするための最良のアプローチは何ですか??

前もって感謝します、

4

3 に答える 3

5

まず最初に... お気づきのように、現在のセットアップは、単体テスト (複雑な VM 初期化) の際に問題になる可能性があります。しかし、単純にDI の原則に従い、concretion ではなく抽象化に依存することで、この問題はすぐに解消されます。ビューモデルがインターフェイスを実装し、依存関係がインターフェイスを介して実現される場合、テストでは単純にモックを使用するため、複雑な初期化は無関係になります。

次に、注釈付きプロパティの問題は、ビュー モデルと Unity の間に高い結合を作成することです(これが、間違っている可能性が最も高い理由です)。理想的には、登録は単一の最上位ポイント (あなたの場合はブートストラップ) で処理する必要があるため、コンテナーはそれが提供するオブジェクトに何らかの方法でバインドされません。オプション #3 と #4 は、この問題の最も一般的な解決策ですが、いくつかの注意事項があります。

  • #3 : あまりにも多くのコンストラクターの依存関係は、通常、ファサード クラスで共通の機能をグループ化することによって軽減されます(ただし、結局のところ、4 はそれほど多くありません)。通常、適切に設計されたコードにはこの問題はありません。何をするかによっては、具体的なものではなく、子ビューモデルのリストMainViewModelへの依存関係が必要になる可能性があることに注意してください。
  • #4 : 単体テストでは IoC コンテナーを使用しないでください。MainViewModel(ctorを介して)手動で簡単に作成し、手動でモック注入します。

もう一点申し上げたいと思います。プロジェクトが成長したときに何が起こるかを考えてみましょう。すべてのビュー モデルを 1 つのプロジェクトにパックすることは、お勧めできません。各ビュー モデルには独自の (多くの場合、他のビュー モデルとは関係のない) 依存関係があり、これらすべてを一緒に配置する必要があります。これはすぐに保守が困難になる可能性があります。代わりに、いくつかの一般的な機能 ( messagingtoolsなど) を抽出し、それらを別のプロジェクト グループ (ここでも M-VM-V プロジェクトに分割) に含めることができるかどうかを検討してください。

また、機能に関連するグループ化があると、ビューを交換するのがはるかに簡単になります。プロジェクト構造が次のようになっている場合:

> MyApp.Users
> MyApp.Users.ViewModels
> MyApp.Users.Views
> ...

ユーザー編集ウィンドウの別のビューを試すには、単一のアセンブリを再コンパイルして交換する必要があります ( User.Views)。オールインワン バッグアプローチでは、アプリケーションの大部分がまったく変更されていなくても、アプリケーションの大部分を再構築する必要があります。

編集:プロジェクトの既存の構造を変更することは(たとえ小さなものであっても)、通常は非常にコストのかかるプロセスであり、ビジネス上の結果はマイナー/なしであることに注意してください。許可されていないか、単にそうする余裕がないかもしれません。使用法ベース (DAL、BLL、BO など) の構造は機能しますが、時間とともに重くなります。コア機能を用途ごとにグループ化し、モジュラー アプローチを利用して新しい機能を追加するだけで、混合モードを使用することもできます。

于 2012-06-22T14:30:47.177 に答える
2

まず、具体的なクラスではなくインターフェイスを使用することをお勧めします。これにより、単体テスト時にモックオブジェクトを渡すことができます。つまり、IToolboxViewModelの代わりにToolboxViewModel.

そうは言っても、3番目のオプションであるコンストラクター注入をお勧めします。var mainVM = new MainViewModel()そうしないと、機能しないビューモデルを呼び出してしまう可能性があるため、これは最も理にかなっています。そうすることで、ビューモデルの依存関係が非常に理解しやすくなり、単体テストの記述が容易になります。

あなたの質問に関連しているので、このリンクをチェックアウトします。

于 2012-06-22T14:27:07.333 に答える
1

私は Lester の指摘に同意しますが、いくつかの選択肢や意見を追加したいと思います。

コンストラクターを介して ViewModel を View に渡す場合、これは少し型破りです。WPF のバインド機能により、DataContext オブジェクトにバインドすることで ViewModel を View から完全に切り離すことができるからです。概説した設計では、ビューは具体的な実装に結合されており、再利用が制限されています。

サービス ファサードはオプション 3 を簡素化しますが、(概要を説明したように) トップレベルの ViewModel が多くの責任を持つことは珍しくありません。考慮できるもう 1 つのパターンは、viewmodel を組み立てるコントローラーまたはファクトリー パターンです。ファクトリはコンテナーによってバックアップされて作業を実行できますが、コンテナーは呼び出し元から抽象化されています。コンテナー駆動型アプリを構築する際の重要な目標は、システムの組み立て方法を理解するクラスの数を制限することです。

もう 1 つの懸念事項は、最上位のビューモデルに属する責任とオブジェクトの関係の量です。Prism (WPF + Unity の有力な候補) を見ると、モジュールによって設定される「領域」の概念が導入されています。領域は、複数のモジュールによって取り込まれたツールバーを表す場合があります。このような設計では、最上位ビューモデルの責任 (および依存関係) が少なくなり、各モジュールには単体テスト可能な DI コンポーネントが含まれます。あなたが提供した例からの考え方の大きな変化。

オプション 4 については、コンテナーがコンストラクターを介して渡される場合、技術的には依存関係の逆転ですが、依存関係の挿入ではなく、サービスの場所の形式になっています。非常に滑りやすい坂道 (崖のようなもの) であると断言する前に、この道をたどると、依存関係はクラス内に隠され、コードは「ジャスト イン タイム」の狂気の網のようになります。

于 2012-06-23T16:12:18.800 に答える