Hawlitzek GmbH/ Veröffentlichungen/ MVC/
 

Hawlitzek IT-Consulting GmbH ©Foto Kirsten Literski-Hawlitzek

MVC

Dieser Artikel ist im JavaMagazin 5/00 erschienen. Vielen Dank an den Software & Support Verlag für die Genehmigung zur Veröffentlichung auf dieser Webseite!

Model-View-Controller-Ansatz in VisualAge umsetzen

Supermodel

Der zweite Teil unserer Serie [1] zum Entwicklungswerkzeug IBM VisualAge for Java beschäftigt sich mit der praktischen Umsetzung des Model-View-Controller-Ansatzes. Die Bean-orientierte Umgebung eignet sich hervorragend für die graphische Programmierung von MVC-Anwendungen und das nicht nur für die Präsentation, sondern auch für die Ablaufsteuerung.

Anfang des Jahres haben wir bereits das MVC-Konzept [2] und insbesondere dessen Umsetzung in der Swing-Bibliothek vorgestellt. Deshalb erläutern wir im folgenden nur kurz die wichtigsten Begriffe. MVC ist ein Entwurfsmuster, das eine logische Trennung der Präsentation (View) vom Datenmodel (Model) und der Ablaufsteuerung (Controller) vorgibt.

Schicht

Inhalt

Charakteristik

Umsetzung in Swing

Model

Daten, fachliche Logik

stellt Verbindungen zu den unterliegenden Datenquellen dar;
niedrige Änderungshäufigkeit

Modell-Interfaces,
abstrakte und Default-Implementierungen

View

Präsentation, Darstellung

je nach Anwendungszweck oder Benutzergruppe;
hohe Änderungshäufigkeit

Kontrollelemente mit Properties vom Typ des Modells

Controller

Ablaufsteuerung, Workflow

Behandlung von Benutzereingaben und Modelländerungen

Events


Einfaches Beispiel: Produktdaten

Angenommen wir haben ein System zur Verwaltung von Produkten. Das Modell sind die Produktdaten, eine View könnte ein Eingabedialog für die Produktdaten sein, eine andere ein Dialog in einem Bestellsystem, in dem der Benutzer die Daten eines Produkts nur ansehen und die zu bestellende Stückzahl angeben kann. Der Controller ist je nach Anwendungsfall der Use Case für die Eingabe bzw. Änderung der Produktdaten oder ein Bestellvorgang.


Abb.1: MVC-Beispiel Produktdaten

Modell

Das Modell besteht aus zwei Teilen, dem Modell-Interface – also der Schnittstelle zwischen View und Model – und der Implementierung. Diese Trennung erlaubt auch später eine Änderung der internen Datendarstellung und -speicherung im Modell. Die Schnittstelle kann schon sehr früh im Entwurf der Anwendung spezifiziert werden und sollte nur selten geändert werden.

In unserem Beispiel nennen wir das Modell-Interface Produkt und erstellen es mit dem Interface-Wizard von VisualAge. Es besteht hier nur aus den Gettern und Settern für die drei Attribute identifier, name und price (siehe Abbildung 2).

Für die Spezifikation gibt es keine besondere Unterstützung in VisualAge. Meist passiert die ohnehin in externen Designtools wir Rational Rose oder TogetherJ. Der generierte Code wird dann importiert.

Abb.2: UML-Darstellung des Modell-Interfaces und der Implementierungsklasse

Die Implementierungsklasse ProduktImpl können wir nun auf diesem Interface aufbauen. Selektieren Sie das Interface in VisualAge und öffnen Sie den SmartGuide zur Erstellung einer neuen Klasse (siehe Abb.3) . Als zu implementierendes Interface wird automatisch Produkt ausgewählt. Wenn Sie die entsprechende Option (s. Abb. 3) selektiert haben, werden auch die Stubs für unsere Methoden erzeugt. Alternativ können Sie die neuen Felder identifier, name und price aber auch selbst anlegen und sich die Getter und Setter mitgenerieren lassen.


Abb.3: SmartGuide zur Erzeugung der Implementierungsklasse

Zusätzlich zu diesen Methoden brauchen wir auch noch Felder zu Speicherung der Daten und gegebenenfalls weitere Methoden für die Datenverarbeitung, hier beispielhaft als Methoden load() und store() angedeutet. Diese Methoden sind für die interne Verwaltung sehr wichtig, spielen aber in der Schnittstelle zur View keine Rolle. Deshalb kann man auch ohne Probleme die Schnittstelle zum Backendsystem ändern (z.B. eine relationale Datenbank oder ein Warenwirtschaftsystem).

