Start - Projekte - Wissen - Impressum -
Prozesse & Systeme - Optimierung von Entwicklungsprozessen

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:

  • Unabhängig von der Technologie wird immer das Maximale verlangt. Alles was irgendwo schon gesehen wurde muss umgesetzt und eingesetzt werden.
  • Viel Projektteilnehmer sehen sich als Experten für Frontends. Die meisten irren aber mit dieser Ansicht.
  • Die Anforderungen sind statt auf Anwendungsfälle zu fokuszieren oftmals frontendlastig formuliert - die Frontendentwicklung befindet sich dann schnell in einem Spannungsverhältnis zwischen den falsch ausgerichteten Erwartungen der Anwender und den technischen, operativen und organisatorischen Möglichkeiten im Projekt.
  • Bei der Analyse der Anforderungen darf nicht unterschieden werden zwischen 'Oberflächenkomponenten' und 'Fachkomponenten'. Gleich mehr dazu.
  • Projektiterationen werden häufig Frontend-zentriert initiiert. Die Frontendentwicklung ist dann besonders häufig von Umbaumaßnahmen betroffen.
Ohne Zweifel lassen sich die daraus resultierenden Spannungen besser abfedern, wenn man so gut es geht eine solide technische Basis bei der Frontendentwicklung durchhält. Es folgen ein paar Eckpunkte dafür.

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:

  1. Über diese Komponenten initiiert der Nutzer Aktionen mit denen der Programmablauf der GUI gesteuert wird.
  2. Den Komponenten werden Inhalte zugeordnet, diese Inhalte werden von den Komponenten dargestellt.
Zum Zeitpunkt der Entwicklung von GUI-Komponenten steht weder fest, wie die Aktionen im Detail konsumiert werden, noch welche Daten darzustellen sind. Beides bestimmt der konkrete Anwendungsfall den Sie umsetzen.

Dieses grundsätzliche Problem löst der Komponentenentwickler mittels der dafür vorgesehenen Design-Patterns:

  1. Konsumenten von Nachrichten (Aktionen) registrieren sich als Listener.
  2. Inhalte werden mittels komponentenspezifischer Modell-Schnittstellen abstrahiert. Die Komponenten melden sich als Listener bei ihren Modell-Implementierungen an, um über Modelländerungen informiert zu werden.
Der Komponentenentwickler stattet uns somit mit einer Infrastruktur aus, die man folgendermaßen charakterisieren kann: Fertige GUI-Komponenten (der view) realisieren die grafische Sicht auf Daten. Den GUI-Komponenten sind Interfaces (models) zugeordnet, die die darzustellenden Daten abstrahieren und model listener über Modelländerungen informieren. GUI-Komponenten benachrichtigen ihre action listener wenn Nutzeraktionen stattfinden und melden sich an ihren Models an. Alle Bestandteile auf einem Blick und eine JList als Beispiel:
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:

  1. Implementieren Sie action listener/model listener und melden Sie diese bei den Komponenten und ihren Modellen an. Hier kann man kaum etwas falsch machen. Das Ergebnis dieser Arbeit ist die eigentliche Ablaufsteuerung im Programm und wird control genannt.
  2. Implementieren Sie die models der Komponente. Sie sind damit erst fertig, wenn Sie auch die model listener korrekt benachrichtigen. Dazu müssen Sie die An- und Abmeldung von listener erlauben und dann bei Datenänderungen diese mit den richtigen Nachrichten versorgen.
Zusammenfassung: Als Frontendentwickler nutzen Sie eine Infrastruktur, die fertige GUI-Komponenten und deren Modellabstraktion breitstellt. Sie implementieren diese Modelle und die Listener für Komponentenereignisse und Modelländerungen. Dieses Vorgehen nennt man das MVC-Paradigma.

Respektieren Sie das MVC-Paradigma indem Sie folgende Fehler vermeiden:

  • Sie implementieren die model-Schicht ohne korrekte Benachrichtigung der model listener. Sie erkennen diesen Fehler daran, dass Ihre Komponenten nicht korrekt aktualisieren oder dass sich Ihr control-Code um diese Aktualisierung kümmert.
  • Ihr control kennt nur action listener, keine model listener. Als Folge implementieren Sie in Ihren Modellen Code, der eigentlich für den Programmfluss verantwortlich ist.
  • Modellimplementierungen rufen ihre view direkt auf, statt über model events.
