9 Minuten Lesezeit (1804 Worte)

MVC - Variationen und Implementierung

Obwohl das Design-Pattern MVC (Model View Controler) bereits 1979 von Trygve Reenskaug veröffentlicht wurde, ist es auch heute noch - in unterschiedlichsten Variation im Einsatz. Dabei macht mir persönlich die Kreativität bei der Auslegung des Patterns zu schaffen: Das Akronym MVC wird meiner Erfahrung nach nicht sehr trennscharf verwendet. Daher möchte ich DAS MVC Pattern im Folgenden noch einmal beschreiben und die Motivation für die Variationen MVP (Model View Presenter) und MVVM (Model View ViewModel) darlegen.

Die Implementierung grafischer Benutzeroberflächen gehört aufgrund ihrer Komplexität zu den anspruchsvollen Aufgaben des Anwendungsprogrammierers. Die Vielfalt der Anforderungen auf der einen Seite und die Vielfalt der Lösungsmöglichkeiten andererseits führt nicht selten zu einer derartigen Verwirrung, dass der Entwickler seinen eigenen Code nicht mehr versteht.

Die Verständlichkeit des eigenen Codes ist eine grundlegende Voraussetzung für die Implementierung. In einem Team ist es darüber hinaus wichtig, dass auch andere den Code verstehen: Die Verständlichkeit hat eine überragende Bedeutung.

Daneben gibt es weitere Wünsche an den Code: Der Quellcode ist Änderungen unterworfen: selbst wenn die Anforderungen stabil sind, ist die Implementierung selten unmittelbar fehlerfrei. Fehler möchte der Entwickler gern an einer Stelle im Code finden und beheben, ohne dass andere Teile des Codes betroffen sind. Der Wunsch nach Änderbarkeit bedeutet in jedem Fall, dass lokale Änderungen des Programms nur lokale Auswirkungen haben. 

Neben den genannten Wünschen gibt es Randbedingungen, die die Lösung sehr stark beeinflussen. Die Auswahl der Hardware, des Roll-Out-Verfahrens, des Technologie-Stacks sind nur einige Beispiele, die den Lösungsraum für die Software wesentlich bestimmen. Allgemein gültige Anforderungen an die Software sind als Qualitätsmerkmale in der Norm ISO 9126 benannt.

Zerlegung in Domain Model, Persistenz und GUI

Abbildung 1: Interaktion zwischen GUI und Domain Model

Die Software-Architektur hat die Aufgabe, die Randbedingungen und Wünsche zu erfüllen. Der erste Schritt der Architektur-Entwicklung ist die System-Analyse mit dem Ziel der Zerlegung des Systems (siehe Abbildung 1). 

Vielleicht überraschend: die zentrale Komponente meines Systems mit GUI stellt das Domain Model dar. Das Domain-Model ist die Abstraktion des realen Systems. Das Domain-Model eines Autoverleihs besteht beispielsweise aus Autos, Kunden, Geschäftsstellen usf. Dabei enthält das Domain-Model nur solche Eigenschaften, die für die Prozesse der Datenverarbeitung erforderlich sind.

Die Eigenschaften des Domain Models setzen sich zusammen aus Zustand und Verhalten. Der Zustand eines Domain-Models besteht aus Daten. Der Zugriff auf diese Zustands-Daten wird in Java üblicherweise durch Get-Methoden gewährleistet. Das Verhalten des Models besteht aus einem Auslöser, einer Aktion und der daraus folgenden Zustandsänderung. Aus Sicht der Implementierung wird das Verhalten des Models dadurch provoziert, dass eine Do-Methode des Models aufgerufen wird. In Folge dieses Methoden-Aufrufs wird sich der Zustand des Models verändern. Eine Do-Methode beim Autoverleih kann die Herausgabe eines Leihwagens sein. Die passende Methode könnte herausgabeFahrzeug lauten. Als Argument eine ID des Kunden und eine des Fahrzeugs. Durch diese Methode verändert sich der Status des Fuhrparks.

Zu dem Domain Model gibt es die Persistenz. Mit Hilfe dieser Komponente werden die Aktionen des Models und der jeweilige Zustand dauerhaft gespeichert. Die Struktur dieser Komponente wird im Folgenden nicht weiter berücksichtigt.

Dagegen wollen wir die Komponenten der GUI  (Abbildung 1) detailliert diskutiert. Aufgabe der GUI ist es, den Zustand des Domain Models zu ermitteln und Aktionen des Models auszuführen.

Die GUI selbst hat ebenfalls einen Zustand: Das sind beispielsweise die Daten, die dem Anwender angezeigt werden. Auch gibt es ein Verhalten. Das Verhalten wird durch den Anwender ausgelöst, der beispielsweise ein Bedienelement betätigt.

Die Komponenten Domain Model, GUI und Persistenz sind die ersten Ergebnisse des Architektur-Entwurfs. Diese Komponenten sind als Einheiten in der Implementierung zu berücksichtigen. In objektorientierten Programmiersprachen werden diese gekapselt. Eine Interaktion mit der Außenwelt findet nur durch wohldefinierte Schnittstellen statt.

