Professionelle Anwendungsentwicklung mit MVC

Das MVC (Model View Controller)-Konzept stellt für zukunfts­orientierte Applikationen eine Grundvoraussetzung dar. Es trennt die unterschiedlichen Applikationslogiken und gibt den Entwicklern einen transparenten Gesamtüberblick. Zusätzlich sind die einzelnen Komponenten leichter zu warten, zu erwei­tern und auszutauschen. Solche Flexibilitäten sind  für mehr­schichtige Anwendungen unverzichtbar.

Die Einsatzszenarien der heutigen Softwaresysteme werden immer komplexer. MVC hilft hierbei, den Überblick zu wahren. Die meisten Ap­plikationen werden mittlerweile fürs In­ternet konzipiert und müssen interope­rabel mit verschiedensten verteilten Sy­stemen sein. Dabei kommt eine Mehrschichten-Architektur zum Tra­gen, die eine strikte Trennung der ver­schiedenen Logiken erfordert. In solch komplexen Applikationsaufbauten stellt der Ansatz von MW eine Grundvor­aussetzung dar. Die Grundidee, die hinter MVC steht, ist, daß grundsätzlich jede zu rea­lisierende Anwendung aus drei Teilen besteht: dem Modell, einigen Repräsen­tationssichten und den Controllern.

Da­bei stellt das Modell den Teil der Appli­kation dar, der die aktuelle Anwen­dungslogik beinhaltet. Wenn wir sagen, wir wollen die Benutzerschnittstelle von der Anwendung trennen, so ist das Modell die Applikation. Wenn das Mo­dell die Anwendung repräsentiert, dann

stellen die Sichten (Views) und Control­ler-Klassen das Benutzerinterface dar. Dabei ist das Benutzerinterface konse­quent in Eingabe- und Ausgabekompo­nenten aufgeteilt. Der Controller ist die Eingabekomponente. Sie liefert konkre­te Informationen an das Modell. Die Sicht dazu ist die Ausgabekomponente, welche die Informationen aus dem Mo­dell wiedergibt. Dabei sollte das Modell vollkommen unabhängig von seinen ex­ternen Repräsentationen dieser Infor­mationen sein. Dies ist ein extrem wich­tiger Punkt, da es die Wiederverwen­dung von Code betrifft. Es muß möglich sein, die Eingabedaten und Ausgabeformate zu ändern, ohne dabei das Modell anpassen zu müssen. Das Modell soll dabei nur mit reinen Infor­mationen arbeiten.

...page...
Anforderungen zur Konvertierung der Eingabedaten haben nichts im Modell zu suchen. Wenn sol­che Konvertierungen notwendig wer­den, dann sind diese vom Controller auszuführen. Ebenso übernimmt das Modell keine Verantwortung, wie die Daten zum Schluß dargestellt werden. Das Konzept von MVC erreicht so eine klare Schichtentrennung zwischen den Anwendungsdaten (fachlicher Be­reich), den Sichten auf die Anwen­dungsdaten (Präsentationsbereich) und den Benutzerschnittstellen (Interak­tionsbereich). Das Hauptziel von MVC ist, die Verarbeitung eines Problembe­reiches von seiner Präsentation zu tren­nen. Ein weiterer Aspekt stellt die Steuerung der Interaktion mit dem Be­nutzer dar, welche die thematischen Zu­sammenhänge eines Problembereiches darstellt.

...page...
Die Idee der strikten Trennung der drei Komponenten ist, daß das Ge­samtsystem hierdurch sehr flexibel ge­genüber jeglichen Änderungensanfor­derungen wird - egal, ob diese sich auf die fachliche Verarbeitung, die Präsen­tation anwendungsbezogener Daten oder die Benutzerführung jeglicher Ent­wicklungsplattformen beziehen. Eine derartige Flexibilität ist nur über diese Schichtentrennung gewahrt.