Wenn Sie das das MVC-Paradigma respektiert haben, sollten sich zwischen model, view und control ausschließlich folgende Abhängigkeiten ergeben:
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:
ModelVerwendung
ButtonModelJButton, JToggleButton, JCheckBox, JRadioButton, JMenu, JMenuItem, JCheckBoxMenuItem, JRadioButtonMenuItem
ComboBoxModelJComboBox
BoundedRangeModelJProgressBar, JScrollBar, JSlider
SingleSelectionModelJTabbedPane
DocumentJEditorPane, JTextPane, JTextArea, JTextField, JPasswordField
ListModelJList
ListSelectionModelJList
TableModelJTable
TableColumnModelJTable
TreeModelJTree
TreeSelectionModelJTree

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:

  • Sie bauen eine Komponente komplett eigenständig aus dem zugrunde liegenden Basisframework AWT. Dies ist der schwierigste Weg und nur sinnvoll, wenn die Basiskomponenten von Swing nicht ausreichen.
  • Sie setzen aus Basiskomponenten eine eigenen Komponente zusammen.
  • Sie erweitern eine bestehende Basiskomponente.
Indem Sie das tun schlüpfen Sie in die Rolle eines Komponenten-Entwicklers und implementieren nach dem MVC-Paradigma. Ihre Arbeit ist dann beendet, wenn Sie neben der eigentlichen Komponente ihr model als Interfaces, sowie die Infrastruktur für die action listener und model listener zur Verfügung gestellt haben. In diesem Stadium haben Sie noch nicht eine Zeile fachspezifischen Code implementiert, unbahängig davon, dass ihre Komponente eine fachlich spezifische Aufgabe übernehmen wird.

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 --> S
Speziell 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 <-- S
Das 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:

  • Eclipse bietet eine solche komplette Laufzeit-Infrastruktur (die Eclipse Runtime) und zusätzlich eine Entwicklungsumgebung für eine deklarative Schnittstellendefinition. Die Eclipse Runtime isoliert zur Laufzeit die Services (Plugins) maximal mittels einer ausgefeilten Strategie des Classloadings. Der damit erreichte Grad der Isolierung der Plugins untereinander ist geeignet, komplexe Produkte in sehr heterogenen Entwicklerteams entstehen zu lassen. Eclipse selbst ist das beste Beispiel dafür.
  • Wenn Sie den Overhead einer Plugin-zentrierten Entwicklung mit Eclipse vermeiden wollen, können Sie den Einsatz von Spring in Betracht ziehen. Spring ist perfekt dafür geeignet, die Registrierung und Verdrahtung Ihrer Services und Plugins zu übernehmen. Eine saubere technische Trennung der Plugins ist möglich, da, genau wie bei Eclipse, die Registrierung deklarativ erfolgt. Dieser Ansatz eignet sich damit für große und mittlere Projekte und heterogene Teams.
  • Als überaus leistungsfähige Infrastruktur für den genannten Zweck sollte auch JMX in Betracht gezogen werden. Allerdings ist der Einsatz im Rahmen einer GUI Entwicklung sicher nur für sehr große Projekte gerechtfertigt.
  • Vielleicht können Sie sogar auf jegliches wire up framework verzichten. Bauen Sie einfach alle Plugins und verdrahten Sie sie in der Main-Methode. Damit verlieren Sie zwar die perfekte technische Trennung der Plugins in Ihrer Anwendung, aber Sie finden die Verletzung der Isolierung an einer einzigen, wohl bekannten Stelle. Dieser Ansatz eignet sich für mittlere und kleine Projekte und homogene Teams.

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


  • Asynchrone Verarbeitung muss schon im Anwendungsfall klar sein - Beziehen Sie die Anforderungsanalyse mit ein
  • Asynchrone Verarbeitung unter Swing

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


http://java.sun.com/products/jfc/tsc/articles/architecture/

copyright © 2002-2012 | Dr. Christian Dürr | prozesse-und-systeme.de | all rights reserved