Neben der Identifikation der Komponenten ist die Interaktion der Komponenten für die weitere Zerlegung wichtig.

Model View ControIer (MVC)

Abbildung 2: Das Observer-Pattern für Domain Model und GUI

Oftmals erfolgt der Informationsfluss zwischen den Get-Methoden des Domain Models zum Zustand der GUI-Komponente (Abbildung 1) nach der folgenden Regel: Immer dann, wenn der Zustand des Domain-Models sich ändert muss der Zustand der GUI angepasst werden.

Die Kenntnis, wann sich der Domain-Zustand ändert, liegt in der Verantwortung des Domain-Models. Die Zustands-Änderung wird durch die Do-Methoden provoziert. Im Regelfall führt der Aufruf der Do-Methoden zu einer Veränderung des Zustands. Das legt die Verwendung eines Observer-Patterns nahe: Die GUI-Komponente oder die GUI-Komponenten stellen den Observer dar (Abbildung 2).

Die nächste Überlegung betrifft den Informationsfluss der GUI zum Model (Abbildung 1). Der Anwender betätigt eine Schaltfläche – zum Beispiel für das Ausleihen eines Fahrzeugs. Daraufhin ruft die GUI eine Do-Methode des Domain Models auf, um mitzuteilen welches Auto von welchem Kunden ausgeliehen wurde. Die GUI weist ein Verhalten auf: immer dann, wenn der Anwender eine Aktion ausführt, reagiert die GUI mit entsprechenden Aktionen.

Für die Zerlegung der GUI ist es hilfreich, die verwendete GUI-Bibliothek zu analysieren. Oftmals wird durch die Bibliothek bereits eine Zerlegung der GUI in unterschiedliche Komponenten intendiert.

Abbildung 3: Struktur des Design-Patterns Model View Controler

Die Trennung von Gestaltung (View) und Verhalten (Controler) der GUI ist dabei häufiger anzutreffen. Diese Aufteilung erscheint sinnvoll, da die Gestaltung (an welcher Stelle ist ein Eingabefeld zu platzieren, welchen Abstand soll es von dem zugehörigen Label haben, welche Farbe, welche Schriftart sollen verwendet werden?) naturgemäß häufig Gegenstand von Diskussionen ist. Das Architektur-Ziel Änderbarkeit sollte bei der View besondere Berücksichtigung finden.

Bestandteile, die eine hohe Änderungswahrscheinlichkeit haben, sollten separiert werden. Die Ausführung der Änderungen soll möglichst einfach sein. Meta-Sprachen für die View-Gestaltung haben sich bewährt. Java Server Faces oder Angular verwendet angereicherte XHTML-Dokumente. JavaFX setzt FXML für die Definition der View ein. Zu älteren Bibliotheken wie zum Beispiel Java Swing gibt es Erweiterungen, die in ähnlicher Weise funktionieren.

Damit ist die View als Komponente der GUI identifiziert und durch die eingesetzte Bibliothek definiert. Für das Verhalten der GUI wird dann (mindestens) eine zweite Komponente benötigt. Diese hat die Verantwortung für das Verhalten der GUI und kann daher als Controler bezeichnet werden.

Damit haben wir die Komponenten (Domain) Model, View und Controler des gleichnamigen MVC-Design-Patterns motiviert. Die View nimmt die Rolle des Observers ein (Abbildung 3). Das Domain Model informiert die View, wenn sein Zustand verändert wurde. Die View erfragt die Attribute des Domain Models mit dessen Get-Methoden. Der Controler hat die Aufgabe, die Änderungs-Wünsche des Anwenders gegenüber dem Domain Model auszuführen. Die Verbindung von Controler und View wird nicht spezifiziert.

MVP - Model View Presenter

Abbildung 4: Taschenrechner als Swing-Anwendung

Dieser Abschnitt zeigt eine Implementierung des MVC-Design-Patterns. Der fachliche Hintergrund dieser Anwendung ist ein Taschenrechner. 

Der Anwender gibt über die Schaltflächen Zahlen und eine binäre Operation ein. Der Taschenrechner berechnet das Ergebnis.

Die erste Herausforderung ist die Identifikation des Domain Models. Klar ist, dass die fachliche Logik in der Lage sein muss, Operationen mit den angegebenen Grundrechenarten durchzuführen. In der Domain kann also die Addition 1+2 durchgeführt und das Ergebnis 3 ermittelt werden.

Aber: In der Darstellung des Taschenrechners ist nur Platz für eine Zahl. Es bleibt die Aufgabe, den Zustand des Anwender-Dialogs mit dem Taschenrechner - also eine zweite Zahl und den Operator - zu merken.

Eine Möglichkeit, Logik der Kommunikation zwischen Anwender und GUI zu verwalten, ist die Einführung eines Presenters. Der Presenter nimmt im MVC die Rolle des Models ein. Neben den reich fachlichen Schnittstellen, verfügt der Presenter auch über Logik zur Benutzerführung. Der Presenter delegiert nicht nur Zugriff auf die Daten des Domain Models sondern bereitet diese Daten auch so auf, dass sie in der GUI oder vom Domain-Model verwendet werden können.

