Model-View-Adapter statt Model-View-Controller: Vorteile und Praxis-Tipps

Autor / Redakteur: Tobias Boldte * / Sebastian Gerstl

Ein Model View Adapter (MVA) ist eine modernere Methode, um komplexe GUIs in C++ zum Einsatz in Qt umzusetzen, als der herkömmliche Ansatz eines Model View Controllers (MVC). Diese Beitrag zeigt auf, wo die Unterschiede liegen, und was Sie beim praktischen Einsatz beachten sollten.

Anbieter zum Thema

Im MVA-Pattern übernimmt die View die gesamte Schnittstelle zum Benutzer: Anzeige des Programmzustands und Entgegennahme von Benutzereingaben. Der Adapter sorgt für eine klare Trennung von Daten und Programmlogik auf der einen Seite (Model) und GUI / Benutzeroberfläche auf der anderen Seite (View).
Im MVA-Pattern übernimmt die View die gesamte Schnittstelle zum Benutzer: Anzeige des Programmzustands und Entgegennahme von Benutzereingaben. Der Adapter sorgt für eine klare Trennung von Daten und Programmlogik auf der einen Seite (Model) und GUI / Benutzeroberfläche auf der anderen Seite (View).
(Bild: MixedMode)

Um eine grafische Benutzeroberfläche in C++ Umzusetzen, hatte sich in der Vergangenheit der Einsatz eines Model-View-Controllers gut bewährt. Die Umsetzung des klassischen MVC-Patterns bot schon zahlreiche Vorteile:

  • 1. Die Benutzeroberfläche ist austauschbar, ohne dass die Programmlogik davon betroffen ist. Dadurch wird zum einen eine Umsetzung verschiedener Views für verschiedene Benutzergruppen deutlich erleichtert. Zum anderen ermöglicht dies eine spätere Portierung der graphischen Oberfläche (z.B. von Qt-Widgets nach QML).
  • 2. Verbesserte Wartbarkeit und Erweiterbarkeit der Applikation: Veränderungen des User Interfaces ohne Einfluss auf das Datahandling.
  • 3. Durch zentrale Datenhaltung wird Redundanz vermieden und der Anschluss an ein File IO zum Nachladen und Speichern von Settings wird vereinfacht.

Allerdings passt dieser Ansatz in seiner ursprünglichen Definition nicht mehr zu modernen Grafikbibliotheken wie zum Beispiel Qt. Das schränkt die Komplexität solcher GUIs ein und macht es schwer, die Dateneingaben und -darstellungen in der Software strikt getrennt zu halten.

Eine neue Herangehensweise ist der Model-View-Adapter (MVA). Wie sich eine moderne GUI mittels eines MVA-Ansatzes mit C++ verwirklichen lässt, wurde in einem früheren Beitrag bereits dargelegt. Für den Einsatz in einer modernen Grafikbibliothek wie Qt bietet die Umsetzung des MVA-Patterns mit einem Adapter als zentrale Vermittlerschicht darüber hinaus zahlreiche weitere Vorteile:

