Ich erstelle eine WPF-Anwendung mit dem MVVM-Muster. Momentan rufen meine Ansichtsmodelle die Serviceebene auf, um Modelle abzurufen (was für das Ansichtsmodell nicht relevant ist) und sie in Ansichtsmodelle zu konvertieren. Ich verwende die Konstruktorinjektion, um den erforderlichen Dienst an das Ansichtsmodell zu übergeben.
Es ist leicht zu testen und funktioniert gut für Ansichtsmodelle mit wenigen Abhängigkeiten. Sobald ich jedoch versuche, Ansichtsmodelle für komplexe Modelle zu erstellen, wird in einen Konstruktor eine Menge Dienste eingefügt (einer zum Abrufen aller Abhängigkeiten und eine Liste aller verfügbaren Werte) um zum Beispiel an eine itemsSource zu binden). Ich frage mich, wie ich mit mehreren Diensten wie diesen umgehen soll und habe immer noch ein Ansichtsmodell, das ich problemlos testen kann.
Ich denke an ein paar Lösungen:
Erstellen eines Services Singleton (IServices), der alle verfügbaren Services als Schnittstellen enthält. Beispiel: Services.Current.XXXService.Retrieve (), Services.Current.YYYService.Retrieve (). Auf diese Weise habe ich keinen riesigen Konstruktor mit einer Vielzahl von Dienstparametern.
Erstellen einer Fassade für die vom viewModel verwendeten Dienste und Übergeben dieses Objekts in den ctor meines viewmodel. Aber dann muss ich für jedes meiner komplexen Ansichtsmodelle eine Fassade erstellen, und es könnte ein bisschen viel sein ...
Was ist Ihrer Meinung nach die "richtige" Art, diese Art von Architektur zu implementieren?
quelle
new
zum Erstellen anderer Ansichtsmodelle verwendet werden sollten, aber man denke an eine einfache MDI-Anwendung, bei der durch Klicken auf die Schaltfläche "Neues Dokument" oder das Menü ein neuer Tab hinzugefügt oder ein neues Fenster geöffnet wird. Die Shell / der Dirigent muss in der Lage sein, neue Instanzen von etwas zu erstellen , auch wenn es sich hinter einer oder mehreren Indirektionsebenen verbirgt.Antworten:
In der Tat sind diese beiden Lösungen schlecht.
Dies ist im Wesentlichen das Service Locator Pattern , bei dem es sich um ein Anti-Pattern handelt. Wenn Sie dies tun, können Sie nicht mehr verstehen, wovon das Ansichtsmodell tatsächlich abhängt, ohne auf seine private Implementierung zu achten, was das Testen oder Umgestalten sehr schwierig macht.
Dies ist weniger ein Anti-Pattern, sondern ein Code-Geruch. Im Wesentlichen erstellen Sie ein Parameterobjekt , aber der Sinn des PO-Refactoring-Musters besteht darin, sich mit Parametersätzen zu befassen, die häufig und an vielen verschiedenen Orten verwendet werden , während dieser Parameter immer nur einmal verwendet werden würde. Wie Sie bereits erwähnt haben, würde dies viel Code aufblähen, ohne dass dies wirklich von Vorteil wäre, und würde nicht gut mit vielen IoC-Containern zusammenspielen.
Tatsächlich übersehen beide oben genannten Strategien das Gesamtproblem, nämlich dass die Kopplung zwischen Ansichtsmodellen und Diensten zu hoch ist . Das einfache Ausblenden dieser Abhängigkeiten in einem Service-Locator oder Parameterobjekt ändert nicht , von wie vielen anderen Objekten das Ansichtsmodell abhängt.
Überlegen Sie, wie Sie eines dieser Ansichtsmodelle einem Unit-Test unterziehen würden. Wie groß wird Ihr Setup-Code sein? Wie viele Dinge müssen initialisiert werden, damit es funktioniert?
Viele Leute, die mit MVVM anfangen, versuchen, Ansichtsmodelle für einen gesamten Bildschirm zu erstellen , was grundsätzlich der falsche Ansatz ist. In MVVM dreht sich alles um Komposition , und ein Bildschirm mit vielen Funktionen sollte aus mehreren unterschiedlichen Ansichtsmodellen bestehen, von denen jedes nur von einem oder wenigen internen Modellen / Diensten abhängt. Wenn sie miteinander kommunizieren müssen, geschieht dies über Pub / Sub (Nachrichtenbroker, Ereignisbus usw.).
Eigentlich müssen Sie Ihre Ansichtsmodelle umgestalten, damit sie weniger Abhängigkeiten aufweisen . Wenn Sie dann einen aggregierten "Bildschirm" benötigen, erstellen Sie ein anderes Ansichtsmodell, um die kleineren Ansichtsmodelle zu aggregieren. Dieses aggregierte Ansichtsmodell muss nicht viel für sich tun, daher ist es auch ziemlich einfach zu verstehen und zu testen.
Wenn Sie dies richtig gemacht haben, sollte es schon beim Betrachten des Codes offensichtlich sein, da Sie kurze, prägnante, spezifische und testbare Ansichtsmodelle haben.
quelle
Ich könnte ein Buch darüber schreiben ... eigentlich bin ich es;)
Erstens gibt es keinen allgemein "richtigen" Weg, um Dinge zu tun. Sie müssen andere Faktoren berücksichtigen.
Möglicherweise sind Ihre Dienste zu feinkörnig. Eine bessere Lösung könnte darin bestehen, die Dienste mit Fassaden zu versehen, die die Schnittstelle bieten, die ein bestimmtes ViewModel oder sogar ein Cluster verwandter ViewModels verwenden würde.
Noch einfacher wäre es, die Services in einer einzigen Fassade zusammenzufassen, die von allen Ansichtsmodellen verwendet wird. Natürlich kann dies möglicherweise eine sehr große Schnittstelle mit vielen unnötigen Funktionen für das durchschnittliche Szenario sein. Aber ich würde sagen, es ist nicht anders als ein Nachrichtenrouter, der jede Nachricht in Ihrem System verarbeitet.
Tatsächlich habe ich gesehen, dass sich eine Menge Architekturen letztendlich zu einem Nachrichtenbus entwickelt haben, der um etwas wie das Muster des Ereignisaggregators herum aufgebaut ist. Dort ist das Testen einfach, da Ihre Testklasse nur einen Listener beim EA registriert und das entsprechende Ereignis als Antwort auslöst. Aber das ist ein fortgeschrittenes Szenario, das Zeit braucht, um sich zu entwickeln. Ich sage, fang mit der einheitlichen Fassade an und gehe von dort aus.
quelle
Warum nicht beides kombinieren?
Erstellen Sie eine Fassade und stellen Sie alle Dienste bereit, die Ihre Ansichtsmodelle verwenden. Dann können Sie eine einzige Fassade für alle Ihre Ansichtsmodelle ohne das falsche S-Wort haben.
Oder Sie können die Eigenschaftsinjektion anstelle der Konstruktorinjektion verwenden. Aber dann müssen Sie sicherstellen, dass diese richtig injiziert werden.
quelle