Einleitung
Inzwischen steht für mich fest: die Entwicklung der Präsentationsschicht ist in einer Mehrschichtarchitektur der schwierigste Teil. Dafür habe ich einige Gründe:
Respektieren Sie das Model-View-Control Paradigma
Wenn Sie glauben das MVC-Paradigma verstanden zu haben, lesen Sie unbedingt weiter!
Es ist ein weit verbreiteter Irrtum, dass ein GUI Entwickler das MVC-Paradigma zu implementieren hätte. Um wirklich zu verstehen was MVC ist, muss man sich zunächst in die Rolle eines Entwicklers von Oberflächenkomponenten versetzen. Das sind im allgemeinen nicht Sie, sondern die Entwickler von GUI-Baukästen, aus denen Sie sich dann bedienen, um eine Oberfläche zusammenzustecken und zu verdrahten. Die Komponenten dieses Baukastens haben nun zwei wesentliche Eigenschaften:
Dieses grundsätzliche Problem löst der Komponentenentwickler mittels der dafür vorgesehenen Design-Patterns:
gui component --> JList action listener interface --> MouseListener u.v.m. action event --> MouseEvent u.v.m. model interface --> ListModel model listener interface --> ListDataListener model event --> ListDataEvent
Mit dieser Infrastruktur ist ein ziemlich klarer Weg vorgegeben, wie GUI-Komponente zu nutzen sind:
Respektieren Sie das MVC-Paradigma indem Sie folgende Fehler vermeiden:
view <--------- control | | | | +---> model <---+Machen Sie sich noch einmal klar, alle entgegengesetzt gerichteten Abhängigkeiten sind komplett über das Observer-Pattern vermieden. Aus der Sicht der view ist control ein EventListener für action events, aus der Sicht des model sind view und control EventListener für model events:
action events
view -------------> control ist action listener
model events
model -------------> control und view sind model listener
Alle Swing Componenten haben mindestens ein zugeordnetes Modell, hier die wichtigsten:
| Model | Verwendung |
|---|---|
| ButtonModel | JButton, JToggleButton, JCheckBox, JRadioButton, JMenu, JMenuItem, JCheckBoxMenuItem, JRadioButtonMenuItem |
| ComboBoxModel | JComboBox |
| BoundedRangeModel | JProgressBar, JScrollBar, JSlider |
| SingleSelectionModel | JTabbedPane |
| Document | JEditorPane, JTextPane, JTextArea, JTextField, JPasswordField |
| ListModel | JList |
| ListSelectionModel | JList |
| TableModel | JTable |
| TableColumnModel | JTable |
| TreeModel | JTree |
| TreeSelectionModel | JTree |
Bauen Sie GUI-Komponente
Sie haben im letzten Abschnitt gelernt, dass für eine erfolgreiche GUI-Implementierung die Einhaltung des MVC-Paradigma absolut erforderlich ist. Sie können nun in die Rolle eines Komponenten-Entwicklers schlüpfen und für Ihre Anwendung maßgeschneiderte GUI-Komponenten entwickeln. Indem Sie das tun isolieren Sie ein Stück Fachlichkeit vom Rest der Anwendung in einen eigenständigen grafischen Bereich. Tun Sie das immer dann, wenn es die Anforderungen zulassen und sooft es geht.
Für die Entwicklung eigener GUI-Komponente stehen Ihnen bei Swing drei Möglichkeiten offen:
Wenn Sie das erledigt haben befinden Sie sich wieder in der Rolle des Nutzers einer MVC-konformen Komponente, Sie implementieren nun das model der Komponente und verdrahten über die Listener-Infrastruktur das control mit model und view. Der Code ihrer Komponente besitzt keine Abhängigkeiten zu dieser konkreten fachlichen Umsetzung.
Modellieren Sie in Kaskaden
In diesem Abschnitt geht es um die Schichtung Ihres models. Ihnen ist sicher aufgefallen, dass jede Komponente, egal ob aus der Basis oder als Eigenentwicklung, mit einer sehr spezifischen Abstraktion von Daten ausgeliefert wird. Eine JList hat ein ListModel, ein JTree ein TreeModel und so fort. Dem steht ihr eigenes, fachlich getriebenes Datenmodell gegenüber. Sie können nun bei der Implementierung der Komponentenmodelle direkt auf Ihr Fachdatenmodell zugreifen. Ihr Fachdatenmodell existiert dann nur implizit, der Code Ihrer Komponentenmodelle greift zum Beispiel mit JDBC oder durch direkte Zugriffe auf einen Cache auf Ihre Fachdaten zu. Er hält damit unweigerlich viel Wissen über die Struktur der Fachdaten. Sollte sich diese ändern, was häufig geschieht, dann müssen Sie diese Änderung auf alle Modellimplementierungen verteilen.
nicht empfohlen!
+-------+
view1 --> model1 ----> | Fach |
view2 --> model2 ----> | Daten |
: | |
: +-------+
Sie können diese grundsätzliche Problem (der sich ändernden Fachdatenstruktur) mildern, indem Sie Ihre Fachdatenstruktur MVC-konform abstrahieren. Die komponentennahen models greifen dann auf dieses Modell zu und registrieren sich als Listener.
+-------+
view1 --> model1 ----> Fachdaten- | Fach |
view2 --> model2 ----> modell ---> | Daten |
: | |
: +-------+
Diese Schichtung ist nach Bedarf beliebig erweiterbar. Die Abhängigkeiten sind streng von links nach rechts, die andere Richtung wird über model listener abgewickelt. Statt eines Fachmodells lassen sich mehrere einführen, und der Zugriff der Komponentenmodelle kann über mehrere Zwischenmodelle erfolgen. Das ist eine klassische Designentscheidung und eine wichtiger Teil Ihrer Entwicklungsaufgaben.
Planen Sie Plugins
Wir haben gesehen, wie mittels fachlich getriebener Komponenten auf der Mikroebene Fachlichkeit separiert werden kann. Dieser Abschnitt behandelt den gleichen Ansatz aus der Makroperspektive. Stellen Sie sich vor Sie haben eine fertige Anwendung (oder sehen diese in Ihrem Kopf vor sich). Vielleicht können Sie einen Teil der Funktionen der Anwendung restlos streichen, ohne dass alle übrigen Funktionen davon betroffen sind. Wenn das möglich ist sollten Sie darüber nachdenken, diesen Teil als Plugin zu implementieren. Und zwar auch dann, wenn Sie niemals planen Ihre Anwendung ohne dieses Plugin auszuliefern. Mit dieser Strategie zerteilen Sie auf der Makroebene Ihre Anwendung in fachliche und technische Schichten die Sie getrennt voneinander entwickeln und warten können.
Lassen Sie mich erläutern, was ich in diesem Zusammenhang mit 'Plugin' meine. Als Plugin bezeichne ich eine Softwarekomponente, die von anderen Komponenten gerufen (benutzt) wird, weil sie sich als ein Service an einer dafür vorgesehenen Schnittstelle registriert hat. Die Registrierung ist der entscheidende Unterschied. Eine gewöhnliche Komponente kann von einer anderen Komponente benutzt werden, wenn sie dieser bekannt ist. Es gibt dann unweigerlich Abhängigkeiten von der benutzenden (master, M) zur benutzten (service, S) Komponente.
M --> SSpeziell bei der GUI-Entwicklung gibt es nun das Problem, dass der Service S im allgemeinen bei M viel Wirkung verursacht. Das ist eine umständliche Umschreibung dafür, dass eine GUI im allgemeinen eine hoch interaktive Anwendung sein soll, dass zum Beispiel der Klick auf ein Menüpunkt einen Service S triggert, der als Ergebnis die komplette GUI-Ansicht bei M anpasst. Wir wünschen also eher eine Abhängigkeit in diese Richtung:
M <-- SDas Problem dabei ist, dass nun M von der Existenz S nichts weiß und S nicht aufrufen kann. Damit das möglich ist muss sich S bei M über von M vorgegebene Schnittstellen registrieren. Mit dieser Strategie lässt sich dann ein System als eine Schichtung aufeinander basierender Services oder Plugins designen.
Für eine Umsetzung dieser Strategie brauchen Sie eine Infrastruktur, die die Registrierung Ihrer aus Services komponierten Anwendung vornimmt. Dafür gibt es verschiedene Ansätze:
Hinzufügen möchte ich noch, dass alle Bestrebungen eine Anwendung als eine Komposition von Plugins zu designen zum Scheitern verurteilt sind, wenn aus fachlicher Sicht solche Plugins nicht gibt. Die Anforderungsanalyse muss also eine Schichtung aufeinander aufbauender fachlicher Services benennen.
Planen Sie asynchrone Verarbeitung wo immer es geht
Automatisieren Sie Tests
AWT stellt für die Testautomation die Klasse java.awt.Robot bereit. Mit dieser Klasse lassen sich auf Betriebssystemebene Nutzerereignisse wie Tastaturklicks und Mausklicks simulieren. Wenn man dieses Konzept ausbaut und auf GUI-Events wie das Erscheinen von Windows lauscht, dann kann man mit recht einfachen Mitteln eine Automatisierung erreichen. Gerne stelle ich eine prototypische Realisierung bereit, die sich selbst in dieser einfachen Ausbaustufe in Projekten schon bewähren konnte.
Referenzen