Vorteile des MVA-Ansatzes zur GUI-Erstellung

  • 1. Bei der gewählten Architektur ist das Basis-Framework bestehend aus View Interface, Adapter und Model Interface für weitere Applikationsprojekte zu 100 % wiederverwendbar.
  • 2. Durch eine noch striktere Trennung zwischen Modell und View wird die Wartbarkeit und Erweiterbarkeit der Applikation weiter verbessert.
  • 3. Die Undo- bzw. Redo-Funktionalität muss als Teil des Adapters nur einmal umgesetzt werden. Von da an wird sie dem Entwickler sozusagen geschenkt. So wird die Umsetzung für lineare Undo-Entries, die nicht gruppiert werden müssen, ohne eine weitere Zeile Code automatisch durch den Adapter umgesetzt.
  • 4. Auch das File IO muss als Zwischenschicht des Modells nur einmal umgesetzt werden. Danach müssen für Neuimplementierungen nur noch die Streaming-Operatoren von neu hinzugekommenen Datentypen implementiert werden, für die das File IO benutzt wird. Die Implementierung eines einfachen File-Zugriffs, die nur von einem File Identifier pro Datenelement abhängt, wird dem Entwickler also geschenkt.
  • 5. Durch die zentrale Programmsteuerung über den Adapter bietet diese Umsetzung eine Möglichkeit, dort ein Testframework mit tracing und externer Programmsteuerung durch automatisierte Tests zu installieren. Solch ein Testframework könnte eine gute Alternative zu einem GUI-gestützten Testframework sein.
  • 6. Durch die extrem schlanke Implementierung des Modells wird der Aufwand für das Hinzufügen eines weiteren Datenelements in das Modell auf ein Minimum reduziert. Durch die Vermeidung zahlreicher Ableitungen wird die Umsetzung dabei wie zuvor beschrieben auf nur zwei zentrale Stellen im Code reduziert: Eine Code-Zeile im Konstruktor, in der das gesamte Datenhandling des Datentyps (inklusive festgelegt wird und eine Stelle in der einzigen abgeleiteteten Funktion, in der das Controlling implementiert werden kann. Dieser Vorteil tritt besonders klar bei der Umsetzung komplexer Programmsteuerungen hervor, die oftmals über besonders große Parametersätze verfügen. An dieser Stelle kann viel Code und damit Wartungsaufwand gespart werden.
  • 7. Durch das klare, sehr lineare Konzept kann dieser Ansatz auch die Grundlage für das dynamische Laden einer kompletten settings-basierten GUI aus einem File (z.B. über eine XML-Description) bieten. Solch eine dynamisch angelegte GUI gibt dem Benutzer die Möglichkeit, einen auf diese Weise dynamisch angepassten Parametersatz mit graphischer Oberfläche zu managen.
  • 8. Gegenüber älteren Standardansätzen, in denen für jede View ein eigenes dafür angepasstes Modell angelegt werden musste, bietet dieser Ansatz mit seinen 1 bis n (key, value)-pairs pro Modell und View deutliche Vorteile durch seine Flexibilität. Dadurch können die Modelle nach dem Gesichtspunkt der Applikationsstruktur angelegt werden und es ist möglich, die Keys eines Modells mit den Keys verschiedenartiger Views zu verbinden. Unter Umständen kann es auch sinnvoll sein, die Datenelemente einer View mit Datenelementen aus verschiedenen Models zu verbinden.

Praktische Tipps für den MVA-Ansatz

Bei der Umsetzung von Modell und View Interface sollte generell auf eine möglichst schlanke Implementierung geachtet werden. Durch maximale Reduktion virtueller Funktionen in den Interfaces (Modell und View) können viele Lines of Code eingespart werden. Der Code wird dadurch verständlicher und besser wartbar. Der Verzicht auf Spezialisierung wird dabei erzielt, indem die Kommunikation zwischen View und Modell für alle Datentypen der Datenelemente mit dem gleichen Schema abläuft.

Das Modell hat nur eine virtuelle Funktion, die die zentrale Kontrollfunktion umsetzt. Dabei kann in dieser Funktion nach der automatischen Validitätsprüfung (range check des Datenelements) entschieden werden, wie mit einer eingehenden Änderung eines Datenelements verfahren werden soll, bevor sie im Modell übernommen wird. Dazu zählen z.B. Konsistenzcheck, Aufruf von Programmlogik etc.

Der Rest der Umsetzung der Modells kann für jedes anzulegende Datenelement durch einen Funktionsaufruf in der Initialisierung umgesetzt werden. Dabei wird der Datentyp, der key sowie der default value für die Initialisierung und den Reset übergeben. Außerdem kann das Datenelement durch das Setzen eines Undo strings für das Undo Framework registriert werden. Die Aufnahme des Datenelements ins Standard File IO kann durch das Setzen eines File Identifier-Strings umgesetzt werden. Darüber hinaus kann ein Objekt für die Validitätsprüfung des einzelnen Datenelements gesetzt werden, das später automatisch den Range überprüfen oder ein ein automatisches Wrap around in einem spezifizierten Range umsetzen kann.

Maschinenzugriffe auf das Modell sollten günstigerweise über den Adapter laufen. Dazu kann zum Beispiel eine Control-Schnittstelle dienen, die fast identisch mit der View- Schnittstelle ist. Unter Umständen könnte es auch sinnvoll sein, auch die Programmlogik selbst nicht dem Modell zuzuordnen, sondern als eigenen Layer hinter einer solchen Control-Schnittstelle zu verstecken und wie eine View an das Modell anzuhängen.

Ausbaufähigkeit eines GUIs

Wie im zuvor bereits erwähntem Artikel aufgezeigt wird, kann durch die klare Trennung der Datenhaltung von der GUI zum einen die Wartbarkeit und zum anderen Erweiterbarkeit des Quellcodes verbessert werden. Die dort aufgeführte Version wurde in Projekten entwickelt, in denen das Hauptaugenmerk auf der Manipulation einzelner, voneinander unabhängiger Datenelemente verschiedener Typen lag. Daher war es nie ein Problem, wenn bei Datenänderungen das ganze Datenelement verändert werden musste.

Der Autor: Tobias Boldte.
Der Autor: Tobias Boldte.
(Bild: Mixed Mode GmbH)

Der nächste Schritt eines weiteren Ausbaus wird daher im Einbau von Listen als Standard-Datenelemente im Modell und deren Connections an Einzelemente und entsprechenden Listen (und Teillisten) in den Views liegen. Dies wird dann auch den Einsatz größerer ListViews und Trees mit über 10.000 Einträgen zu ermöglichen. Hierzu bedarf es einer Spezialimplementierung bzw. Erweiterung des bisherigen Vorgehens, da hierfür natürlich kein komplettes Update der Listen für eine Veränderung einzelner Elemente durchgeführt werden darf.

Der Autor

* Tobias Boldte ist Softwareentwickler mit Fokus auf C++, Qt und OpenGL bei der Mixed Mode GmbH

(ID:45486856)