Abbildung 5: Presenter übernimmt aus Perspektive der GUI die Rolle des Models

Der Presenter ist die direkte Schnittstelle von Controler und View. Darüber hinaus ist er aber auch Observer des Domain Models. Nach wie vor gilt: Immer dann wenn sich der Zustand des Domain Models ändert muss die View informiert werden. Mit Presenter informiert das Domain Model zunächst den Presenter. Der Presenter informiert darauf die View.

Die View ermittelt den Status vom Presenter, indem dessen Get-Methoden aufgerufen werden. Diese delegiert der Presenter an das Domain Model.

Model View ViewModel (MVVM)

Abbildung 6: JavaFX-Anwendung 'Kasse'

Eine Variante des MVC-Patterns wird durch die Einführung es Data-Bindings ermöglicht. Binding bedeutet: Der Wert einer Variablen entspricht dem Wert in der View. Es gibt unterschiedliche Mechanismen, um diesen Abgleich durchzuführen.

In JavaFX werden für die Anzeige von Daten sogenannte Property-Objekte eingesetzt. Das Attribut value dieser Property-Objekte wird angezeigt. Das Binding der Objekte wird dadurch ermöglicht, dass Property-Objekte selbst die Rolle eines Subjects (s. Abbildung 1) einnehmen: Immer dann, wenn sich das Attribut value verändert, kann eine Listener-Methode ausgeführt werden. Die View nutzt diesen Mechanismus, um die Anzeige zu aktualisieren.

Durch das Binding wird die Anzeige so verändert, dass die Binding-Variable verändert wird. Umgekehrt enthält die Binding-Variable immer den aktuellen Wert der Anzeige.

In Abbildung 6 ist der Dialog zu einer Kassenanwendung zu sehen. Die Eingabe-Werte werden durch Binding-Variablen realisiert. So lassen sich einerseits die Anwender-Eingaben direkt aus den Binding-Variablen ablesen als auch beispielsweise der berechnete Preis direkt in die Binding-Variablen schreiben.

Abbildung 7: MVC mit ViewModel

Die Idee des Model-View-ViewModel-Design-Pattern ist es, die Binding-Variablen in einer Komponente ViewModel zu konzentrieren: dort werden die Anzeigedaten angepasst. Diese Komponente übernimmt auch die Aufgabe des Controllers, in dem Aktionen des Anwenders behandelt werden. 

Die Darstellung in Abbildung 7 berücksichtigt, dass für die Darstellung der Daten diese oftmals umgewandelt werden – diese Aufgabe übernimmt der Presenter. Das ViewModel wird in der Abbildung als Teil des Presenters bezeichnet – genauso gut ließe sich diese Komponente aber auch ViewModel mit Presenter bezeichnen. 

Die Controler-Komponente wird in JavaFX für das Binding verwendet: Sie verknüpft die Property-Objekte mit der View. In anderen Frameworks kann eine solche Komponente komplett entfallen. Motiviert ist das ViewModel dadurch, dass hier eine Trennung der View in Form und Daten stattfindet. Das ViewModel beinhaltet die komplette Logik der Präsentation. Es kann unabhängig von der Darstellung getestet werden.

package javafx_with_viewmodel;

import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;

public class RechnungControllerTest {
	private RechnungViewModel viewModel;
	
	@Before
	public void setUp() {
		viewModel = new RechnungViewModel();
		viewModel.getAnzahl().setValue(0);
		viewModel.getArtikel().setValue("");
		viewModel.getStueckpreis().setValue(0);
		viewModel.getPreis().setValue(0);
	}
	
	@Test
	public void preisBerechnenTest() {
		viewModel.getAnzahl().setValue(2);
		viewModel.getStueckpreis().setValue(3);
		int gesamtpreis = viewModel.getPreis().getValue();
		assertEquals(6, gesamtpreis);
	}
}
 

In dem Listing  wird geprüft, ob die Komponente ViewModel korrekt implementiert ist. Immer dann, wenn der Anwender Änderungen an der Anzahl oder dem Stückpreis vornimmt, soll der Gesamtpreis neu berechnet werden. Dazu wird der Ausgangszustand der View in der Methode setUp initialisiert: Alle Binding-Variablen werden zu dem Zweck auf Null oder leeren String gesetzt.

Die Änderungen des Users lassen sich in der Test-Methode preisBerechnenTest so simulieren, dass die Binding-Variable der Anwender-Eingabe auf den entsprechenden Wert gesetzt werden. Zum Schluss erfolgt die Überprüfung, ob der Gesamtpreis korrekt berechnet wurde. So kann das komplette Verhalten der GUI in der Komponenten ViewModel getestet werden. In diesem Beispiel ist der Test (und die Implementierung des ViewModel) ohne jeglichen Bezug auf die Gestaltung bzw. JavaFX möglich.

Principal Consultant bei ORDIX

 

Kommentare

Derzeit gibt es keine Kommentare. Schreibe den ersten Kommentar!
Donnerstag, 25. April 2024

Sicherheitscode (Captcha)

×
Informiert bleiben!

Bei Updates im Blog, informieren wir per E-Mail.

Weitere Artikel in der Kategorie