MVVM ist ein Pflaster für schlecht gestaltete Datenbindungsschichten. Insbesondere in der WPF / silverlight / WP7-Welt hat es aufgrund der Einschränkungen bei der Datenbindung in WPF / XAML viel Verwendung gefunden.
Von nun an gehe ich davon aus, dass wir über WPF / XAML sprechen, da dies die Dinge klarer machen wird. Schauen wir uns einige der Mängel an, die MVVM in WPF / XAML zu beheben versucht.
Datenform vs UI-Form
Die 'VM' in MVVM erstellt eine Reihe von in C # definierten Objekten, die einer Reihe von in XAML definierten Präsentationsobjekten zugeordnet werden. Diese C # -Objekte sind in der Regel über DataContext-Eigenschaften für Präsentationsobjekte mit XAML verbunden.
Infolgedessen muss das Ansichtsmodell-Objektdiagramm dem Präsentationsobjektdiagramm Ihrer Anwendung zugeordnet werden. Das heißt nicht, dass die Zuordnung eins zu eins erfolgen muss. Wenn jedoch ein Listensteuerelement in einem Fenstersteuerelement enthalten ist, muss es eine Möglichkeit geben, vom DataContext-Objekt des Fensters zu einem Objekt zu gelangen, das die Daten dieser Liste beschreibt.
Das Ansichtsmodell-Objektdiagramm entkoppelt das Modellobjektdiagramm erfolgreich vom UI-Objektdiagramm, jedoch auf Kosten eines zusätzlichen Ansichtsmodell-Layers, der erstellt und verwaltet werden muss.
Wenn ich einige Daten von Bildschirm A auf Bildschirm B verschieben möchte, muss ich mit Ansichtsmodellen herumspielen. Für einen Geschäftsmann ist dies eine Änderung in der Benutzeroberfläche. Es sollte rein in der Welt von XAML stattfinden. Leider kann es selten. Schlimmer noch, je nachdem, wie die Ansichtsmodelle strukturiert sind und wie aktiv sich die Daten ändern, kann ein beträchtlicher Teil der Datenumleitung erforderlich sein, um diese Änderung durchzuführen.
Umgehen der unexpressiven Datenbindung
WPF / XAML-Bindungen sind nicht ausreichend aussagekräftig. Grundsätzlich müssen Sie einen Weg angeben, um zu einem Objekt zu gelangen, einen Eigenschaftspfad zum Durchlaufen und Bindungskonverter, um den Wert der Dateneigenschaft an die Anforderungen des Präsentationsobjekts anzupassen.
Wenn Sie eine Eigenschaft in C # an etwas Komplexeres binden müssen, haben Sie im Grunde kein Glück. Ich habe noch nie eine WPF-App ohne einen Bindungskonverter gesehen, der aus wahr / falsch Sichtbar / Reduziert wurde. Viele WPF-Apps haben auch NegatingVisibilityConverter oder ähnliches, das die Polarität umkehrt. Dies sollte Alarmglocken auslösen.
MVVM enthält Richtlinien für die Strukturierung Ihres C # -Codes, mit denen diese Einschränkung behoben werden kann. Sie können eine Eigenschaft in Ihrem Ansichtsmodell mit dem Namen SomeButtonVisibility verfügbar machen und sie einfach an die Sichtbarkeit dieser Schaltfläche binden. Ihre XAML ist jetzt nett und hübsch ... aber Sie haben sich zum Angestellten gemacht - jetzt müssen Sie Bindungen an zwei Stellen (der Benutzeroberfläche und dem Code in C #) anzeigen und aktualisieren, wenn sich Ihre Benutzeroberfläche weiterentwickelt. Wenn Sie dieselbe Schaltfläche auf einem anderen Bildschirm benötigen, müssen Sie eine ähnliche Eigenschaft in einem Ansichtsmodell verfügbar machen, auf das dieser Bildschirm zugreifen kann. Schlimmer noch, ich kann nicht nur auf die XAML schauen und sehen, wann die Schaltfläche nicht mehr sichtbar ist. Sobald Bindungen nicht mehr ganz einfach sind, muss ich Detektivarbeit im C # -Code leisten.
Der Zugriff auf Daten ist sehr umfangreich
Da Daten in der Regel über DataContext-Eigenschaften in die Benutzeroberfläche eingegeben werden, ist es schwierig, globale oder Sitzungsdaten in der gesamten App konsistent darzustellen.
Die Idee des "derzeit angemeldeten Benutzers" ist ein gutes Beispiel - dies ist häufig eine wirklich globale Sache in einer Instanz Ihrer App. In WPF / XAML ist es sehr schwierig, den globalen Zugriff auf den aktuellen Benutzer auf konsistente Weise sicherzustellen.
Ich möchte das Wort "CurrentUser" in Datenbindungen frei verwenden, um auf den aktuell angemeldeten Benutzer zu verweisen. Stattdessen muss ich sicherstellen, dass mir jeder DataContext eine Möglichkeit gibt, zum aktuellen Benutzerobjekt zu gelangen. MVVM kann dies ausgleichen, aber die Ansichtsmodelle werden ein Chaos sein, da alle von ihnen Zugriff auf diese globalen Daten gewähren müssen.
Ein Beispiel, in dem MVVM umfällt
Angenommen, wir haben eine Liste von Benutzern. Neben jedem Benutzer soll die Schaltfläche "Benutzer löschen" angezeigt werden, jedoch nur, wenn der aktuell angemeldete Benutzer ein Administrator ist. Benutzer dürfen sich auch nicht selbst löschen.
Ihre Modellobjekte sollten nichts über den aktuell angemeldeten Benutzer wissen - sie stellen lediglich Benutzerdatensätze in Ihrer Datenbank dar, aber irgendwie muss der aktuell angemeldete Benutzer Datenbindungen in Ihren Listenzeilen ausgesetzt sein. MVVM schreibt vor, dass wir für jede Listenzeile, die den aktuell angemeldeten Benutzer mit dem durch diese Listenzeile dargestellten Benutzer zusammensetzt, ein Ansichtsmodellobjekt erstellen und dann für dieses Ansichtsmodellobjekt eine Eigenschaft mit dem Namen "DeleteButtonVisibility" oder "CanDelete" verfügbar machen sollen (abhängig von Ihren Gefühlen) über Bindungskonverter).
Dieses Objekt wird in den meisten anderen Aspekten einem Benutzerobjekt sehr ähnlich sein - es muss möglicherweise alle Eigenschaften des Benutzermodellobjekts widerspiegeln und Aktualisierungen an diese Daten weiterleiten, wenn sie sich ändern. Dies fühlt sich wirklich unangenehm an - MVVM macht Sie zu einem Angestellten, indem es Sie zwingt, dieses benutzerähnliche Objekt zu verwalten.
Beachten Sie, dass Sie wahrscheinlich auch die Eigenschaften Ihres Benutzers in einer Datenbank, im Modell und in der Ansicht darstellen müssen. Wenn Sie eine API zwischen sich und Ihrer Datenbank haben, ist dies schlimmer: Sie sind in der Datenbank, dem API-Server, dem API-Client, dem Modell und der Ansicht dargestellt. Ich würde wirklich zögern, ein Entwurfsmuster zu übernehmen, das eine weitere Ebene hinzufügt, die jedes Mal berührt werden muss, wenn eine Eigenschaft hinzugefügt oder geändert wird.
Schlimmer noch, diese Ebene skaliert mit der Komplexität Ihrer Benutzeroberfläche und nicht mit der Komplexität Ihres Datenmodells. Häufig werden dieselben Daten an vielen Stellen und in Ihrer Benutzeroberfläche dargestellt - dies fügt nicht nur eine Ebene hinzu, sondern eine Ebene mit viel zusätzlicher Oberfläche!
Wie es hätte sein können
In dem oben beschriebenen Fall möchte ich sagen:
<Button Visibility="{CurrentUser.IsAdmin && CurrentUser.Id != Id}" ... />
CurrentUser wird in meiner App global für alle XAML-Dateien verfügbar gemacht. ID verweist auf eine Eigenschaft im DataContext für meine Listenzeile. Die Sichtbarkeit wird automatisch vom Booleschen Wert konvertiert. Alle Aktualisierungen von Id, CurrentUser.IsAdmin, CurrentUser oder CurrentUser.Id lösen eine Aktualisierung der Sichtbarkeit dieser Schaltfläche aus. Kinderleicht.
Stattdessen zwingt WPF / XAML seine Benutzer, ein vollständiges Durcheinander zu erstellen. Soweit ich das beurteilen kann, haben einige kreative Blogger diesem Chaos einen Namen gegeben, und dieser Name war MVVM. Lass dich nicht täuschen - es ist nicht in der gleichen Klasse wie die GoF-Designmuster. Dies ist ein hässlicher Hack, um ein hässliches Datenbindungssystem zu umgehen.
(Dieser Ansatz wird manchmal als "Functional Reactive Programming" bezeichnet, falls Sie weitere Informationen benötigen.)
Abschließend
Wenn Sie in WPF / XAML arbeiten müssen, empfehle ich MVVM immer noch nicht.
Sie möchten, dass Ihr Code so strukturiert ist, wie es oben im Beispiel "Wie die Dinge hätten sein können" beschrieben wurde - das Modell wird direkt in der Ansicht angezeigt, mit komplexen Datenbindungsausdrücken und flexiblen Werteberechnungen. Es ist viel besser - lesbarer, beschreibbarer und wartbarer.
MVVM weist Sie an, Ihren Code ausführlicher und weniger wartbar zu strukturieren.
Erstellen Sie statt MVVM ein paar Dinge, die Ihnen dabei helfen, die gute Erfahrung zu schätzen: Entwickeln Sie eine Konvention, um den globalen Status konsistent für Ihre Benutzeroberfläche verfügbar zu machen. Bauen Sie sich Werkzeuge aus Bindungskonvertern, MultiBinding usw. auf, mit denen Sie komplexere Bindungsausdrücke ausdrücken können. Bauen Sie sich eine Bibliothek mit Bindungskonvertern auf, um häufige Fälle von Nötigung weniger schmerzhaft zu machen.
Noch besser - ersetzen Sie XAML durch etwas aussagekräftigeres. XAML ist ein sehr einfaches XML-Format zum Instanziieren von C # -Objekten - eine ausdrucksstärkere Variante wäre nicht schwer zu finden.
Meine andere Empfehlung: Verwenden Sie keine Toolkits, die solche Kompromisse erzwingen. Sie beeinträchtigen die Qualität Ihres Endprodukts, indem sie Sie in Richtung MVVM treiben, anstatt sich auf Ihre Problemdomäne zu konzentrieren.