Zusätzlich ermöglicht es dem Ent­wickler, durch die komplette Isolierung der Geschäftsmodelle (business model), sich konkret seinem eigentlichen Pro­blembereich zu widmen. Da die unter­schiedlichen Komponenten im MW vollkommen unabhängig voneinander sind, lassen sich diese in verschiedenen anderen Kontexten wiederverwenden. Um eine besonders hohe Wartbarkeit ei­nes Systems zu erreichen muß die MVGArchitektur so einfach wie mög­lich aufgebaut sein. Denn mit steigen­der Komplexität verringert sich auch die Wartbarkeit des Systems entspre­chend. Grundsätzlich besteht die Archi­tektur deshalb nur aus drei Klassen: dem Modell (model), seiner Sicht (view) und dem Controller (controller).

Die Kontrollflüsse im MVC
Beim MVC-Paradigma werden die Sich­ten und Objektmodelle (Models) durch den Aufbau konkreter Protokollklassen für Benachrichtigungszwecke entkop­pelt. Das betreffende Protokoll ist dabei in entsprechenden Controller-Klassen implementiert. Die Aufgabe eines View­Objektes ist es, sicherzustellen, daß sei­ne Darstellung den konsistenten Zu­stand der Objektmodelle wiedergeben. Das Modell benachrichtigt dabei alle von ihm abhängigen Sichten, wenn sich seine Daten im Modell ändern. Hieran sieht man gut, daß die MVC-Architek­tur rein nachrichtenorientiert arbeitet. Daraufhin erhält eine Sicht die Möglich­keit, mit Hilfe verschiedener Objektme­thoden einen konsistenten Zustand her­zustellen. Der Ansatz erlaubt es, dabei mehrere Sichten an ein Objektmodell zu hängen. Alle Sichten können, unabhän­gig vom Modell, jederzeit ausgetauscht werden. Die verschiedenen Interaktionsdia­gramme beschreiben am besten die un­terschiedlichen dynamischen Verhaltensweisen innerhalb der MVC-Archi­tektur. Hierunter finden sich die Anmel­dung und Initialisierung, der Ände­rungsablauf und zum Schluß die Ab­meldung und Freigabe. Die komplette MVC-Architektur ist von seinem Ob­jektmodell (Model) abhängig, damit es seine Aufgaben wahrnehmen kann.
...page...
Die einzige Aufgabe des Modells besteht darin, den aktuellen Status allen interes­sierten Sichten und Controller zur Ver­fügung zu stellen. Da das Modell je­doch vollkommen entkoppelt imple­mentiert ist, hat es keine Kenntnis da­von, daß noch Views und Controller existieren, die an dessen Daten interes­siert sind. Deshalb geht man so vor, daß sich alle Objekte beim Modell anmelden müssen, wenn sie von Änderungen im Modell benachrichtigt werden wollen. Die Implementierungsweise wird in der Literatur als Observer-Entwurfsmuster bezeichnet. Die Klassenverantwortun­gen werden innerhalb der Sicht aufge­baut. So meldet sich die View beim ent­sprechenden Modell an. Diese Initiali­sierung wird wie üblich im Hauptpro­gramm implementiert. Dabei ist für jede Sicht, die im Programm geöffnet wer­den soll, eine Sicht und ein Controller zu initialisieren. Nun die einzelnen Schritte hierzu:
...page...
Als erstes wird das Modellobjekt er­stellt, wobei dessen interne Datenstruk­tur direkt initialisiert wird. Danach wird die View-Instanz erzeugt. Diese Instanz erhält eine direkte Referenz auf das Objektmodell und wird als Parame­ter übergeben. Mit Hilfe der addObserver()-Metho­de meldet sich die View beim Objekt­modell an. Die Sicht erstellt daraufhin eine Instanz der Klasse Controller. Die­se verfügt über eine initialize()-Metho­de, der per Parameterübergabe eine Re­ferenz auf das Modell und seiner Sicht übergeben wird. Genau wie die Anmel­dung bei der View durchzuführen war, meldet sich jetzt der Controller mittels der addObserver()-Methode beim Ob­jektmodell an. Über die initialize()-Me­thode können konkret Ressourcen re­serviert öder angelegt werden, die die View und der Controller dann verwen­den. Nachdem dieser Initialisierungs­vorgang komplett abgeschlossen ist, kann die Applikation auf Benutzerein­gaben reagieren und diese entspre­chend weiterverarbeiten. Im folgendem wird der Kommunikationsfluß, der dem MVC-Szenario zu Grunde liegt, beschrieben. Das Interaktionsdiagramm zeigt hierzu konkret, wie die verschie­denen Objekte (Modell,View,Controller) miteinander kommunizieren. Zusätz­lich kann das Objektmodell über die no­tifyObservers()-Methode alle angemel­deten Sichten und Controller über Än­derungen informieren. Hierzu geht das Modell alle bei ihm angemeldeten Views und Controller durch und ruft die update-Methode auf. Bild 1 zeigt, wie Änderungen im Ob­jektmodell durch Benutzereingaben den Änderungsablauf auslösen. Hierbei ak­zeptiert die Applikation zuerst ein Dia­logereignis eines Benutzers und ruft eine Callback-Funktion im entsprechen­den Controller auf. Dort wird die Ein­gabe interpretiert und aktiviert den ent­sprechenden Dienst, der vom Model an­geboten wird. Das Modell führt den angeforderten Dienst aus und ändert dabei seinen internen Zustand. Dies veranlaßt das Modell, alle bei ihm ange­meldeten Sichten und Controller durch Aufruf ihrer zugehörigen update-Me­thoden mittels notifyObservers über die Änderung zu benachrichtigen. Dabei holt sich jede Sicht die geänderten Da­ten vom Modell und aktuali­siert seine Bildschirmpräsen­tation. Jeder beim Modell an­gemeldete Controller kann bestimmte Benutzerfunktio­nen ein- oder ausschalten, wenn sich der Modellzustand geändert hat. Beispielsweise kann eine Zustandsänderung des Modells den Menüpunkt zum Speichern der Daten de­aktivieren.


