Zusammenfassung
- Die Verwendung aller Muster ist situativ und der Vorteil (falls vorhanden) liegt immer in der verringerten Komplexität.
- MVVM zeigt uns, wie Sie Verantwortlichkeiten auf Klassen in einer GUI-Anwendung verteilen.
- ViewModel projiziert die Daten aus dem Modell in ein Format, das zur Ansicht passt.
- Für triviale Projekte ist MVVM nicht erforderlich. Es reicht aus, nur die Ansicht zu verwenden.
- Bei einfachen Projekten ist die Aufteilung von ViewModel / Modell möglicherweise nicht erforderlich, und die Verwendung eines Modells und einer Ansicht ist ausreichend.
- Model und ViewModel müssen nicht von Anfang an vorhanden sein und können bei Bedarf eingeführt werden.
Wann man Muster verwendet und wann man sie vermeidet
Für eine ausreichend einfache Anwendung ist jedes Entwurfsmuster übertrieben. Angenommen, Sie schreiben eine GUI-Anwendung, die eine einzelne Schaltfläche anzeigt, die beim Drücken "Hallo Welt" anzeigt. In diesem Fall erhöhen Entwurfsmuster wie MVC, MVP und MVVM die Komplexität erheblich, ohne jedoch einen Mehrwert zu bieten.
Im Allgemeinen ist es immer eine schlechte Entscheidung, ein Designmuster einzuführen, nur weil es etwas passt. Entwurfsmuster sollten verwendet werden, um die Komplexität zu reduzieren, entweder indem die Gesamtkomplexität direkt reduziert wird oder indem ungewohnte Komplexität durch vertraute Komplexität ersetzt wird. Wenn das Entwurfsmuster die Komplexität auf keine dieser beiden Arten reduzieren kann, verwenden Sie es nicht.
Um die vertraute und unbekannte Komplexität zu erklären, nehmen Sie die folgenden 2 Zeichenfolgen:
- "D. € | Ré% dfà? C"
- "CorrectHorseBatteryStaple"
Während die zweite Zeichenfolge doppelt so lang ist wie die erste, ist sie leichter zu lesen, schneller zu schreiben und leichter zu merken als die erste Folge, weil sie vertrauter ist. Gleiches gilt für bekannte Muster im Code.
Dieses Problem gewinnt eine andere Dimension, wenn Sie bedenken, dass die Vertrautheit vom Leser abhängt. Einige Leser werden feststellen, dass "3.14159265358979323846264338327950" leichter zu merken ist als eines der oben genannten Passwörter. Einige werden nicht. Wenn Sie also eine MVVM-Variante verwenden möchten, versuchen Sie, eine zu verwenden, die die häufigste Form in der von Ihnen verwendeten Sprache und dem verwendeten Framework widerspiegelt.
MVVM
Lassen Sie uns anhand eines Beispiels auf das Thema MVVM eingehen. MVVM zeigt uns, wie Verantwortlichkeiten zwischen Klassen in einer GUI-Anwendung (oder zwischen Ebenen - dazu später mehr) verteilt werden, mit dem Ziel, eine kleine Anzahl von Klassen zu haben und gleichzeitig die Anzahl der Verantwortlichkeiten pro Klasse klein und klar zu halten.
'Richtig' MVVM setzt zumindest eine mäßig komplexe Anwendung voraus, die sich mit Daten befasst, die von "irgendwo" stammen. Es kann die Daten aus einer Datenbank, einer Datei, einem Webdienst oder aus einer Vielzahl anderer Quellen abrufen.
Beispiel
In unserem Beispiel haben wir 2 Klassen Viewund Model, aber nein ViewModel. Der ModelWraps eine CSV-Datei, die er beim Start liest und beim Herunterfahren der Anwendung speichert, mit allen Änderungen, die der Benutzer an den Daten vorgenommen hat. Dies Viewist eine Window-Klasse, die die Daten aus Modeleiner Tabelle anzeigt und es dem Benutzer ermöglicht, die Daten zu bearbeiten. Der CSV-Inhalt könnte ungefähr so aussehen:
ID, Name, Price
1, Stick, 5$
2, Big Box, 10$
3, Wheel, 20$
4, Bottle, 3$
Neue Anforderungen: Preis in Euro anzeigen
Jetzt werden wir gebeten, eine Änderung an unserer Anwendung vorzunehmen. Die Daten bestehen aus einem zweidimensionalen Raster, das bereits eine "Preisspalte" enthält, die einen Preis in USD enthält. Wir müssen eine neue Spalte hinzufügen, in der die Preise in Euro zusätzlich zu denen in USD angezeigt werden, basierend auf einem vordefinierten Wechselkurs. Das Format der CSV-Datei darf sich nicht ändern, da andere Anwendungen mit derselben Datei arbeiten und diese anderen Anwendungen nicht unter unserer Kontrolle stehen.
Eine mögliche Lösung besteht darin, die neue Spalte einfach zur ModelKlasse hinzuzufügen . Dies ist nicht die beste Lösung, da Modelhierdurch alle Daten gespeichert werden, die der CSV zur Verfügung gestellt werden - und wir keine neue Euro-Preisspalte in der CSV wünschen. Die Änderung an Modelwäre also nicht trivial, und es wäre auch schwieriger zu beschreiben, was die Model-Klasse tut, was ein Code-Geruch ist .
Wir könnten die Änderung auch in der vornehmen View, aber unsere aktuelle Anwendung verwendet Datenbindung, um die Daten direkt anzuzeigen, wie von unserer ModelKlasse bereitgestellt . Da unser GUI-Framework es uns nicht erlaubt, eine zusätzliche berechnete Spalte in eine Tabelle einzufügen, wenn die Tabelle an eine Datenquelle gebunden ist, müssten wir eine wesentliche Änderung Viewan vornehmen, damit dies funktioniert, was die ViewKomplexität erheblich erhöht .
Einführung in das ViewModel
Es gibt keine ViewModelin der Anwendung, da Modeldie Daten bis jetzt genau so dargestellt werden, wie es die CSV benötigt, und auch so, wie sie Viewbenötigt werden. Ein ViewModelZwischenraum zu haben, wäre ohne Zweck komplexer geworden. Aber jetzt, da die ModelDaten nicht mehr so dargestellt werden, wie sie Viewbenötigt werden, schreiben wir eine ViewModel. Das ViewModelprojiziert die Daten der Modelso, dass die Vieweinfach sein können. Zuvor hat die ViewKlasse die Klasse abonniert Model. Jetzt ViewModelabonniert die neue Klasse die ModelKlasse und stellt die ModelDaten der Klasse der View- zur Verfügung. In einer zusätzlichen Spalte wird der Preis in Euro angezeigt. Das Viewweiß der nicht mehrModelJetzt kennt es nur noch das ViewModel, was vom Standpunkt des ViewAussehens aus genauso aussieht Modelwie zuvor - außer dass die exponierten Daten eine neue schreibgeschützte Spalte enthalten.
Neue Anforderungen: andere Art, die Daten zu formatieren
Die nächste Kundenanforderung lautet, dass wir die Daten nicht als Zeilen in einer Tabelle anzeigen sollen, sondern stattdessen die Informationen jedes Elements (auch als Zeile bezeichnet) als Karte / Box anzeigen und 20 Boxen in einem 4x5-Raster auf dem Bildschirm anzeigen sollen, wobei 20 angezeigt werden Boxen auf einmal. Weil wir die Logik des ViewEinfachen beibehalten haben , ersetzen wir das Einfache Viewvollständig durch eine neue Klasse, die den Wünschen des Kunden entspricht. Natürlich gibt es einen anderen Kunden, der den alten bevorzugt View, also müssen wir jetzt beide unterstützen. Da die gesamte gängige Geschäftslogik bereits vorhanden ViewModelist, ist dies kein großes Problem. Wir können dieses Problem lösen, indem wir die View-Klasse in umbenennen TableViewund eine neue schreibenCardViewKlasse, die die Daten in einem Kartenformat anzeigt. Wir müssen auch einen Klebercode schreiben, der möglicherweise ein Oneliner in der Startfunktion ist.
Neue Anforderungen: dynamischer Wechselkurs
Die nächste Kundenanfrage ist, dass wir den Wechselkurs aus dem Internet beziehen, anstatt einen vordefinierten Wechselkurs zu verwenden. Dies ist der Punkt, an dem wir meine frühere Aussage über "Ebenen" erneut betrachten. Wir ändern unsere ModelKlasse nicht, um einen Wechselkurs bereitzustellen. Stattdessen schreiben (oder finden) wir eine völlig unabhängige zusätzliche Klasse, die den Wechselkurs liefert. Diese neue Klasse wird Teil der Modellebene, und wir ViewModelkonsolidieren die Informationen des CSV-Modells und des Wechselkursmodells, die sie dann dem Modell präsentieren View. Für diese Änderung müssen die alte Model-Klasse und die View-Klasse nicht einmal berührt werden. Nun, wir müssen die Model-Klasse in umbenennen CsvModelund rufen die neue Klasse auf ExchangeRateModel.
Wenn wir das ViewModel nicht eingeführt hätten, sondern stattdessen bis jetzt darauf gewartet hätten, wäre der Arbeitsaufwand für die Einführung des ViewModel jetzt höher, da wir sowohl vom als auch vom Viewund zum ModelVerschieben erhebliche Mengen an Funktionen entfernen müssen die Funktionalität in die ViewModel.
Nachwort zu Unit Tests
Der Hauptzweck von MVVM besteht nicht darin, dass der Code im Modell und im ViewModel unter Unit Test gestellt werden kann. Der Hauptzweck von MVVM besteht darin, dass der Code in Klassen mit einer kleinen Anzahl genau definierter Verantwortlichkeiten unterteilt wird. Einer von mehreren Vorteilen eines Codes, der aus Klassen mit einer geringen Anzahl genau definierter Verantwortlichkeiten besteht, besteht darin, dass es einfacher ist, den Code einem Unit-Test zu unterziehen. Ein viel größerer Vorteil ist, dass der Code leichter zu verstehen, zu warten und zu ändern ist.