|
|||||||||||||||||||
|
|
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 umsetzenSupermodelDer 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.
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.
ModellDas 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.
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). ViewAls 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.
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.
ControllerAuch 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).
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) { Tipp 1 : Zeitpunkt von Datenübertragung und ValidierungBisher 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:
2 Möglichkeiten zur Validierung:
Tipp 2: Aufnahme von Listener-Methoden in die ModellschnittstelleUnser 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); 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) { 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 |
||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
© 2006 Hawlitzek IT-Consulting GmbH,
|
|||||||||||||||||||