Ist der Aktualisie­rungsvorgang abgeschlossen, wird die Callback-Funktion verlassen und die Applika­tion kann wieder auf Benut­zeraktionen reagieren. Manchmal kommen auch Situationen vor, in denen die verschiedenen Abhängig­keitsbeziehungen innerhalb eines MVC-Szenarios wieder aufgelöst werden müssen, beispielswei­se, wenn eine Sicht vom Benutzer expli­zit geschlossen wird. Die folgenden Schritte skizzieren die auszuführenden Operationen. Über eine Callback­Funktion im Controller wird das Ereignis der Sicht interpretiert. Daraufhin wird die release()-Me­thode des Controllers der zugehö­rigen Sicht aufgerufen. Die Abmel­dung von View und Modell ge­schieht mit dem Aufruf der Methode deleteObserver() des Mo­dells. Genauso meldet sich der Controller beim Modell via der re­lease-Methode() ab, die von der Sicht aufgerufen wird.

Die Basisklassen im MVC
Der Hauptmechanismus im ge­samten MVC wird über die Ob­servable-Klasse und die Observer­Schnittstelle erreicht. Denn hier­über läuft die Anmeldung bezie­hungsweise Abmeldung einzelner Komponenten und gleichzeitig die Benachrichtigung über Änderun­gen im Modell. Die MVC-Anwen­dung nutzt die Observable-Klasse zur Implementierung des Modells, während die Observer-Schnittstelle innerhalb der Sichten implemen­tiert ist. Hingegen benötigt die Controller-Klasse keine Hilfsklas­sen, um Benutzereingaben zu verarbeiten. Die Sichten und Modelle erben dann von diesen Klassen und helfen so automatisch, ein Benachrichtigungssy­stem einzurichten. Jede Ansicht wird somit sofort in Kenntnis gesetzt, sobald sich Daten im Modell geändert haben. Mittels der notifyObser­vers()-Methode werden alle angemeldeten Ob­server-Objekte benachrich­tigt. Diese rufen dann ih­rerseits ihre update()-Me­thode auf, um ihre Bildschirmdarstellungen
zu aktualisieren, wobei die aktuellen Daten aus dem Modell geholt werden. Dieser Entwurf wird in der Literatur als Observer-Ent­wurfsmuster beschrieben. Dabei nimmt das Modell die Rolle des Subjekts ein. Für die Registrierung der Beobachter-Objekte ist da­her ein Klassenprotokoll realisiert.
...page...
Somit muß jede Methode die eine Änderung im Modell vornehmen kann, dafür Sorge tragen, daß auch die notify­Observers()-Methode gerufen wird. Die Klasse KundenModell ist dabei von der Klasse java.util.Observable abgeleitet, welche diesen Change-Dependend-Me­chanismus implementiert. Alle Metho­den im Modell, die ihren Zustand än­dern, rufen die Methode setChanged() auf, die sich wiederum der Methode no­tifyObservers() der Klasse java.util.Ob­servable bedient. Die Methode notify­Observers() iteriert über alle registrier­ten Observer-Objekte und ruft ihre zugehörigen update()-Methoden auf. Die Observable-Klasse und das Ob­server-Interface sehen dann wie in Li­sting 1 dargestellt aus:

Die Implementierung
Bei der Implementierung einer MVC­Applikation sollte zuerst immer das Modell implementiert werden. Erst da­nach sollten die verschiedenen Control­ler für die verwendeten GUI-Kompo­nenten folgen. Zum Schluß sind dann die entsprechenden Sichten noch zu entwerfen. Das Objektmodell unseres Beispiels enthält die Basisdaten von Mitgliedern, welche vom Modell zu verwalten sind. Wir haben es sehr ein­fach gehalten und nur Nachname und Vorname berücksichtigt (siehe Bild 6). Innerhalb der Sicht können Sie per Eingabefeld neue Mitglieder einfügen oder bereits bestehende wieder löschen. Die einzelnen Einträge werden dabei in ei­ner ListBox dargestellt (siehe Bild 7). Wenn Sie dabei mehrere Ansichten öff­nen, werden Sie sehen, daß, sobald Sie Einträge verändern, diese sofort in allen aktiven Sichten dargestellt werden. Dies liegt daran, daß alle Sichten sich auf ein und dasselbe Modell beziehen. Hieran kann man die Arbeitsweise und den Vorteil von MW gut nachvollziehen.

Das Modell
Jede Applikation verfügt über eine ge­wisse Funktionalität, um die Kerndaten aus der fachlichen Sicht zu verwalten. Diese sind im MW so implementiert, daß man unabhängig von Ein- und Ausgabesichten ist. Die Entwicklung je­der Applikation wird mit der Erstellung der Modell-Komponente begonnen, welche die Anwendungsdaten und die Funktionalitäten der Applikationen un­abhängig von ihrer Repräsentation kap­selt. In den Modell-Komponenten wer­den Methoden (holeInhalt, setzelnhalt) bereitgestellt, die einen Zugriff auf die darzustellenden Daten ermöglichen. Diese Daten werden über sogenannte Dienstmethoden verändert. Damit die Benutzerschnittstelle diese verwenden kann, sind sie dort öffentlich deklariert. Da die Klasse KundenModell die Klasse Observable erweitert, sind Objekte vom Typ KundenModell in der Lage, eine Li­ste von registrierten Observer-Objekten zu verwalten. Dies wird als Callback be­zeichnet. Interessierte Observer-Objekte (views) registrieren ihr Interesse, um von Anderungen von Modellinhalten benachrichtigt zu werden. Sie melden sich durch Aufruf der addObserver()­Methode innerhalb der Observable Klasse an, welche eine Refe­renz zum Modell besitzt. So­bald sich Modelleinträge ge­ändert haben, benachrichtigt das Modell alle registrierten Ansichten (views) darüber und übergibt dabei die eigene Identität und den neuen Wert dieser lnstanzvariable an jedes Ansichtsobjekt (view). In Li­sting 2 ist die Klasse Kunden­Modell abgebildet. Die Aufgabe der Modelle besteht lediglich darin, dessen Daten zu verwalten. Dabei fungiert der Controller als rei­ne Vermittlerklasse zwischen Modell und Sicht. Relativ schnell kommt hierbei die Fra­ge auf, wie eine Ansicht mit dem Modell sinnvoll verbun­den werden kann. Denn ver­schiedene Komponenten einer Ansicht mit einem einzigen Modell zu verwenden, wäre zu aufwendig. Am leichtesten funktioniert es, wenn für jede Komponente in der Grafik-Bi­bliothek eine abstrakte Con­trollerklasse (Controller) exi­stiert.