View

Als zweites erstellen wir nun ein Fenster zur Eingabe von Produktdaten. Dies kann man im Visual Composition Editor leicht erledigen. Die Eingabemaske enthält einige Eingabefelder und einen Knopf zum Schließen des Fensters. Hier stellen wir die erste Verbindung zum Model dar. Wir fügen eine Variable vom Typ des Modell-Interface ein. Es ist wichtig, dass es sich hierbei lediglich um eine Referenz der Schnittstelle handelt (Symbol: Puzzleteil in eckigen Klammern, Abb. 4), nicht um ein Objekt der Implementierungsklasse. Dies erlaubt es uns nämlich, später die Implementierung durch eine andere zu ersetzen, welche dieselbe Modellschnittstelle erfüllt.


Abb.4: Erstellung der View im Visual Composition Editor

Die einzelnen Datenfelder sind zur Synchronisation mit dem Model verbunden. Achtung, hier kommt es auf die Richtung der Connection an! Wenn man sie vom Modell zur GUI zieht, werden die Daten der Oberfläche mit denen des Modells initialisiert (korrekt). Zieht man die Verbindung jedoch umgekehrt, überschreibt man die Daten des Datenobjektes mit den Startwerten der GUI und das werden in der Regel Null-Strings sein!

Bei den Textfeldern verbindet man das Attribut text mit dem entsprechenden Property des Modells, zum Beispiel dem Produktnamen; hätten wir zum Beispiel eine JTable benutzt, würden wir deren Attribut model mit dem Attribut this eines Modells verbinden, welches das Swing-Interface TableModel implementiert.

Damit wir später im Controller die Referenz auf das Modell mit einem konkreten Objekt belegen können, müssen wir den Zugriff auf unsere (private) Variable in die Bean-Schnittstelle promoten (Abb. 5). Dadurch werden öffentliche Getter und Setter erzeugt, mit denen wir aus einer anderen Bean heraus auf das Produkt zugreifen können. Dazu wählt man im Kontextmenü der Produktvariable “Promote Bean Feature...” bzw. “Bean-Element hochstufen...”. Als Property wählt man this – also die Referenz als Ganzes – und gibt ihm einen sinnvollen Namen, zum Beispiel model.


Abb.5: Promoten der Referenz auf das Modell

Controller

Auch der Controller kann graphisch implementiert werden. Dies sieht dann einem Zustandsdiagramm ähnlich, indem man recht gut ablesen kann, welche Objekte mit anderen kommunizieren und bei welchen Ereignissen was passiert. In unserem Beispiel enthält der Controller ein Objekt der Viewklasse (ProduktView), ein Objekt der Modellimplementierung (Produkt) sowie weitere Fenster für den Programmablauf (Abb. 6).


Abb.6: Graphisch programmierter Controller

Die Connection (1) öffnet bei der Initialisierung des Controllers (VisualAge-Ereignis initialize()) das ProduktFenster und initialisiert es mit den Daten aus dem Modell. Per Property-Connection (2) ist das Modell mit der View verbunden. Schließt der Benutzer das Fenster, wird nach der Validierung entweder ein Fehlerdialog oder ein Auswahlfenster (3) angezeigt. Als einziger manuell programmierter Code muss in der main-Methode ein Objekt der Controllerklasse angelegt werden, der generierte Konstruktor ruft darin automatisch die initialize-Methode auf, die den Controller aktiviert:

public static void main(java.lang.String[] args) {
 ProduktInputController runner = new ProduktInputController();
}

Tipp 1 : Zeitpunkt von Datenübertragung und Validierung

Bisher werden die eingegebenen Daten weder validiert noch in das Modell zurückgeschrieben. Dies kann man an verschiedenen Stellen tun:

2 Möglichkeiten zur Datenübertragung:

  • Datenübertragung bei Events der Eingabekontrollelemente: In den Optionen der einzelnen Property-Connections (Abb. 7) kann man angeben, bei welchen Ereignissen die Daten ins Modell geschrieben werden. Standardmäßig ist dies <none>, also niemals. Sinnvolle Werte sind focusLost (Benutzer wechselt in anderes Feld) oder keyTyped (nach jeder Tastatureingabe).

    
    Abb.7: Eigenschaften einer Property-Connection

  • Datenübertragung beim Verlassen des Fensters: Man kann natürlich die Daten auch erst beim Drücken der OK-Knopfes ins Modell zurückschreiben. Dies macht in unserem Fenster Sinn, denn solange der Benutzer seine Eingaben noch nicht bestätigt hat, erwartet er nicht, dass die Ergebnisse schon übertragen sind. Es erfordert allerdings drei statt nur einer Connection: eine für die Initialisierung der GUI, eine beim Klick auf OK (zum Modell) und die letzte zum Beziehen des Parameters aus dem Eingabefeld.

2 Möglichkeiten zur Validierung:

  • Validierung in der Oberfläche: Genauso kann man auch die Benutzereingaben überprüfen. Entweder man prüft bei jeder Tastatureingabe oder beim Verlassen des Feldes. Dies ist besonders bei einfachen Prüfungen sinnvoll, zum Beispiel, ob in ein numerisches Feld wirklich nur Zahlen eingetippt werden.
  • Validierung im Modell: Hier findet die Prüfung in den set-Methoden der Modellimplementierung statt. Dies macht bei fachlichen Prüfungen der Werte Sinn.
Tipp 2: Aufnahme von Listener-Methoden in die Modellschnittstelle

Unser Beispiel leidet bisher daran, dass die Oberfläche nicht mitbekommt, wenn sich die Modelldaten geändert haben. Dies mag in unserem Beispiel noch erträglich sein, aber bei einer Anzeige von sich laufend ändernden Daten, zum Beispiel für Sportergebnisse, muss die Oberfläche bei jeder Modelländerung aktualisiert werden. Hätten wir eine Swingkomponente benutzt, zum Beispiel eine JTable, wäre dieser Benachrichtigungsmechanismus bereits integriert. Wir hätten dann nur in den Eigenschaften der Property-Connection (Abb. 7) das Ereignis tableChanged als Synchronisationsevent angeben müssen, diesmal jedoch auf Seiten das Modells statt beim Kontrollelement wie eben.

Bei eigenen Modellen (wie unserem Produkt) kann man diesen Mechanismus ebenfalls benutzen und den dazu notwendigen Code von VisualAge generieren lassen. Zuerst muss man ein Ereignis definieren, das bei Modelländerungen ausgelöst werden soll. Man kann dazu eine neue Ereignisklasse definieren oder ein bestehendes Event benutzen, hier zum Beispiel den PropertyChange-Event. Im Modell-Interface fügt man zwei Methoden zum Registrieren der Listener hinzu:

public void addPropertyChangeListener(java.beans.PropertyChangeListener listener);
public void removePropertyChangeListener(java.beans.PropertyChangeListener listener);

Den Rest erledigt VisualAge fast von allein. In der Modell-Implementierung ProduktImpl fehlt nun natürlich die Implementierung der gerade geforderten Methoden. Wechseln Sie auf die BeanInfo-Seite der Klasse ProduktImpl und wählen Sie “New Event Set Feature” (Abb. 7), in unserem Fall für “propertyChange”. VisualAge erzeugt dann nicht nur die Listener-Methoden, sondern auch die zur Verwaltung nötigen Datenstrukturen und die Methode zum Feuern des Ereignisses. Sie müssen nun nur noch die fire-Methode in den Fällen aufrufen, in denen Sie die Oberfläche von den Modelländerungen in Kenntnis setzen wollen, also in unserem Fall in den set-Methoden, zum Beispiel für den Preis:

public void setPrice(java.lang.String newPrice) {
 String oldPrice = price;
 price = newPrice;
 firePropertyChange("price", oldPrice, newPrice); 
}

Auf der Seite der GUI können Sie nun – analog zur JTable – in den Eigenschaften der Property-Connection den PropertyChange-Event als Synchronisationsereignis auswählen.

Literatur

 

[1] Florian Hawlitzek: Java International II, Java Magazin 3/2000

 

[2] Frank Ulbricht: Modelle, Ansichten und Kontrolleure, Java Magazin 2/2000

 

[3] Frank Ulbricht: Swinger-Club, Java Magazin 3/2000

 

© 2006 Hawlitzek IT-Consulting GmbH,
Marketing&Design Kirsten Literski-Hawlitzek

Seitenanfang

Hawlitzek GmbH
Die Gründer
Dienstleistungen
Java Downloads
Vorträge
Veröffentlichungen
Kontakt
International
Sitemap