wie würdet Ihr eine komplexe Swing GUI aufbauen ?
Welches Design Pattern würdet ihr verwenden?
ich entwickle seit ein paar Jahren eine recht komplexe GUI mit Swing und habe dabei einiges an Erfahrung gesammelt. Mein erster großer Fehler war es die Daten, also die Inhalte und die Logik, zu sehr mit der GUI zu verdrahten, das sollte man auf keinen Fall machen. Mehrere Zwischenschichten sind ein Muss. Ein anderes großes Problem ist der sehr schnelle Verschleiß der GUI. Mit der Zeit kommen immer neue Java-Versionen mit neuen Sachen raus und die Anforderungen ändern sich ständig. Deshalb wird die GUI ständig umgeschrieben.
Daraus habe ich ein paar Erkenntnisse abgeleitet:
1. Man sollte die GUI von dem restlichen Programm komplett und strikt trennen. Zu einer komplexen GUI gehört ein noch komplexeres Programm welches die Logik und die Daten repräsentiert. Deshalb sollte man, bevor man eine einzige Zeile UI-Code schreibt, sich eine Zwischenschicht überlegen, was den die GUI leisten soll. Das könnte so aussehen:
[highlight="java"]
public interface IMyUI
{
public void buildUI() throws (Irgendeine)Exception;
public void destroyUI() throws (Irgendeine)Exception;
public void showUI(boolean visible);
public void showView(EViewType type, Object param); // EViewType ist ein Enum mit allen möglichen Anzeigearten
public void showData(MyData data);
public void showMessage(EMessageType type, String message);
public void addListener(MyListener l);
public void removeListener(MyListener l);
// weitere Funktionen ...
}
[/highlight]
Diese Zwischenschicht sollte nur die Sachen haben, die man für sein Programm braucht. Hier sollten keine Swing oder AWT Elemente auftauchen. Überall außerhalb des GUI-Parts sollte man nur IMyUI verwenden und java.awt.* und javax.swing.* erst gar nicht zulassen. Das impliziert natürlich, dass man auch eigene Events und Listener definiert und wenn Threads ins Spiel kommen, auch eine eigene Synchronisationsverwaltung schreibt. Es ist ein Aufwand diese Zwischenschicht umzusetzen, aber so zwingt man sich zu überlegen, was man wirklich braucht und ist nicht von den vielen AWT oder Swing Funktionen geblendet. Das Interface IMyUI kann auch ruhig 50 oder 100 Funktionen enthalten, wobei man das mit Subinterfaces lösen kann. Das wichtigste ist eine saubere Trennung.
Ein weiterer Vorteil von so einer strikten Trennung ist, dass man parallel (z.B. von zwei verschiedenen Teams) eine GUI in Swing und eine in SWT umsetzen kann, um zu sehen, was einem besser gefällt. Genau das versuche ich gerade mit meinem Projekt hinzukriegen. Weiterhin kann man mit dem selben Interface, oder einer Erweiterung IMyUI2, schon die GUI 2.0 implementieren, während 1.0 noch produktiv eingesetzt wird.
2. Wenn man eine komplexe GUI will, sollte man diese großenteils wie ein Framework organisieren. Man kann sich ruhig an Swing orientieren. Mit der Zeit ändert sich die GUI recht schnell und auch Java bringt immer wieder neue Sachen mit. So ist das MVC-Konzept Pflicht. Daten immer von der Darstellung trennen. Am besten von keinen Komponenten ableiten, außer man muss paintComponent() überschreiben. Für JTree, JTable oder JList immer eigene Datenmodelle, CellRenderer und EventListener entwickeln, anstatt die Klassen zu überschreiben. Bevor man eigene Komponenten entwickelt sollte man im Internet schauen, ob es die nicht schon fertig gibt, wie z.B. SwingX oder JIDE. Eigene komplexe Komponenten zu schreiben und zu warten ist sehr aufwändig. Ich hatte mal JSpinner selber umgesetzt, bevor es das im JDK gab. Ich glaube, es ist bis Heute nicht fehlerfrei geworden.
3. Genau wie bei Punkt 1 kann man auch die eigenen Darstellungskomponenten komplett abstrakt machen. Hat man z.B. in seinem Programm 3 verschiedene Baum-Darstellungen, so sollte man dafür zuerst 3 Interfaces definieren:
[highlight="java"]
public interface IMyTreeViewDataType01
{
showData(MyDataType01 data);
}
public interface IMyTreeViewDataType02
{
showData(MyDataType02 data);
}
public interface IMyTreeViewDataType03
{
showData(MyDataType03 data);
}
[/highlight]
und danach die realen Komponenten umsetzen. Kann sein, dass am Ende eine einzige Klasse alle diese Interfaces bedient, es können aber auch 10 werden. Wieder haben wir hier den Vorteil, dass man die Version 2.0 oder 3.0 der Komponenten entwickeln und testen kann, ohne das Programm großartig ändern zu müssen.
4. Man sollte nicht zu schlau mit dem Interfacedesign sein. Manchmal reizt es einen, eine komplett einzigartige GUI zu machen, die es so noch nie gab. Da sollte man besondere Vorsicht walten lassen. Die meisten User lehnen neue Sachen ab, weil sie diese nicht verstehen und nicht benutzen können. Ein Beispiel wären z.B. die Ribbons, die Microsoft eingeführt hat. Es wurde mit gespaltener Meinung aufgenommen. Deshalb sollte man sich am besten an das gängige halten, was die meisten User ohne das Handbuch zu lesen sofort erkennen und verwenden können. Neue Sachen oder Interfacearten sollte man in Ruhe mit der Zielgruppe austesten und langsam einführen, damit die User Zeit haben sich daran zu gewöhnen.
5. Das PLAF (Pluggable look and feel) sollte man nicht außer Sicht lassen. Irgendwann will man seine Anwendung in verschiedenen Styles sehen und es gibt auch einige wirklich gute, wie Substance. Deshalb sollte man sehr sparsam mit setFont, setBackground/Foreground, setOpaque oder setBorder umgehen. Am besten integriert man schon von Anfang an ein paar gute PLAFs in sein Programm, zwischen denen man hin und her schaltet. So kann auch der eine oder andere böser Schnitzer erkannt werden, wenn die GUI mit einem anderen PLAF plötzlich kaputt ist.
Eine weitere Herausforderung im Zusammenhang mit dem PLAF sind die Icons. Am besten legt man diese von Anfang an als Vektorgrafik aus, damit man diese dynamisch dem PLAF anpassen kann.
6. Auch wenn die meisten User eine GUI mit der Maus benutzen, sollte man doch von Anfang an so programmieren, dass die ganze GUI komplett mit der Tastatur steuerbar ist. Einerseits ist es guter Programmierstill und sorgt für ein durchgedachtes Design, andererseits benutzen User bei bestimmten Sachen, die z.B. oft wiederholt werden, Tastaturabkürzungen, um einfach schneller zu arbeiten. Niemand wird vielleicht am Ende komplett ohne Maus arbeiten, aber jeder wird seinen Bereich haben, den er am liebsten mit der Tastatur bedient.
7. I18N und L10N! Wenn man sein Programm später in mehreren Sprachen rausbringen will, oder gar lokalisiert (z.B. rechts-zu-links Schreibweise), sollte man das gleich von der ersten Stunde an machen. Dazu bietet Swing gute Unterstützung und es gibt eine Reihe Frameworks, welche die Verwaltung der textlichen Sachen übernehmen. Da sollte man vor allem ein paar Artikel zum Thema lesen, bevor man was eigenes bastelt. I18N und L10N ist ein sehr komplexes Thema, das oft unterschätzt wird. Mit ein paar falsch gesetzten Satzzeichen schafft man es gleich eine ganze Kultur zu beleidigen. Ein anderer grober Fehler sind Labels, in welche die Texte nicht komplett reinpassen.
8. Um wieder auf den Punkt 1 zu verweisen, sollte man sein gesamtes Programm so auslegen, dass es komplett ohne GUI laufen kann. Später kann es passieren, dass man vielleicht das Programm auf einem Server ohne einer Benutzeroberfläche laufen lassen oder es automatisieren will.
So, das war es erst mal, vielleicht fällt mir später noch mehr ein. Der Hauptpunkt ist, den man sich unbedingt merken muss, dass komplexere GUIs
viel Zeit erfordern. Während dessen bleibt aber die Entwicklung nicht stehen und man muss regelmäßig seine Sachen an den aktuellen Stand der Zeit anpassen. Auch, dass man vielleicht für die Version 2.0 alles über den Haufen wirft und eine komplett neue GUI implementiert, ist sehr wahrscheinlich. Deshalb sollte man von Anfang an darauf vorbereitet sein und Abstraktion ist ein gutes Mittel dafür. Abschließend möchte ich sagen, dass die Logik und die Daten die GUI bestimmen sollen und nicht umgekehrt.
Slawa