Die Ansicht
Der Konstruktor hat die Auf­gabe, die Darstellung aufzu­bauen. Daher ist die initiali­ze()-Methode für die Ansicht am bedeu­tendsten. Diese erledigt das Anmelden der Ansichten beim Modell und stellt dabei die Beziehungen zum Controller her. Die Methode baut den Abhängigkeitsmechanismus für den Fall auf, daß andere Komponenten sich ändern. Nach erfolgreicher Initialisierung der Sichten und Controller kann die Sicht am Bildschirm dargestellt werden. Das Abmelden der Sichten beim Modell mit dem dazugehörigen Controller erfolgt dann über die Methode release(). Die einzelnen Referenzen für die betreffen­den Modelle und Controller werden in zwei Instanzvariablen gehalten. Für jede Ansicht ist eine zeichneLi­ste-Methode() anzulegen, die die Mo­delldaten auf den Bildschirm bringt. Dabei holt sich die Methode die darzu­stellenden Daten direkt vom Modell. Hierbei kann die Implementierung auf unterschiedlichen Plattformen anders ausfallen, da andere Funktionen not­wendig sind. Bei der Benachrichtigung über eine Änderung wird in der Sicht eine update()-Methode aufgerufen. Die­se Methode ist in der Observer-Schnitt­stelle deklariert und daher von jeder Ansichtsklasse aus zu implementieren. Diese update()-Methode wird bei allen registrierten Ansichtobjekten aufgeru­fen, sobald das Modellobjekt die notify­Observers()-Methode aus der Observ­able-Klasse ruft. Die einfachste Aktualisierung der Ansicht kann über den Aufruf der zeichneListe()­Methode erzielt werden. Dabei werden alle Daten vom Modell geholt und diese dann dargestellt. Es wird jedoch nicht berück­sichtigt, welche Daten sich überhaupt geändert ha­ben. Die Art der Aktuali­sierung ist bei komplexen Sichten und Modellen al­lerdings ineffizient. Für eine Optimierung gibt es verschiedene Vorgehens­weisen. Beim Aufruf der update()-Methode werden einfach weitere Informa­tionen bezüglich der Da­ten, die sich konkret geän­dert haben, übergeben. Dadurch kann die Sicht entscheiden, ob überhaupt und wo eine Aktualisie­rung auszuführen ist. Li­sting 3 stellt die ListView­Klasse im Überblick dar.

Der Controller
Grundsätzlich betrachtet man zuerst, welche Verhal­tensweisen eine Applika­tion auf dem User-Interfa­ce aufweist. Genau die Re­aktionsweisen sind im Controller zu implemen­tieren. Grundvorausset­zung dabei ist, daß jede Benutzerinteraktion ein Ereignis im darunterlie­genden System auslöst. Die einzelnen Ereignisse werden vom Controller mittels entsprechender
Callback-Funktionen abgefangen und entsprechend interpretiert. Dies hat dann den Aufruf betreffender Metho­den im Objektmodell zur Folge. Inner­halb der Controller-Klasse sind ver­schiedene Instanzvariablen (kunden­Modell, listView) zu deklarieren.
Der Aufbau der Beziehung vom Controller zur Sicht und dem Modell über Instanzvariablen (kundenModell, listView), geschieht genauso wie im Modell mittels der initialize()-Methode. Diese ermöglichen der Controllerklasse einen Zugriff auf das Modell und die Ansicht, um etwaige Methode dort auf­rufen zu können. So kann die setzeIn­halt()-Methode gerufen werden, um Daten dem Modell übergeben zu kön­nen. Genauso verwenden die Ansichts­objekte die addObserver()-Methode, um sich als Observer-Objekt zu regi­strieren. Das Gegenteil erfolgt für die Freigabe und Abmeldung des Control­lers über die release()-Methode. Die In­itialisierung der Ereignisbehandlung fällt dabei auf jeder Plattform anders aus. Diese kann in der Ansicht oder im Controller erfolgen. Die Views übernehmen im MVC keine Verantwortung der Ereignisbehandlung, da dies allein dem Controller vorbehalten ist. Die Sicht ist ausschließlich für die Darstel­lung zuständig. Jede Frameansicht kann auch beendet werden. Dieses Er­eignis ist vom Controller abzufangen und entsprechend zu verarbeiten (WindowClosing). Die in Listing 4 auf­geführte ListController-Klasse regelt die beschriebenen Ereignisse.

Die Vorteile von MVC
Nun haben wir gesehen, daß die Imple­mentierung von MVC gar nicht so schwer ist. Folgende Vorzüge können helfen, Ihre Projekte wesentlich flexibler zu realisieren:
(1) Mehrere Darstellungen des Modells Durch die strikte Trennung des Modells von den Komponenten der Benutzer­oberfläche ist es möglich, mehrere Dar­stellungen des gleichen Modells auf un­terschiedliche Weise abzubilden. Die Sichten können auch zur Laufzeit jeder­zeit dynamisch geöffnet und wieder ge­schlossen werden.
(2) Konsistente Darstellung
Eine Änderung der Daten im Modell bewirkt eine Benachrichtigung aller bei ihm angemeldeten Views und Control­ler. Somit kann gewährleistet werden, daß die Views und Controller sich je­derzeit in einem konsistenten Zustand zum Modell befinden.
(3) Klare Aufgabenverteilung der Objekte
Durch die strikte Aufgabenteilung von Modell, View und Controller wird eine erhöhte Wiederverwendbarkeit der ein­zelnen Komponenten in anderen Kon­texten ermöglicht. Das System kann zu­dem sehr flexibel auf Änderungen der Anforderung an die Applikation reagie­ren.
(4) Austauschbare Views und Controller
Durch die lose Kopplung der Views und Controller vom Modell in der MVC-Architektur, ist es jederzeit mög­lich, die View- und Controller-Objekte eines Modells auszutauschen. Kompo­nenten der Benutzeroberfläche können sogar zur Laufzeit geändert werden.

Fazit
Grundsätzlich kann man sagen, daß sich der Mehraufwand für die Imple­mentierung einer richtigen MVC-Archi­tektur lohnt. Schon bei kleineren bis mittelgroßen Anwendungen sollten Sie wegen der enormen Vorteile nicht dar­auf verzichten. Dies gilt besonders für Anwendungen, die immer wieder zu modifizieren sind. Unverständlicher­weise wurde das Konzept nur teilweise in das Grafiktoolset „Swing" übernom­men, obwohl es sich leicht realisieren läßt. Denkt man das Konzept von MVC mal konsequent zu Ende, treten die Schwächen innerhalb von Swing her­vor. Die Grundbedingung, daß ein Mo­dell vollkommen unabhängig von der gewählten Darstellung sein muß, ist dort nicht haltbar, da Swing vom kon­kreten GUI-Element abhängige, unter­einander inkompatible Modelle ver­wendet. Zudem repräsentiert ein GUI­Element in der Regel nur einen Aspekt eines komplexen Objektes. Zum Bei­spiel eines Kunden mit Adresse, Konto­nummer und so weiter. Somit zerstük­kelt Swing den engeren Zusammen­hang der Objekte jedoch in Modelle für jeden einzelnen Aspekt. Trotzdem wird Swing immer mehr in Projekten ver­wendet. Eine Anpassung tut hier Not.