Skript zur Vorlesung - Vorlesungen

Werbung
Informatik C
Vorlesungsskript WS 2001/2002
Jürgen Schönwälder
Version vom 6. Februar 2002
Fachbereich Mathematik/Informatik
Universität Osnabrück
Vorwort
Die Vorlesung Informatik C vermittelt einen Einblick in die ingenieurmäßige Konstruktion von
komplexen Software-Systemen (Software-Engineering) und vertieft die Java-Programmierkenntnisse zur Realisierung von graphischen Benutzungsoberflächen. Die Vorlesung setzt Kenntnisse aus
den Vorlesungen Informatik A (Algorithmen) und Informatik B (objekt-orientierte Programmierung
mit Java) voraus und vertieft einige Themen aus diesen Vorlesungen.
Einige Beispiele im ersten Kapitel stammen aus einer Vorlesung mit dem Titel Fehlertolerante
”
Systeme“, die ich an der TU Braunschweig angeboten habe. Einige Details im Kapitel über Prozessmodelle und im Kapitel zum Thema Qualitätssicherung sind den Folienskripten zur Vorlesung
Einführung in Software Engineering“ an der TU Braunschweig von Prof. Dr. G. Snelting, Prof. Dr.
”
A. Zeller, Dr. M. Huhn und Dr. A. Zündorf entnommen.
Bedanken möchte ich mich an dieser Stelle auch bei allen Studenten, die durch kritische Fragen dazu beigetragen haben, daß dieses Skript etwas weniger Fehler enthält und hoffentlich besser verständlich geworden ist. Besonderer Dank auch an Elmar Ludwig, der die Übungen im WS
2001/2002 betreut hat und viel konstruktive Kritik an diesem Skript geübt hat.
Jürgen Schönwälder
Inhaltsverzeichnis
I
Software Engineering
1
Einführung
1.1 Softwarekrise . . . . . . . .
1.2 Fehlertolerante Software . .
1.3 Sicherheitskritische Software
1.4 Software-Qualität . . . . . .
2
3
1
.
.
.
.
.
.
.
.
Prozessmodelle
2.1 Code-and-Fix . . . . . . . . . .
2.2 Wasserfallmodell . . . . . . . .
2.2.1 Planungsphase . . . . .
2.2.2 Definitionsphase . . . .
2.2.3 Entwurfsphase . . . . .
2.2.4 Implementierungsphase
2.2.5 Installationsphase . . . .
2.2.6 Wartungsphase . . . . .
2.3 V-Modell . . . . . . . . . . . .
2.4 Prototyping . . . . . . . . . . .
2.5 Spiral-Modell . . . . . . . . . .
2.6 Transformationelles Modell . . .
2.7 Extremes Programmieren (XP) .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Unified Modeling Language (UML)
3.1 Notizen . . . . . . . . . . . . . . . . . .
3.2 Klassendiagramme . . . . . . . . . . . .
3.2.1 Klassen . . . . . . . . . . . . . .
3.2.2 Sichtbarkeit . . . . . . . . . . . .
3.2.3 Gültigkeitsbereiche . . . . . . . .
3.2.4 Generalisierung / Spezialisierung
3.2.5 Abstraktheit . . . . . . . . . . . .
3.2.6 Aggregation / Komposition . . . .
3.2.7 Assoziationen . . . . . . . . . . .
3.2.8 Schnittstellen und Realisierungen
3.2.9 Abhängigkeiten . . . . . . . . . .
3.2.10 Objekte . . . . . . . . . . . . . .
3.2.11 Pakete . . . . . . . . . . . . . . .
3.3 Sequenzdiagramme . . . . . . . . . . . .
3.4 Kollaborationsdiagramme . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
4
6
7
8
.
.
.
.
.
.
.
.
.
.
.
.
.
13
15
16
17
17
18
18
18
19
19
20
20
21
21
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
25
25
25
26
27
27
28
29
30
30
33
34
34
35
36
38
INHALTSVERZEICHNIS
3.5
3.6
3.7
3.8
3.9
3.10
4
5
6
Zustandsdiagramme . . . .
Aktivitätsdiagramme . . .
Anwendungsfalldiagramme
Komponentendiagramme .
Verteilungsdiagramme . .
Beispiel: Fahrstuhl . . . .
Entwurfsmuster
4.1 Factory Method . . . .
4.2 Iterator . . . . . . . . .
4.3 Composite . . . . . . .
4.4 Observer . . . . . . . .
4.5 Strategy . . . . . . . .
4.6 Chain of Responsibility
4.7 Model View Controller
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
40
41
42
44
45
46
.
.
.
.
.
.
.
51
54
55
58
61
65
66
67
Qualitätssicherung
5.1 Typische Softwarefehler . . . . . . . . . . .
5.2 Programminspektionen . . . . . . . . . . . .
5.3 Testverfahren . . . . . . . . . . . . . . . . .
5.3.1 Funktionale Testverfahren . . . . . .
5.3.2 Kontrollfluß-orientierte Testverfahren
5.3.3 Mutationstesten . . . . . . . . . . . .
5.3.4 Regressionstesten . . . . . . . . . . .
5.4 Qualitätssicherung mit ISO 9000 . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
69
69
69
70
71
73
74
75
75
Werkzeuge
6.1 Debugger . . . . . . . . . . . . . . . . . .
6.1.1 Java Debugger (jdb) . . . . . . . .
6.1.2 Data Display Debugger (ddd) . . .
6.2 Programmkonstruktion . . . . . . . . . . .
6.2.1 make . . . . . . . . . . . . . . . .
6.3 Versionsmanagement . . . . . . . . . . . .
6.3.1 Revision Control System (RCS) . .
6.3.2 Concurrent Versions System (CVS)
6.4 Testwerkzeuge . . . . . . . . . . . . . . .
6.4.1 JUnit . . . . . . . . . . . . . . . .
6.5 Integrierte Entwicklungsumgebungen . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
77
77
77
78
78
78
80
80
81
82
82
85
.
.
.
.
.
.
.
.
.
.
.
II Graphische Benutzungsoberflächen
7
Ergonomische Aspekte
7.1 Dialogarten und Dialogmodi . . . .
7.2 Grundsätze zur Dialoggestaltung . .
7.2.1 Aufgabenangemessenheit .
7.2.2 Selbstbeschreibungsfähigkeit
7.2.3 Steuerbarkeit . . . . . . . .
7.2.4 Erwartungskonformität . . .
87
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
89
90
90
90
91
91
91
INHALTSVERZEICHNIS
.
.
.
.
92
92
93
93
8
Java Abstract Windowing Toolkit (AWT)
8.1 AWT Komponenten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.2 Ereignisorientiere Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . .
95
95
96
9
Java Foundation Classes (Swing)
9.1 Einführende Beispiele . . . . . . . . . . . . . . .
9.1.1 Hello World . . . . . . . . . . . . . . . .
9.1.2 Hello World mit Ereignisverarbeitung . .
9.1.3 Celsius Converter . . . . . . . . . . . . .
9.1.4 Celsius Converter mit Dialog . . . . . . .
9.1.5 Look And Feel . . . . . . . . . . . . . .
9.2 Grundlegende Konzepte . . . . . . . . . . . . . .
9.2.1 Root Panes . . . . . . . . . . . . . . . .
9.2.2 Threads . . . . . . . . . . . . . . . . . .
9.3 Layout Manager . . . . . . . . . . . . . . . . . .
9.3.1 Border Layout . . . . . . . . . . . . . .
9.3.2 Box Layout . . . . . . . . . . . . . . . .
9.3.3 Card Layout . . . . . . . . . . . . . . .
9.3.4 Flow Layout . . . . . . . . . . . . . . .
9.3.5 Grid Layout . . . . . . . . . . . . . . . .
9.3.6 Grid Bag Layout . . . . . . . . . . . . .
9.3.7 Absolute Positionierung . . . . . . . . .
9.4 Ränder . . . . . . . . . . . . . . . . . . . . . . .
9.4.1 Einfache Ränder . . . . . . . . . . . . .
9.4.2 Ränder mit Titeln . . . . . . . . . . . . .
9.4.3 Zusammengesetzte Ränder . . . . . . . .
9.5 Menüs . . . . . . . . . . . . . . . . . . . . . . .
9.5.1 Toolbars . . . . . . . . . . . . . . . . . .
9.5.2 Actions . . . . . . . . . . . . . . . . . .
9.6 Scrolling und Splitting . . . . . . . . . . . . . .
9.7 Text Komponenten . . . . . . . . . . . . . . . .
9.7.1 Dokumente . . . . . . . . . . . . . . . .
9.7.2 Ereignisse von Dokumenten . . . . . . .
9.7.3 Dokument Editor . . . . . . . . . . . . .
9.7.4 Edieren von Dokumenten . . . . . . . .
9.8 Dateidialoge . . . . . . . . . . . . . . . . . . . .
9.9 Bäume . . . . . . . . . . . . . . . . . . . . . . .
9.10 Tabellen . . . . . . . . . . . . . . . . . . . . . .
9.11 Zweidimensionale Graphik . . . . . . . . . . . .
9.11.1 Primitive geometrische Formen (Shapes)
9.11.2 Beispiel: Animation springender Bälle . .
9.11.3 Beispiel: Santa Claus . . . . . . . . . . .
7.3
7.2.5 Fehlertoleranz . . . . . . . . . . . .
7.2.6 Individualisierbarkeit . . . . . . . . .
7.2.7 Lernförderlichkeit . . . . . . . . . .
Acht goldene Regeln für die Dialoggestaltung
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
101
102
102
102
103
105
105
108
108
109
109
110
111
113
116
116
117
120
121
121
123
125
126
129
131
134
137
137
138
140
142
148
151
154
158
160
165
175
INHALTSVERZEICHNIS
III
Software-Komponenten und Verteilte Anwendungen
10 Verteilte Anwendungen
10.1 Client/Server-Modell . . .
10.2 N-Tier Architekturen . . .
10.2.1 2-Tier Architektur
10.2.2 3-Tier Architektur
10.2.3 4-Tier Architektur
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
179
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
181
181
182
183
183
184
11 Java Beans
11.1 Properties . . . . . . . . . . . . .
11.2 Explizite Beschreibung einer Bean
11.3 Verpacken einer Bean . . . . . . .
11.4 Beispiel . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
185
185
191
193
194
12 Servlets
12.1 Grundlagen der Web-Technologie
12.1.1 URIs, URLs und URNs .
12.1.2 MIME . . . . . . . . . . .
12.1.3 HTTP . . . . . . . . . . .
12.1.4 HTML . . . . . . . . . .
12.2 Generische Servlets . . . . . . . .
12.3 HTTP Servlets . . . . . . . . . .
12.4 Lebenszyklus und Container . . .
12.5 Sessions . . . . . . . . . . . . . .
12.6 JavaServer Pages (JSP) . . . . . .
12.6.1 Deklarationen . . . . . . .
12.6.2 Ausdrücke . . . . . . . .
12.6.3 Scriptlets . . . . . . . . .
12.6.4 Einfügungen . . . . . . .
12.6.5 Weiterleitungen . . . . . .
12.6.6 Seiten-Direktiven . . . . .
12.7 JavaServer Pages und Beans . . .
12.7.1 Bean-Benutzung . . . . .
12.7.2 Properties . . . . . . . . .
12.8 Beispiel: Raten von Nummern . .
12.9 Tomcat Referenzimplementation .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
199
199
199
199
200
200
201
202
204
204
205
207
207
207
208
208
208
209
209
210
210
212
.
.
.
.
.
.
.
.
.
.
213
213
214
214
214
215
215
216
217
217
217
13 CORBA
13.1 CORBA Grundlagen . . . . . . . .
13.2 Object Request Broker (ORBs) . . .
13.3 Object Services . . . . . . . . . . .
13.4 Common Facilities . . . . . . . . .
13.5 Komponenten eines ORBs . . . . .
13.6 Interface Definition Language (IDL)
13.6.1 Typsystem . . . . . . . . .
13.6.2 Konstanten . . . . . . . . .
13.6.3 Ausnahmen . . . . . . . . .
13.6.4 Interfaces . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
INHALTSVERZEICHNIS
13.6.5 Module . . . . .
13.7 Objekt-Referenzen . . .
13.8 Java Language Mapping
13.9 Naming Service . . . . .
13.10Mau Mau . . . . . . . .
13.11JDK’s Java IDL . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
219
219
219
222
224
240
14 Java Name and Directory Interface (JNDI)
241
14.1 JNDI-Terminologie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
14.2 Zugriff auf Namensdienste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
14.3 Zugriff auf Verzeichnisdienste . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
15 Enterprise Java Beans
249
15.1 Eigenschaften von EJBs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
15.2 Eigenschaften eines Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
15.3 Aufbau einer EJB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
Teil I
Software Engineering
1
Kapitel 1
Einführung
Eigenschaften von Software (Balzert [1]):
• Software ist ein immaterielles Produkt.
Software kann man nicht anfassen oder sehen. Entsprechend schwierig ist es beispielsweise
festzustellen, ob die Dokumentation mit dem lauffähigen Programmcode übereinstimmt.
• Software unterliegt keinem Verschleiß.
Software nutzt sich nicht ab und es ist daher eine klassische Wartung (Austausch verschlissener Teil) nicht notwendig.
• Software wird nicht durch physikalische Gesetze begrenzt.
Software basiert nicht auf physikalischen Gesetzen und erlaubt damit dem Schöpfer von Software einen großen Gestaltungsspielraum.
• Software ist leicht und schnell änderbar.
Gut strukturierte und modularisierte Software ermöglicht es, Änderungen einfach und schnell
durchzuführen.
• Software altert.
Zunächst scheint die Aussage falsch zu sein, da Software keinem Verschleißprozeß unterliegt.
Berücksichtigt man jedoch, daß sich die Umgebung, in der eine Software eingesetzt wird,
ständig ändert, dann wird diese Aussage verständlich.
• Software ist schwer zu vermessen.
Für Software gibt es keine exakten Meßgeräte. Die Qualität von Software ist schwer definierbar und quantifizierbar.
Definition der Begriffs Software-Engineering (Software-Technik):
• Die zielorientierte Bereitstellung und die systematische Verwendung von Prinzipien, Methoden und Werkzeugen für die arbeitsteilige Entwicklung und Anwendung von umfangreichen
Software-Systemen. Zielorientiert bedeutet Berücksichtigung z.B. von Kosten, Zeit, Qualität.
(Balzert [1])
• Das ingenieurmäßige Entwerfen, Herstellen und Implementieren von Software sowie die ingenieurwissenschaftliche Disziplin, die sich mit Methoden und Verfahren zur Lösung der
damit verbundenen Problemstellungen befaßt. (Brockhaus-Enzyklopädie)
3
4
KAPITEL 1. EINFÜHRUNG
• The systematic approach to the development, operation, maintenance, and requirement of
software. (ANSI/IEEE Standard Glossary of Software Engineering Terminology, 1983)
• Software engineering = multi-person construction of multi-version software. (Parnas 1987)
• The application of a systematic, disciplined, quantifiable approach to the development, operation, and maintenance of software; that is, the application of engineering to software. (ANSI/IEEE Standard Glossary of Software Engineering Terminology, 1990)
1.1 Softwarekrise
Ende der 60er Jahre wurden die ersten großen kommerziellen Softwaresysteme entwickelt und beobachtet, daß die vorhandenen Vorgehensweisen nicht erfolgreich auf die neuen größeren Projekte
übertragen werden konnten. Viele Softwareprojekte scheiterten und dadurch wurde der Begriff der
Softwarekrise geprägt.
Die aktuelle Lage ist nach Untersuchungen von Laprie (1999) immer noch nicht rosig:
• Gut 13 der Software-Projekte wird zur Zufriedenheit des Kunden und des Anbieters durchgeführt.
• Bei 13 der Software-Projekte wird ein Produkt oder eine Dienstleistung abgeliefert, die in
wesentlichen Teilen den Anforderungen des Kunden nicht entspricht.
• Knapp
1
3
der Software-Projekte enden ohne Auslieferung der Leistung an den Kunden.
Häufige Problemquellen:
• Größe und Komplexität von Softwaresystemen
SAP R/3 hatte 1994 ca. 7.000.000 Zeilen Quellcode (in der Hochsprache Abab bzw. C++),
ca. 100.000 Funktionsaufrufe, ca. 20.000 Funktionen, ca. 14.000 Funktionsbausteine und ca.
17.000 Menüleisten. SAP hat allein am Hauptsitz in Walldorf mehrere tausend Entwickler
beschäftigt.
• Integration mit anderen (informations)technischen Systemen
Fast alle Software-Systeme müssen heutzutage mit anderen (informations)technischen Systemen interagieren und Daten austauschen können (Business to Business, Business to Customer).
• Steigende Qualitätsanforderungen
Nach Cusumano (MIT Sloan School of Management) wurden pro 1000 Zeilen Quelltext
1977 durchschnittlich 7-20 Defekte gefunden. 1994 waren es noch durchschnittlich 0.05-0.2
Defekte. Die Defektrate konnte also um ungefähr das 100fache in 15 Jahren gesenkt werden.
Allerdings ist ein Defektniveau von einem Defekt auf 1000 Zeilen Quelltext bei weitem noch
nicht akzeptabel. Für die Personenbeförderung ist beispielsweise eine Fehlerrate < 10−7 des
technischen Geräts notwendig.
• Flexibilitätsanforderungen bei der Entwicklung
1.1. SOFTWAREKRISE
5
– Produktanforderungen und die umgebenden Komponenten verändern sich, insbesondere in den späteren Phasen des Software-Lebenszyklus.
– Die eingesetzten Methoden und Werkzeuge verändern sich ebenfalls während der Entwicklung.
• Portabilitätsanforderungen
– Anwendungssoftware hat eine Lebensdauer von 10-15 Jahren. In der Regel muß die
Software ca. 1-2 Wechsel der darunterliegenden Systemsoftware sowie 3-4 Hardwarewechsel überstehen.
– Durch den Trend zu Standardsoftware müssen viele Versionen für spezifische KundenAnforderungen erstellt und gewartet werden (Hardwareanpassungen, Sprachanpassungen der Benutzungsoberfläche, länder- und anwenderspezifische Vorschriften).
• Organisation der Entwicklung und Wartung
– Häufig scheitern Softwareprojekte nicht an technischen Problemen, sondern an schlechter Kommunikation (Benutzer - Entwickler, Entwickler - Entwickler), mangelhafter
Ausbildung, hoher Personalfluktuation und mangelhaftem Projektmanagement.
Prominente Probleme und Katastrophen:
• Viele Programme des letzten Jahrhunderts speicherten Jahreszahlen als zweistellige Dezimalzahlen (73 für 1973), was bei der Umstellung auf das Jahr 2000 verstärkt zu Problemen
führen kann (00-73 = -73 anstatt 2000-1973 = 27). Mit einem enormen Aufwand mußten alte
Programme getestet und überarbeitet werden.
• Häufige Ausfälle bei Büro-Standardsoftware marktführender Hersteller.
• Der Verlust des Mars-Climate-Orbiters im September 1999 ist auf mangelnde Kompatibilität zurückzuführen. Die NASA hatte zwei Firmen mit der Implementation von SoftwareKomponenten für die Navigation beauftragt. Die eine berechnete die physikalischen Daten
in metrischen Maßeinheiten, während die andere mit englischen Maßeinheiten rechnete. Die
Verwendung unterschiedlicher Maßeinheiten war bekannt und wurde an fast allen Stellen
berücksichtigt. Erst beim Anflug der Marsatmosphäre führte eine Interaktion dieser Komponenten mit Daten in der falschen Maßeinheit dazu, daß der Orbiter zu nah an den Mars geriet
und verglühte.
• Die amerikanische Viking Venus Sonde wurde durch eine fehlerhaft programmierte DOSchleife (Fortran) verloren. Anstatt des korrekten Quelltextes
DO 20 I = 1,100
enthielt das Programm die folgende Zeile:
DO 20 I = 1.100
Als Folge wurde DO20I als eine implizite Variable interpretiert und eine Zuweisung durchgeführt anstatt eine Schleife zu durchlaufen (Fortran ignoriert Leerzeichen).
• Viele weitere Beispiele findet man unter http://catless.ncl.ac.uk/Risks/.
6
KAPITEL 1. EINFÜHRUNG
1.2 Fehlertolerante Software
Fehlertolerante Software versucht auch in bestimmten möglichen Fehlersituationen weiterhin korrekte Ergebnisse zu liefern. Fehlertoleranz ist immer dann erforderlich, wenn der Ausfall eines
Software-Systems katastrophale Folgen haben kann:
• Verlust von Aktienwerten durch fehlerhafte oder nicht vorhandene Informationen (Materieller
Schaden)
• Ausfall eines Flugüberwachungssystems (Materielle Schäden und ggf. Verluste von menschlichem Leben)
• Fehlfunktion oder Ausfall einer Prozeßsteuerungsanlage (Materielle Schäden und ggf. ökologische und Gesundheitsschäden)
• Fehlfunktionen in elektronischen Stellsystemen für den spurgeführten Verkehr (Materielle
Schäden und evtl. Gefährdung menschlichen Lebens)
• Fehlfunktion oder Ausfall eines medizinischen Hilfsgeräts (Personenschäden und evtl. Verlust von menschlichem Leben)
Ausfälle entstehen durch Fehler im System oder von außen kommende Störungen. Prinzipiell gilt:
• Je mehr Komponenten ein System besitzt, desto mehr können fehlerhaft sein.
• Je komplexer eine Komponente ist, umso größer ist die Chance, dass die Komponente fehlerhaft ist.
Beispiel Telefon-Vermittlungssysteme:
• Typische Anforderungen an Telefon-Vermittlungssysteme (AT&T, USA):
– Weniger als 1 Stunde ungeplante Ausfallszeit in 20 Jahren bei fortlaufendem Betrieb.
(Das entspricht wenige als 3 Minuten Ausfallszeit pro Jahr.)
– Maximal eine von 10.000 Telefonverbindungen darf durch einen Fehler unterbrochen
werden.
– Die Zeit vom Abnehmen des Hörers bis zum Freizeichen darf maximal 1 Sekunde betragen.
– Bei der Durchführung von Wartungsarbeiten (z.B. Erneuerung von Software und Hardware) dürfen keine bereits begonnenen Telefongespräche gestört werden.
– Die benutzte Software, die System-Konfiguration und die Datenbanken müssen ohne
Unterbrechung des Betriebs gepflegt werden können.
• Ein Ausfall des Backbone-Netzes von AT&T von 9 Stunden Dauer hat im Januar 1990 zu
einem Schaden von 60-75 Millionen US $ geführt.
• Nach einer US Studie verlieren große Firmen bei Ausfällen ihrer lokalen Netzinfrastruktur
mehr als 30.000 US $ pro Stunde.
1.3. SICHERHEITSKRITISCHE SOFTWARE
7
1.3 Sicherheitskritische Software
Beispiel Eisenbahnen:
• Klassische Eisenbahnen funktionieren nach dem failsafe-Prinzip, bei dem alle möglichen
Ausfälle dürfen nur in einen sicheren Zustand führen dürfen.
• Die klassische Signaltechnik nutzt unverlierbare physikalische Eigenschaften (wie z.B. die
Schwerkraft oder Magnetismus), um Signale failsafe zu realisieren.
• Elektronische Stellwerke werden mit spezieller redundanter Rechnerhardware aufgebaut, die
ebenfalls beim Ausfall nachweisbar die Einnahme eines sicheren Zustands garantiert.
• Die Programmierung elektronischer Stellwerke erfolgt mit aufwendigen Prüfungen (Inspektionen des Quelltextes, Abnahmeprüfungen) und mit speziellen geprüften Entwicklungswerkzeugen.
Beispiel Flugzeuge:
• Flugzeuge können im Gegensatz zu Eisenbahnen bei Ausfällen nicht einfach in einen sicheren
Zustand gebracht werden.
• Flugunfälle haben meist schwerwiegende Folgen, oftmals mit Verlusten von Leben.
• Zur Sicherung wird Redundanz eingesetzt, wobei maximal die Wahrscheinlichkeit eines katastrophalen Ausfalls hinreichend klein gemacht werden kann, aber grundsätzlich keine Katastrophen ausgeschlossen werden können.
Subjektive Sicherheitsbewertung durch den Menschen:
• Der Mensch beurteilt die Sicherheit eines technischen Systems subjektiv und nicht objektiv.
• Ein seltener spektakulärer Unfall findet mehr Beachtung als häufige unspektakuläre Unfälle.
• Nach einem spektakulären Unfall finden eine gewisse Zeit auch unspektakuläre Unfälle Beachtung, die sonst nicht beachtet würden.
• Die Möglichkeit der direkten Einflußnahme läßt Systeme sicherer erscheinen.
• Die zu erwartenden persönlichen Vorteile durch die Benutzung eines Systems beeinflussen
die Risikobereitschaft.
Ansätze zu einer objektiven Sicherheitsbewertung:
• Mit Hilfe von Wahrscheinlichkeitsmodellen wird die Schadenswahrscheinlichkeit eines technischen Systems ermittelt.
• Das allgemeine Lebensrisiko ist die Wahrscheinlichkeit einer Person, innerhalb einer Stunde
das Leben zu verlieren.
• Es wird allgemein angenommen, daß eine Absenkung der Schadenswahrscheinlichkeit unter
das allgemeine Lebensrisiko (10−7 ) nicht sinnvoll ist.
8
KAPITEL 1. EINFÜHRUNG
−6
Lebensrisiko
10
−7
10
Alter
10
20
30
40
50
60
Abbildung 1.1: Lebensrisiko im Bevölkerungsdurchschnitt (1950-1952)
1.4 Software-Qualität
Software-Qualität ist nach DIN ISO 9126 die Gesamtheit der Merkmale und Eigenschaften eines
Software-Produkts, die sich auf dessen Eignung beziehen, festgelegte oder vorausgesetzte Erfordernisse zu erfüllen.
Software-Qualität wird typischerweise aus zwei Sichten beurteilt:
1. Anwendersicht: Aspekte des Systems, die für den Anwender wichtig und sichtbar sind wie
Funktionsumfang, Effizienz, Benutzbarkeit oder Sicherheit.
2. Entwicklersicht: Aspekte des Systems, die für den Entwicklungsprozeß, den Einsatz und die
Pflege relevant sind wie Testbarkeit, Wartbarkeit, Erweiterbarkeit oder Wiederverwendbarkeit.
Beispiel: Kunden beurteilen die Qualität von Benutzungsoberflächen nach den Möglichkeiten sich
verschiedenen Benutzerklassen (Anfänger, Experte) anzupassen. Entwickler beurteilen die Qualität
nach einer gelungenen Entkoppelung der Oberfläche von der Anwendungslogik.
Anmerkungen:
• Es ist die Verantwortung des Entwicklers, die Qualität aus Entwicklersicht zu gewährleisten.
• Die Qualitätsanforderungen aus Anwendersicht müssen erfüllt werden, auch wenn sie dem
Entwickler weniger wichtig erscheinen.
• Software-Qualität kann nur vor dem Hintergrund der Kosten und des Zeitrahmens eines Entwicklungsprozesses beurteilt werden.
Qualitätsmerkmale nach DIN ISO 9126:
• Funktionalität: Vorhandensein von Funktionen mit festgelegten Eigenschaften. Diese Funktionen erfüllen die definierten Anforderungen.
1.4. SOFTWARE-QUALITÄT
9
• Zuverlässigkeit: Fähigkeit der Software, ihr Leistungsniveau unter festgelegten Bedingungen
über einen festgelegten Zeitraum zu bewahren.
• Benutzbarkeit: Aufwand, der zur Benutzung erforderlich ist, und individuelle Beurteilung der
Benutzung durch eine festgelegte vorausgesetzte Benutzergruppe.
• Effizienz: Verhältnis zwischen dem Leistungsniveau der Software und dem Umfang der eingesetzten Betriebsmittel unter festgelegten Bedingungen.
Es ist oft relativ einfach die Effizienz eines gut strukturierten Programms nachträglich zu verbessern. Andererseits ist es in der Regel extrem aufwendig, ein zwar effizientes aber unstrukturiertes Programm zu erweitern. Bei Maßnahmen zur Effizienzverbesserung ist es sinnvoll
strukturiert vorzugehen, da das intuitive Verständnis vom dynamischen Programmverhalten
oftmals falsch ist.
Vorgehensweise zur Effizienzverbesserung (tuning):
1. Kritische Teile des Programms identifizieren (Messungen, Simulationsmodelle, analytische Verfahren)
2. Gezielte Optimierung der kritischen Stellen (bessere Algorithmen, hardwarenahe Realisierung kritischer Teile, Verteilung der Funktionen auf mehrere Prozessoren)
3. Einsatz schnellerer Hardware (Vorsicht vor wachsenden Grundlasten)
• Änderbarkeit: Aufwand, der zur Durchführung vorgegebener Änderungen notwendig ist. Änderungen können Korrekturen, Verbesserungen oder Anpassungen an Änderungen der Umgebung, der Anforderungen und der funktionalen Spezifikation sein.
• Übertragbarkeit: Eignung der Software, von einer Umgebung in eine andere übertragen zu
werden. Umgebung kann organisatorische Umgebung, Hardware- und Software-Umgebung
einschließen.
Weitere Qualitätsmerkmale:
• Robustheit: Software ist robust, wenn der potentielle Schaden der durch Gebrauch, Fehlbedienung oder Mißbrauch ausgelöst werden kann, gering ist.
Beispiel: Ein Editor, der auf die Eingabe unzulässiger Befehle mit der Zerstörung der bearbeiteten Datei reagiert, ist in unzumutbarer Weise nicht robust.
Viele Programme reagieren auf unerwartete Eingaben unkontrolliert [11]. Um die Robustheit
zu erhöhen wird oft das Verhalten in zu erwartenden Fehlersituationen spezifiziert und damit
wird Robustheit ein Teil der Korrektheit.
• Wartbarkeit: Bei wartbarer Software können Korrekturen, Systemanpassungen und Verbesserungen ohne großen Aufwand durchgeführt werden.
Nach etlichen Studien machen Wartungskosten 60-80% der Softwarekosten aus. Davon sind
ca. 20% Beseitigung von Fehlern, ca. 20% Adaptionen wie die Anpassung an neue Betriebssystemversionen und ca. 50% Vervollkommnung des Produkts (wie z.B. die Umstellung auf
eine graphische Benutzungsoberfläche oder die Internationalisierung).
Wartbarkeit wird unterstützt durch eine modulare Systemstruktur mit überschaubaren Schnittstellen, gute Dokumentation und wohldefinierte Änderungsprozeduren (Versions- und Variantenkontrolle). In einigen Fällen ist eine Möglichkeit zur Wartung der Software während des
Betriebs erforderlich.
10
KAPITEL 1. EINFÜHRUNG
• Erweiterbarkeit: Bei erweiterbarer Software wird der Aufwand zur Erweiterung des Funktionsumfangs im wesentlichen vom Umfang der Erweiterung bestimmt.
• Korrektheit: Korrekte Software erfüllt ihre Aufgabe gemäß der Spezifikation.
– Ohne Spezifikation kann Korrektheit nicht gezeigt werden.
– Nur eine hinreichend genaue Spezifikation erlaubt eine Korrektheitsprüfung.
– Eine Validation ist eine Überprüfung des Systems durch Tests.
– Eine Verifikation ist ein mathematischer Beweis, dass ein System eine formale Spezifikation erfüllt.
– Korrekte Software garantiert keine zufriedenen Kunden.
– Fehler und Auslassungen in der Spezifikation können ganze Softwareprojekte scheitern
lassen.
• Kompatibilität und Interoperabilität: Die Software kooperiert mit anderen technischen Systemen (Software) in der vorgesehenen Art und Weise.
Benutzer möchten in der Regel bisherige Daten weiterverwenden und Aufgaben, die sie mit
den vorhandenen Systemen zufriedenstellend erledigen konnten, auch weiterhin in der gewohnten Weise durchführen.
Kompatibilität wird verbessert durch offene Systeme und Standards:
– wohldefinierte Schnittstellen (z.B. Dateiformate)
– normierte Protokolle und Architekturen (TCP/IP, CORBA)
– eigenständige Module mit festen Schnittstellen, die der Benutzer kombinieren kann
• Sicherheit: Sichere Software wahrt die Integrität der Daten, schützt sie vor unberechtigtem
Zugriff und erfüllt insbesondere anwendungsspezifische und gesetzliche Bestimmungen.
Der deutsche Begriff Sicherheit umfaßt die Begriffe Informationssicherheit (Security) und
Funktionssicherheit (Safety):
– Informationssicherheit ist der Schutz der Vertraulichkeit und und die Integrität von Daten und Vorgängen.
– Funktionssicherheit ist die Eigenschaft eines Systems, daß die realisierte Funktion der
Komponenten mit der Spezifikation übereinstimmt und das System keine funktional
unzulässigen Zustände annimmt.
• Ergonomie: Software-Ergonomie befaßt sich mit der Gestaltung interaktiver Programmsysteme und entwickelt Kriterien und Methoden, diese den menschlichen Bedürfnissen entgegenkommend zu gestalten.
– Angemessene Schnittstellen für verschiedene Anwenderklassen.
– Oberflächenelemente sollten in jedem Fenster einer Anwendung einheitlich angeordnet
und beschriftet sein.
– Vernünftige Reaktionen auf fehlerhaftes Benutzerverhalten (brauchbare Fehlermeldungen, undo/redo-Funktionen).
1.4. SOFTWARE-QUALITÄT
11
• Erlernbarkeit: Die Eigenschaften eines Softwaresystems, die das Erlernen des Umgangs mit
einer Software erleichtern.
– Style-Guides für graphische Benutzungsoberflächen führen zu einer Vereinheitlichung
der verschiedenen Anwendungen.
– Einhalten von Quasi-Standards und insbesondere von Betriebssystemmechanismen.
– Lern- und Hilfsprogramme
– Gute Dokumentation
• Wiederverwendbarkeit: Wiederverwendbare Software kann für neue Anwendungen mit wenig Aufwand angepaßt werden.
– Ziel ist ein Baukastensystem, bei dem Software aus Standardskomponenten zusammengesetzt wird.
– Beispiele für erfolgreiche Wiederverwendung: Programmbibliotheken, Fenstersysteme,
Datenbanken
– Wiederverwendung wird hauptsächlich durch Abstraktion erreicht und erfordert einen
Entwicklungsprozeß auf hohem Abstraktionsniveau.
Literaturhinweise
Ein gutes und sehr umfassendes Lehrbüch zum Thema Software-Engineering ist [1]. Unbedingt
lesenswert sind auch [4] und [5]. Das Buch von F. Brooks dokumentiert Erfahrungen, die 1975 bei
der Entwicklung eines neuen Betriebssystems für IBM Mainframes gemacht wurden und auch heute
noch in vielen Projekten beobachtet werden können. Das Buch von T. DeMarco [5] beschreibt sehr
unterhaltsam in einer Art Roman wie viele Software-Projekte in der Realität tatsächlich ablaufen.
12
KAPITEL 1. EINFÜHRUNG
Kapitel 2
Prozessmodelle
Um Softwareentwicklung soll nach festen Regeln nachvollziehbar und systematisch durchführen zu
können, braucht man sogenannte Prozessmodelle, die einen bestimmten organisatorischen Rahmen
zur Entwicklung und Pflege von Software festlegen (Vorgehensmodell). Prozessmodelle legen insbesondere fest, welche Aktivitäten in welcher Reihenfolge von welchen Personen erledigt werden
und welche Ergebnisse dabei entstehen und wie diese in der Qualitätssicherung überprüft werden.
• Im Prozessmodell definierte Aktivitäten werden durch Personen ausgeführt, die definierte
Rollen einnehmen.
• Die Ergebnisse werden auch als Artefakte (artifacts) bezeichnet. (Ein Artefakt ist ein durch
menschliches Können geschaffenes Kunsterzeugnis.)
• Format und Struktur der Artefakte werden durch Muster bestimmt.
• Ein Prozessmodell definiert Verantwortlichkeiten und Kompetenzen.
• Ein Prozessmodell legt Richtlinien, Methoden und Werkzeuge fest, die während der Entwicklung beachtet/benutzt werden müssen.
Das Software Engineering Institute (SEI) and der Carnegie Mellon University hat ein 5-StufenModell (capability maturity model, CMM) entwickelt, mit dem sich tatsächlich erreichte ProzessQualität beschreiben läßt:
1. Initial Level
• Ad-hoc Vorgehensweise und Programmierung, wenig Formalisierung
• Werkzeuge und Methoden werden uneinheitlich eingesetzt
• Projektmanagement durch informelle Absprachen
⇒ Kosten, Termine und Qualität sind unkontrolliert.
2. Repeatable Level
• Vorgehen (Entwicklungsmethoden, Programmierstil) informell vereinheitlicht
• Werkzeuge zur Konfigurationsverwaltung und zum Terminmanagement
13
14
KAPITEL 2. PROZESSMODELLE
l
ontro
ess C
Proc
Optimized
t
emen
anag
ess M
Proc
ion
efinit
ss D
roce
(Level 5)
Managed
(Level 4)
P
Basic
geme
Mana
trol
t Con
Defined
n
(Level 3)
Repeatable
(Level 2)
Initial
(Level 1)
Abbildung 2.1: capability maturity model
• Management und Hop- oder Topentscheidungen funktionieren
⇒ Termine kontrolliert, Qualität und Kosten schwanken.
3. Defined Level
• Vorgehensweise formalisiert, standardisiert und überprüft
• Prozessmodelle vorhanden
• Werkzeuge für Prozessmanagement-Aufgaben (Sammlung von Kenngrößen)
• Werkzeuge, die den definierten Entwicklungsprozess unterstützen
⇒ Termine und Kosten kontrolliert, Qualität schwankt.
4. Managed Level
• Metriken zum Softwareentwicklungsprozess vorhanden
• Qualitätssicherung voll integriert
• Detaillierte Planung (sehr gute Schätzmethoden) und Kontrolle möglich
⇒ Qualität, Termine und Kosten kontrolliert.
5. Optimized Level
• Der Entwicklungsprozess selbst kann kontrolliert verbessert werden.
Nach Studien des SEI an 3500 Softwareprojekten in den USA (Mai 1998) befinden sich 58% der
Projekte im Initial Level, 24% im Repeatable Level, 12% im Defined Level, 2% im Managed Level
und 1% im Optimized Level. Vergleiche mit Studien von 1991 zeigen, daß sich die Prozess-Qualität
deutlich verbessert hat.
15
2.1. CODE-AND-FIX
2.1 Code-and-Fix
Das wohl einfachste Modell ist die Code-and-Fix Vorgehensweise, bei der Entwickler ohne ein
festgelegtes Prozessmodell Funktionen implementieren. Anschließend werden die so entstandenen
Komponenten ad-hoc getestet und gegebenfalls verbessert.
Dieses Modell ist sicherlich jedem Studenten aus eigenen Erfahrungen beim Lösen von Übungsaufgaben vertraut. Für kleine Projekte ist der Ansatz auch durchaus angemessen, er versagt aber total
bei größeren Software-Projekten.
Code schreiben
Testen
Verbessern
Erweitern
Abbildung 2.2: Code-and-Fix Vorgehensmodell
Bewertung:
+ Geeignet für 1-Personen-Projekte im Umfang von Wochen, insbesondere wenn Entwickler
und Anwender identisch sind.
- Wartbarkeit sinkt kontinuierlich
- Fehlerwahrscheinlichkeit steigt
- Abhängigkeit von Entwickler durch fehlende Dokumentation
- Tests und Verbesserungen häufig nicht zufriedenstellend
- Häufig erfüllt das Produkt nicht die Anforderungen des Kunden
⇒ Größere Projekte können so nicht bewältigt werden
16
KAPITEL 2. PROZESSMODELLE
2.2 Wasserfallmodell
Das Wasserfallmodell ist ein erster grundlegender Ansatz zur Strukturierung des Entwicklungsprozesses. Es taucht in der Literatur in vielen verschiedenen Variationen auf. Der Name drückt aus,
daß man sich wie bei einem mehrstufigen Wasserfall von der Planungsphase zur Wartungsphase
bewegt.
Planungsphase
Definitionsphase
Entwurfsphase
Implementierungs−
phase
Installationsphase
Wartungsphase
Abbildung 2.3: Wasserfallmodell
• Grundlegendes Modell mit vielen Varianten.
• Weitere Unterteilung der Phasen (z.B. Implementationsphase → Implementationsphase und
Integrationsphase) möglich.
• Jede Phase ist in der angegebenen Reihenfolge vollständig durchzuführen.
• Jede Phase endet mit der Erstellung eines Dokuments.
2.2. WASSERFALLMODELL
17
• Orientiert sich stark an der Top-Down-Vorgehensweise.
• Möglichkeit zur Rückkehr zu früheren Phasen falls Fehler oder Inkonsistenzen entdeckt werden.
Bewertung:
+ Leicht verständliches Prozessmodell
+ Trennung des Was“ vom Wie”
”
”
- Kundenbeteiligung ist nur in der Definitionsphase vorgesehen
- Hohe Kosten falls die Spezifikation nicht korrekt die Kundenwünsche wiedergibt
- Starres Modell: Oftmals ist es sinnvoller, Entwicklungsschritte nicht genau in der vorgegebenen Reihenfolge oder vorläufig nur partiell durchzuführen.
- Konzentration auf die Entwicklung; Kriterien wie Änderungsfreundlichkeit sind zu wenig
berücksichtigt.
2.2.1
Planungsphase
Das Planen eines Softwareprodukts beinhaltet folgende Einzelaktivitäten:
• Auswählen des Produkts durch Trendstudien, Marktanalysen, Forschungsergebnisse, Kundenanfragen, Vorentwicklungen.
• Voruntersuchung des Produkts durch eine IST-Analyse und Festlegung von Hauptanforderungen und Leistungs- und Qualitätsmerkmalen.
• Durchführbarkeitsuntersuchung (Realisierbarkeit, Alternativen, Wirtschaftlichkeitsprüfung,
Prüfung der organisatorischen Rahmenbedingungen)
Das Ergebnis ist eine Durchführbarkeitsstudie bestehend aus folgenden Artefakten:
• Lastenheft (grobes Pflichtenheft)
• Glossar (Begriffslexikon)
• Projektkalkulation
• Projektplan
2.2.2
Definitionsphase
• Ermitteln der detaillierten Anforderungen
• Beschreibung und Analyse der Anforderungen
• Modellierung, Simulation, Animation
18
KAPITEL 2. PROZESSMODELLE
Das Ergebnis ist eine verbale Beschreibung der Anforderungen in Form eines Pflichtenhefts. Das
Pflichtenheft soll
- klären was das Produkt mit welchen Qualitätsmerkmalen leisten soll (nicht wie)
- verständlich und eindeutig sein (für Kunde und Entwickler)
- konsistent und vollständig sein
2.2.3
Entwurfsphase
Aufgaben in der Entwurfsphase (Programmieren im Großen):
• Festlegung der Softwarearchitektur (Zerlegung des Systems in Systemkomponenten)
• Ermitteln und Festlegen der Umgebungs- und Randbedingungen
• Spezifikation der einzelnen Systemkomponenten
Das Ergebnis dieser Phase ist ein Produktentwurf der aus einer Software-Architektur und den Spezifikationen der Systemkomponenten besteht.
2.2.4
Implementierungsphase
Die Implementierung des Produkts (Programmieren im Kleinen) beinhaltet folgende Aktivitäten:
• Konzeption der Datenstrukturen und Algorithmen
• Strukturierung des Programms durch geeignete Verfeinerungsebenen
• Dokumentation der Problemlösung und der Implementierungsentscheidungen
• Angaben zur Zeit- und Speicherkomplexität sowie Randbedingungen
• Umsetzung in eine Programmiersprache
• Test oder Verifikation des entwickelten Programms
Das Ergebnis der Implementierungsphase ist ein Quellprogramm (inklusive integrierter Dokumentation), ein Objektprogramm, die Testplanung und ein Testprotokoll.
2.2.5
Installationsphase
In der Installationsphase wird das Softwareprodukt ausgeliefert. Das erfolgt häufig in zwei Stufen:
• Auslieferung an ausgewählte Kunden, die Erfahrungen und Fehler zurückmelden (Beta-Test)
• Auslieferung an alle Kunden
Bei der Auslieferung wird in der Regel ein Abnahmetest durchgeführt, der in einem Abnahmeprotokoll festgehalten wird.
19
2.3. V-MODELL
2.2.6
Wartungsphase
Die in der Wartungsphase zu errichtenden Aktivitäten sind lassen sich in folgende Kategorien einteilen:
• Stabilisierung / Korrektur
• Optimierung / Leistungsverbesserung
• Anpassung / Änderung
• Erweiterung
Der Aufwand für die Wartung ist in der Regel um den Faktor 2-4 größer als der Entwicklungsaufwand. Es lohnt sich also, bei der Entwicklung auf die Wartbarkeit zu achten.
2.3
V-Modell
Das V-Modell ist eine Weiterentwicklung des Wasserfallmodells. Es drückt insbesondere die große
Bedeutung der Validierung durch Tests aus. Das V-Modell ist bei der Softwareentwicklung für
staatliche deutsche Organisationen und Behörden vorgeschrieben.
Anwendungsszenarien
Anforderungs−
definition
Abnahmetest
Testfaelle
Grobentwurf
Systemtest
Testfaelle
Feinentwurf
Integrationstest
Modulimple−
mentation
Testfaelle
Modultest
Abbildung 2.4: V-Modell
Bewertung:
+ Integration des Qualitätssicherung in das Wasserfallmodell
20
KAPITEL 2. PROZESSMODELLE
+ Gut geeignet für große Projekte
- Bürokratie-Overhead für kleine und mittlere Projekte
- Strikter Phasenablauf
- Nicht methodenneutral und kaum Unterstützung durch Werkzeuge
2.4
Prototyping
Beim Prototyping-Ansatz werden möglichst früh sogenannte Prototypen (Vorführmodelle) für das
zu entwickelnde System mit reduzierter Funktionalität oder Qualität entwickelt.
• Ein Demonstrationsprototyp dient der Auftragsakquisation und mit meist mit Generatoren
und Script-Sprachen implementiert.
• Ein Horizontaler Prototyp realisiert nur spezifische Ebenen des Systems (z.B. die Benutzungsoberfläche), die aber möglichst vollständig.
• Ein Vertikaler Prototyp realisiert einen abgemagerten Funktionsumfang auf allen Ebenen.
• Ein Pilotsystem ist ein Prototyp einer gewissen Stabilität der einen eingeschränkten Einsatz
erlaubt.
Bewertung:
+ Der Kunde erhält frühzeitig einen Prototypen zum experimentieren.
+ Erfahrungen und Änderungswünsche können frühzeitig integriert werden.
- Große Gefahr in das Code-and-Fix Vorgehen zurückzufallen.
- Große Versuchung, den Prototypen zum Endprodukt auszubauen.
2.5
Spiral-Modell
Das Spiral-Modell ist ein evolutionäres Modell bei dem zuerst Kernkomponenten identifiziert und
realisiert werden. Ein weiteren Teilprojekten werden später weitere Komponenten hinzu kommen.
• Bei der Spezifikation und dem Entwurf von Kernkomponenten bereits Erweiterungen berücksichtigen.
• Architektur offen für Erweiterungen halten.
• Jeweils prüfen, ob Wiederverwendung von Komponenten oder der Einsatz von Standardsoftware möglich ist.
Für jedes Teilprojekt und jede Verfeinerungsebene werden die folgenden Schritte durchlaufen:
2.6. TRANSFORMATIONELLES MODELL
21
1. Anforderungsdefinition
2. Bewertung von Alternativen und Risiken
3. Entwurf, Implementation und Test des Teilprodukts gemäß eines geeigneten Prozeßmodells
4. Auswertung und Vorbereitung des nächsten Umlaufs
Bewertung:
+ Risikominimierung in allen Phasen und bei allen Teilprojekten
+ Jede Komponente kann nach dem geeignetesten Prozessmodell entwickelt werden
+ Regelmäßige Bewertung und Korrektur der Ergebnisse
+ Unterstützt Wiederverwendung
- Hoher Managementaufwand
- Bei vielen Iterationen geht die Phasentrennung verloren
2.6 Transformationelles Modell
Durch semantikerhaltende Transformationen wird eine abstrakte Spezifikation in ein ausführbares
Programm überführt. Entwurfsentscheidungen gehen als Optimierungen ein.
Bewertung:
+ Hoher Automatisierungsgrad
+ Ist die Spezifikation korrekt, so ist auch die Implementierung korrekt.
- Bisher nur für spezielle Anwendungsgebiete praktisch einsetzbar (z.B. Codegenerierung aus
Statecharts)
- Sehr anspruchsvoll und meist nur für kleine Projekte brauchbar.
2.7
Extremes Programmieren (XP)
Als Alternative zu den relativ schwerfälligen Prozessmodellen wurde insbesondere für kleine Projekte Ende der neunziger Jahre Extremes Programmieren“ vorgeschlagen [2, 16]. Der Ansatz ist
”
für kleine Projekte mit bis zu 10-15 Entwicklern geeignet und basiert auf den folgenden zwölf
Techniken (Praktiken in der XP-Terminologie):
1. Kleine Releases
• Hochgradig iterativer Entwicklungsprozess mit einem Release-Zyklus von 1-3 Monaten.
• Ein Release besteht aus mehreren Iterationen von 1-3 Wochen Dauer.
22
KAPITEL 2. PROZESSMODELLE
• Iterationen zerfallen wiederum in Arbeitspakete von 1-3 Tagen.
• Nach jeder Iteration kann der Kunde Abweichungen von seinen Vorstellungen feststellen und korrigieren.
2. Planungsspiel
• Kunden beschreiben gewünschte neue Funktionen für die nächste Iteration in Form einer User Story“ und versehen sie mit Prioritäten.
”
• Entwickler schätzen den Aufwand zur Realisierung für jede Story.
• Der Kunde entscheidet, welche Story in der nächsten Iteration implementiert werden.
• Gegebenenfalls ist eine Wiederholung des Planungsspiels notwendig bis ein Gleichgewicht erreicht wird und eine realistische Planung für die nächste Iteration entstanden
ist.
3. Tests
• Automatisierte Tests spielen eine zentrale Rolle von XP.
• Entwickler schreiben Tests für ihre Klassen (unit tests).
• Der Kunde entwickelt Testfälle für eine Story (functional test).
• Wichtig: Entwickler schreiben Tests bevor die Klassen implementiert werden.
4. Systemmetapher
• Eine Systemmetapher steht für die grundsätzliche Idee hinter der Architektur und ist
sowohl Kunden als auch Entwicklern bekannt.
• Sie ersetzt den Architekturentwurf.
5. Einfacher Entwurf
• Entwickler wählen immer die einfachste Lösung, die den aktuellen Anforderungen genügt (the simplest thing that could possibly work).
• Anforderungen können sich morgen ändern, wodurch zusätzlich eingebaute Flexibilität
nutzlos würde.
• Sollte später eine generellere Lösung notwendig sein, so wird der Entwurf refaktorisiert.
6. Refaktorisierung
• Refaktorisierung dient der Vereinfachung des Entwurfs unter Beibehaltung der Semantik (Eliminierung duplizierten Quellcodes).
• Verbesserung der Verständlichkeit und Änderbarkeit des Quelltextes.
• Durch die umfangreiche Testsammlung kann festgestellt werden, ob bei einer Refaktorisierung sich Fehler eingeschlichen haben.
• Der Quelltext selbst soll schließlich in hohem Maße selbsterklärend sein und anderweitige Dokumentation ersetzen.
7. Programmieren in Paaren
• Die Programmierung erfolgt immer in Paaren vor einem Rechner.
2.7. EXTREMES PROGRAMMIEREN (XP)
23
• Während einer der Entwickler programmiert prüft der Partner den Quelltext auf logische
Fehler und ob der neue Quelltext zur Systemmetapher paßt oder ob weitere Tests fehlen.
• Die Paarbindung ist dynamisch und fördert so den Austausch von Wissen über verschiedene Aspekte des Softwaresystems.
• Nach verschiedenen Studien führt das Programmierung in Paaren zu nur leicht gestiegenen Kosten und deutlich verständlicherem und fehlerfreierem Quelltext.
8. Gemeinsames Eigentum
• Der entstandene Quelltext gehört nicht einem bestimmten Entwickler sondern allen Entwicklern in einem Projekt.
9. Kontinuierliche Integration
• Neu entwickelter oder geänderter Quelltext wird regelmässig (mindestens einmal am
Tag) in die aktuelle Code-Basis integriert
• Dabei müssen alle vorhandenen Tests bestanden werden.
10. 40-Stunden-Woche
• Das Programmieren in Paaren stellt hohe Ansprüche an die Konzentration der Partner.
• Daher werden geregelte Arbeitszeiten von 40 Stunden (Richtwert) eingehalten.
11. Kundenvertreter im Team
• Da keine Spezifikation vorhanden ist, gibt es viele Rückfragen an den Kunden.
• Es sollte daher ein Kunde für die Entwickler ständig verfügbar sein.
12. Programmierrichtlinien
• Alle Entwickler halten sich an feste Programmierrichtlinien um einheitlichen Quelltext
zu erzeugen, der von allen anderen Entwicklern verstanden und leicht geändert werden
kann.
Bewertung:
+ Einfaches Prozessmodell für kleinere Softwareprojekte.
+ Kundenwünsche haben starken Einfluß auf den Entwicklungsprozess.
+ Steigert die Zufriedenheit der Entwickler.
- Fehlen einer expliziten Spezifikation erschwert es, nachträglich neue Entwickler in das Team
zu integrieren.
- Die Umsetzung von XP ist nur unzureichend dokumentiert. Das Konzept der Systemmetapher
ist vage.
24
KAPITEL 2. PROZESSMODELLE
Kapitel 3
Unified Modeling Language (UML)
Die Unified Modeling Language [3, 12, 13] ist eine Sprache zur Erstellung von Konstruktionsplänen für Softwaresysteme (software blueprint). UML kann benutzt werden um die Artefakte eines Softwaresystems zu visualisieren, spezifizieren, konstruieren und dokumentieren. UML ist eine
objekt-orientierte visuelle Modellierungssprache und nicht gebunden an eine konkrete Implementierungssprache.
UML stellt viele verschiedene Diagrammarten zur Verfügung. Die gebräuchlichsten werden in den
folgenden Abschnitten erklärt, wobei wiederum gelegentlich einige Details weggelassen werden.
UML-Diagramme sollten jeweils auf einen bestimmten Aspekt fokussieren und irrelevante Details
vermeiden, um die Lesbarkeit uns Aussagekraft zu erhöhen.
3.1
Notizen
Notizen sind textuelle Bemerkungen, die überall in UML-Diagrammen angebracht werden können.
Abbildung 3.1 zeigt die Darstellung einer einfachen Notiz.
Dies ist eine Notiz.
Abbildung 3.1: Eine einfache Notiz in UML
Notizen können grundsätzlich überall angebracht werden. Allerdings sollte man sie sparsam verwenden, um Diagramme nicht zu überladen.
3.2 Klassendiagramme
Klassendiagramme beschreiben die Struktur der Klassen in einem objekt-orientierten Modell und
die strukturellen Beziehungen zwischen Klassen. Eine Klasse ist die Definition der Attribute, Operationen und der Semantik für eine Menge von Objekten. Alle Objekte einer Klasse entsprechen
dieser Definition.
25
26
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
3.2.1
Klassen
Abbildung 3.2 zeigt die Darstellung einer Klasse (class) Vorlesung in der UML.
Attribute
Operationen
Vorlesung
Klassenname
titel: String
semester: String
dozent: Person
skript: URL
toHtml(): String
Abbildung 3.2: UML-Klasse für eine Vorlesung
• Die Klasse wird durch ein Rechteck dargestellt.
• Der Name der Klasse steht im oberen Teil des Rechtecks.
• Im mittleren Teil des Rechtecks stehen die Attribute der Klasse.
• Im unteren Teil des Rechtecks stehen die Operationen, die von der Klasse unterstützt werden.
• Die Angabe der Attribute und der Operationen ist jeweils optional.
• Klassennamen beginnen typischerweise mit einem Großbuchstaben (Vorlesung) und sind
oftmals Nomen. Der Klassenname kann auch einen Pfad enthalten, der ein Package identifiziert.
• Namen von Attributen und Operationen beginnen typischerweise mit einem Kleinbuchstaben
(titel, toHtml). Namen von Attributen sind in der Regel Nomen während Namen von
Operationen in der Regel Verben sind.
• Ein Attribut wird durch den Namen angegeben, wobei optional der Datentyp des Attributs
und ggf. auch ein Initialwert angegeben werden kann.
• Eine Operation wird durch den Namen angegeben, wobei optional der Datentype des Rückgabewerts definiert werden kann. Ebenso können optional die Parameter der Operation angegeben werden, wobei wiederum ein Parameter durch einen Namen identifiziert wird und
optional einen Datentyp und einen Standardwert (default) besitzt.
27
3.2. KLASSENDIAGRAMME
3.2.2
Sichtbarkeit
Für Attribute und Operationen einer Klasse können zusätzlich Sichtbarkeitsangaben gemacht werden (visibility).
private
protected
public
Vorlesung
-titel: String
-semester: String
-skript: URL
#dozent: Person
+getTitle(): String
+getSemester(): String
+getSkript(): URL
+getDozent(): Person
+toHtml(): String
Abbildung 3.3: Erweiterte UML-Klasse für eine Vorlesung
• Zugriffsrestriktionen werden durch Symbole vor dem Namen eines Attributs oder einer Operation angezeigt.
• Das Symbol + steht für public und bedeutet, daß das Attribut / die Operation für alle sichtbar
und benutzbar ist.
• Das Symbol # steht für protected und bedeutet, daß das Attribut / die Operation für die
Klasse und alle abgeleiteten Klassen sichtbar und benutzbar ist.
• Das Symbol − steht für private und bedeutet, daß das Attribut / die Operation nur für die
Klasse sichtbar und benutzbar ist.
3.2.3
Gültigkeitsbereiche
Attribute und Operationen können einerseits den einzelnen Objekten (instance scope) zugeordnet
sein oder aber auch einer Klasse selbst (class scope).
28
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
Vorlesung
-titel: String
-semester: String
-skript: URL
#dozent: Person
-anzahl: Integer
+getTitle(): String
+getSemester(): String
+getSkript(): URL
+getDozent(): Person
+toHtml(): String
+getAnzahl(): Integer
Klassenattribut
Klassenoperation
Abbildung 3.4: Erweiterte UML-Klasse mit verschiedenen Gültigkeitsbereichen
• Attribute, die nur einmal für alle Instanzen einer Klasse existieren, werden durch unterstreichen markiert.
• Operationen, die zur Klasse gehören und nicht zu den Objekten einer Klasse, werden durch
unterstreichen markiert.
3.2.4
Generalisierung / Spezialisierung
Generalisierung (generalization) und Spezialisierung (spezialization) sind Abstraktionsprinzipien
zur Strukturierung eines Modells. Eine Generalisierung ist eine Beziehung zwischen einem allgemeinerem und einem speziellen Element, wobei das speziellere weitere Eigenschaften hinzufügt
und sich kompatibel zum allgemeinen verhält. Die Vererbung ist ein Programmiersprachenkonzept
mit dem sich Generalisierungen / Spezialisierungen ausdrücken lassen.
29
3.2. KLASSENDIAGRAMME
Veranstaltung
Generalisierungen /
Spezialisierungen
Vorlesung
-titel: String
-semester: String
+getTitle(): String
+getSemester(): String
Uebung
#dozent: Person
-skript: URL
+getDozent(): Person
+getSkript(): URL
Seminar
Praktikum
#leiter: Person
-aufgaben: URL
Abbildung 3.5: Generalisierung der Vorlesung in eine Veranstaltung
• Generalisierungen / Spezialisierungen werden in UML durch Pfeile dargestellt, wobei die
Pfeilspitze zur allgemeineren Klasse zeigt. Die Pfeilspitze ist leer und nicht gefüllt.
3.2.5
Abstraktheit
Operationen oder auch ganze Klassen können abstrakt (abstract) sein. Eine abstrakte Operation ist
zwar innerhalb einer Klasse definiert, muß aber von abgeleiteten Klassen realisiert werden. Eine
abstrakte Klasse kann keine Instanzen (Objekte) haben sondern dient lediglich zur Organisation der
Generalisierungs- / Spezialisierungshierarchie.
Veranstaltung
-titel: String
-semester: String
+getTitle(): String
+getSemester(): String
Vorlesung
#dozent: Person
-skript: URL
+getDozent(): Person
+getSkript(): URL
Uebung
abstrakte Klasse
Seminar
Praktikum
#leiter: Person
-aufgaben: URL
Abbildung 3.6: Veranstaltung ist eine abstrakte Klasse
• Abstrakte Operationen oder Klassen werden durch die kursive Schreibweise der Namen angedeutet.
30
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
3.2.6
Aggregation / Komposition
Eine Aggregation (aggregation) ist ein Beziehung, deren beteiligten Klassen eine Ganzes-TeileHierarchie darstellen. Eine Komposition (composition) ist eine strengere Form der Aggregation,
bei der die Teile vom Ganzen existenzabhängig sind.
Veranstaltung
-titel: String
-semester: String
+getTitle(): String
+getSemester(): String
Vorlesung
#dozent: Person
-skript: URL
+getDozent(): Person
+getSkript(): URL
Uebung
Aggregation
Seminar
Lehrangebot
Praktikum
#leiter: Person
-aufgaben: URL
Abbildung 3.7: Lehrangebot setzt sich aus Veranstaltungen zusammen
• Aggregationen werden in UML durch Linien dargestellt, die an einem Ende eine Raute besitzen. Die Raute steht auf der Seite des Aggregats (also des Ganzen) und ist nicht gefüllt.
• Komposition werden in UML wie Aggregationen dargestellt. Zur Unterscheidung wird bei
einer Komposition die Raute gefüllt.
3.2.7
Assoziationen
Assoziationen (associations) beschreiben allgemein Beziehungen zwischen Klassen. Generalisierung / Spezialisierung und Aggregation / Komposition sind spezielle Assoziationen.
31
3.2. KLASSENDIAGRAMME
Veranstaltung
Assoziationen
-titel: String
-semester: String
+getTitle(): String
+getSemester(): String
chef
1
Person
1
+name: String
mitarbeiter
0..*
Rollen
haelt
Vorlesung
arbeitet fuer
Multiplizitaet
0..* #dozent: Person
-skript: URL
+getDozent(): Person
+getSkript(): URL
Abbildung 3.8: Mitarbeiter halten Vorlesungen und arbeiten für andere Mitarbeiter
• Eine Assoziation wird durch eine Linie zwischen den beteiligten Klassen dargestellt.
• Der Name einer Assoziation beschreibt ihre Bedeutung (meist ein Verb).
• An den Enden der Linie kann die Multiplizität angegeben werden. Die Multiplizität gibt an,
mit wievielen Objekten der gegenüberliegenden Klasse ein Objekt assoziiert sein kann. Die
Multiplizität kann als Bandbreite, d.h. durch Minimum und Maximum angegeben werden.
Das Symbol ∗ bedeutet beliebig viel.
• Eine Assoziation die eine Klasse mit sich selbst verbindet heißt rekursiv.
• An den Enden der Linien können Rollen angegeben werden, was insbesondere bei rekursiven
Assoziationen sinnvoll ist.
Assoziationen können manchmal selbst Attribute besitzen. In solchen Fällen spricht man von attributierten Assoziationen. Sie werden in UML durch Assoziationsklassen dargestellt.
32
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
Veranstaltung
-titel: String
-semester: String
+getTitle(): String
+getSemester(): String
Person
1
+name: String
haelt
Vorlesung
0..* #dozent: Person
-skript: URL
+getDozent(): Person
+getSkript(): URL
Uhrzeit
+zeit: String
Assoziationsklasse
Abbildung 3.9: Dozenten halten Vorlesungen zu bestimmten Uhrzeiten
• Assoziationsklassen werden durch gestrichelte Linien mit der Assoziation verbunden.
Gelegentlich sind an einer Assoziation mehr als zwei Klassen beteiligt, sogenannte mehrgliedrige
Assoziationen.
Raum
+nummer: String
Veranstaltung
-titel: String
-semester: String
+getTitle(): String
+getSemester(): String
Person
+name: String
Vorlesung
mehrgliedrige Assoziation
#dozent: Person
-skript: URL
+getDozent(): Person
+getSkript(): URL
Abbildung 3.10: Dozenten halten Vorlesungen in einem bestimmten Raum
• Mehrgliedrige Assoziationen werden durch Linien dargestellt, die durch eine zentrale Route
miteinander verbunden sind.
33
3.2. KLASSENDIAGRAMME
3.2.8
Schnittstellen und Realisierungen
Schnittstellen (interfaces) beschreiben einen ausgewählten Teil des extern sichtbaren Verhaltens von
Modellelementen. Die Schnittstellenklassen sind abstrakt und definieren ausschließlich abstrakte
Operationen.
Schnittstelle
«interface»
Person
Realisierung
chef
1
Veranstaltung
+getName(): String
-titel: String
-semester: String
+getTitle(): String
+getSemester(): String
Mitarbeiter 1
-name: String
mitarbeiter
0..*
haelt
Vorlesung
0..* #dozent: Mitarbeiter
-skript: URL
+getDozent(): Mitarbeiter
+getSkript(): URL
arbeitet fuer
Abbildung 3.11: Mitarbeiter realisieren die Schnittstelle Person
• Eine Schnittstellenklasse wird durch den Stereotyp interface gekennzeichnet.
• Die Realisierung einer Schnittstellenklasse wird durch einen gestrichelten Pfeil (analog zur
Generalisierung / Spezialisierung) angezeigt.
Realisierung
Veranstaltung
Person
Schnittstelle
chef
1
-titel: String
-semester: String
+getTitle(): String
+getSemester(): String
Mitarbeiter 1
-name: String
mitarbeiter
0..*
haelt
Vorlesung
arbeitet fuer
0..* #dozent: Mitarbeiter
-skript: URL
+getDozent(): Mitarbeiter
+getSkript(): URL
Abbildung 3.12: Mitarbeiter realisieren die Schnittstelle Person
• Schnittstellen können alternativ auch kompakter durch einen einfachen Kreis dargestellt werden.
34
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
• Eine Linie, die einen solchen Kreis mit einem Modellelement verbindet, signalisiert, daß das
Modell die Schnittstelle realisiert.
3.2.9
Abhängigkeiten
Eine Abhängigkeit (dependency) ist eine Beziehung zwischen zwei Modellelementen, die anzeigt,
daß eine Änderung in dem einen unabhängigen Element eine Änderung in dem anderen abhängigen
Element notwendig macht. Abhängigkeiten beziehen sich auf die Modellelemente selbst und nicht
auf eventuelle Instanzen.
• Abhängigkeiten werden durch eine gestrichelten Pfeil dargestellt, wobei der Pfeil vom abhängigen Element auf das unabhängige Element verweist.
• Abhängigkeiten entstehen oftmals durch Klassen, die eine bestimmte Schnittstelle einer anderen Klasse benutzen. Eine Änderung der Schnittstelle kann Änderungen an den benutztenden
Klassen erfordern.
3.2.10
Objekte
Objekte sind konkrete Ausprägungen (Instanzen) von Klassen. Sie stellen einen Schnappschuß eines Systems zu einem bestimmten Zeitpunkt dar.
Die Abbildung 3.13 zeigt eine konkrete Ausprägung der beiden Klassen Mitarbeiter und
Vorlesung und der Assoziation haelt.
c01: Vorlesung
titel = "Informatik C"
semester = "WS 2001/2002"
skript = "http://www.vorlesungen.uos.de/informatik/c01/"
js: Mitarbeiter
name = "Juergen Schoenwaelder"
haelt
haelt
haelt
sec01: Vorlesung
titel = "Sicherheit in vernetzten Systemen"
semester = "WS 2001/2002"
skript = "http://www.vorlesungen.uos.de/informatik/sec01/"
bs01: Vorlesung
titel = "Betriebssysteme"
semester = "WS 2001/2002"
skript = "http://www.vorlesungen.uos.de/informatik/bs01/"
Abbildung 3.13: Konkrete Ausprägung der Klassen Vorlesung und Mitarbeiter
• Objekte werden durch Rechtecke dargestellt.
• Der Name eines Objekts wird unterstrichen im oberen Teil des Rechtecks angegeben (zur
Unterscheidung von Klassen).
35
3.2. KLASSENDIAGRAMME
• Die Angabe der Klasse im Namensteil ist optional.
• Objektnamen beginnen typischerweise mit einem Kleinbuchstaben.
• Belegungen der Attribute können optional im unteren Teil des Rechtecks in der Form Attributename
= Wert dargestellt werden.
• Beziehungen zwischen Objekten werden durch einfach Linien dargestellt.
Das Diagramm von Abbildung 3.13 kann entsprechend auch viel kompakter dargestellt werden,
wenn die Details nicht wesentlich sind (Abb. 3.14).
c01: Vorlesung
haelt
js: Mitarbeiter
haelt
haelt
sec01: Vorlesung
bs01: Vorlesung
Abbildung 3.14: Kompakte Darstellung eines Objektdiagramms
Es ist auch möglich, sogenannte Multiobjekte (Abb. 3.15) darzustellen, die für eine Menge von
Objekten stehen.
js: Mitarbeiter
haelt
mehrereVorlesungen
Abbildung 3.15: Kompakte Darstellung eines Objektdiagramms mit Multiobjekten
3.2.11
Pakete
Pakete (packages) sind Ansammlungen von Modellelementen belieben Typs mit denen das Gesamtmodell in kleinere überschaubare Einheiten gegliedert wird. Ein Paket definiert einen Namensraum.
Alle im Namensraum enthaltenen Elemente müssen eindeutig sein.
Ein Paket Vorlesungsverwaltung für die bisher eingeführten Klassen ist in Abbildung 3.16
dargestellt.
• Ein Paket wird in Form eines Aktenregisters dargestellt. Innerhalb dieses Symbols steht der
Name des Pakets. Werden innerhalb des Symbols Modellelemente angezeigt, so steht der
Name auf der Registerlasche.
• Jedes Element gehört zu genau einem Paket.
• Pakete können andere Pakete enthalten (Hierarchie).
36
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
Vorlesungsverwaltung
Vorlesung
Uebung
Mitarbeiter
Seminar
Praktikum
Abbildung 3.16: Paketdiagram
• Das alleroberste Paket ist implizit vorhanden und enthält das gesamte Modell.
• Der Name eines Pakets beginnt typischerweise mit einem Großbuchstaben.
• Da Pakete andere Pakete enthalten können, wird ein Modellelement eindeutig durch den Pfad
der Paketnamen und den Namen des Modellelements identifiziert.
• Die Sichtbarkeit der in einem Paket enthaltenen Elemente kann wiederum durch Sichtbarkeitssymbole angegeben werden.
Abhaengigkeit
GUI
Generalisierung/
Spezialisierung
Browser
Mac GUI
Windows GUI
Unix GUI
Abbildung 3.17: Paketdiagram mit Abhängigkeiten und Generalisierung/Spezialisierung
• Zwischen Paketen können Abhängigkeiten existieren, die oftmals durch das Importieren von
Paketelementen entstehen.
• Pakete können auch Generalisierungen/Spezialisierungen von anderen Paketen sein.
3.3 Sequenzdiagramme
Ein Sequenzdiagramm zeigt eine Reihe von Nachrichten, die eine ausgewählte Menge von Objekten
in einer zeitlich begrenzten Situation austauschen. Der zeitliche Verlauf der Nachrichten steht beim
Sequenzdiagramm im Vordergrund.
37
3.3. SEQUENZDIAGRAMME
s: Caller
: Switch
r: Caller
liftReceiver
setDialTone()
* dialDigit(d)
«create»
c: Conversation
ring()
liftReceiver
connect(r,s)
connect(r)
connect(s)
hangUpReceiver
disconnect(r)
disconnect(s)
disconnect(r,s)
«destroy»
Abbildung 3.18: Sequenzdiagramm für eine Telefonverbindung
• Die Zeitachse verläuft von oben nach unten.
• Objekte werden wie in Objektdiagrammen durch Rechtecke dargestellt.
• Ausgehend vom Objekt zeigt eine gestrichelte Lebenslinie die Dauer der Existenz des Objekts
an.
• Das Ende der Existenz eines Objekts wird durch ein Kreuz am Ende der Lebenslinie hervorgehoben.
• Breite senkrechte Balken signalisieren welche Objekte gerade aktiv sind und deuten so den
Kontrollfluß an.
• Zwischen Objekten (symbolisch zwischen ihren Balken bzw. Lebenslinien) werden Nachrichten ausgetauscht.
• Zum Erzeugen von Objekten und Löschen von Objekten werden Nachrichten mit dem Stereotypen create bzw. destroy verwendet.
• Nachrichten, die wiederholt auftreten, können durch einen Multiplikator gekennzeichnet werden.
Abbildung 3.19 zeigt die verschiedenen Nachrichtentypen wie sie typischerweise in Sequenzdiagrammen vorkommen.
38
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
anObject
signal()
«create»
anotherObject
call()
return
«destroy»
recursive()
Abbildung 3.19: Nachrichtentypen in Sequenzdiagrammen
• Asynchrone Nachrichten, die keine Antwort erzwingen, werden durch halbe offene Pfeile
dargestellt.
• Eine Nachricht, die einem Operationsaufruf entspricht, wird durch einen gefüllten Pfeil dargestellt.
• Der Abschluß einer Operation kann optional durch einen Pfeil mit einer gestrichelten Linie
dargestellt werden. Fehlt dieser Pfeil, so ergibt sich das zeitliche Verhalten aus der Länge des
Balken des aufgerufenen Objekts.
• Rekursive Aufrufe desselben Objekts deutet man durch eine Überlagerung von Aktivitätsbalken an.
3.4 Kollaborationsdiagramme
Ein Kollaborationsdiagramm zeig eine Menge von Interaktionen zwischen ausgewählten Objekten
in einer bestimmten Situation. Dabei werden die Beziehungen zwischen den Objekten und ihre
Topographie betont.
Kollaborationsdiagramme beschreiben dieselben Sachverhalte wie Sequenzdiagramme, allerdings
aus einer anderen Perspektive.
39
3.4. KOLLABORATIONSDIAGRAMME
s: Caller
2.1: setDialTone()
1: liftReceiver
2: *dialDigit(d)
2.2.3: connect()
2.2.7: disconnect()
: Switch
2.2: <<create>>
2.3: <<destroy>>
c: Conversation
2.2.4: connect()
2.2.6: disconnect()
2.2.1.1: liftReceiver
2.2.2.1: hangUpReceiver
2.2.1: ring()
2.2.2: connect()
2.2.5: disconnect()
r: Caller
Abbildung 3.20: Kollaborationsdiagramm für eine Telefonverbindung
• Der zeitliche Zusammenhang der Nachrichten wird durch die hierarchische Numerierung der
Nachrichten ausgedrückt.
• Neben dem Namen der Nachricht können auch Parameter angegeben werden.
• Sequenzdiagramme können in Kollaborationsdiagramme transformiert werden und Kollaborationsdiagramme können in Sequenzdiagramme transformiert werden.
• Der Fokus bei den Diagrammtypen und damit die Anzahl der Details ist jedoch in der Regel
deutlich unterschiedlich.
40
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
3.5 Zustandsdiagramme
powerOff
powerOn
idle
tooCold
tooHot
atTemp
atTemp
cooling
tooHot
heating
tooCold
Abbildung 3.21: Zustandsdiagramm einer Klimaregelungsanlage
41
3.6. AKTIVITÄTSDIAGRAMME
3.6 Aktivitätsdiagramme
Durst auf Kaffee
Filter einsetzen
Wasser einfuellen
Kaffee einfuellen
Tassen suchen
Kaffee kochen
Kaffee eingiessen
Kaffee trinken
Abbildung 3.22: Kaffeepause als Aktivitätsdiagramm
42
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
Chef
Mitarbeiter1
Mitarbeiter2
Durst auf Kaffee
Filter einsetzen
Wasser einfuellen
Kaffee einfuellen
Tassen suchen
Kaffee kochen
Kaffee eingiessen
Kaffee trinken
Abbildung 3.23: Kaffeepause als Aktivitätsdiagramm mit Swimlanes
3.7 Anwendungsfalldiagramme
Ein Anwendungsfall (use case) beschreibt eine Menge von Aktivitäten eines Systems aus der Sicht
seiner Akteure (actor).
43
3.7. ANWENDUNGSFALLDIAGRAMME
Zeitschrift
registrieren
Zeitschrift
studieren
Mitarbeiter
Umlaufzettel
erstellen
Sekretariat
Bibliothek
Zeitschrift
archivieren
Abbildung 3.24: Anwendungsfall Zeitschriftenumlauf
Kreditkartensystem
KreditkartenTransaktion
Erstellung
der Rechnung
Kunde
Pruefen der
Transaktionen
Verwaltung des
Kundenkontos
PrivatKunde
Haendler
Bank
FirmenKunde
Abbildung 3.25: Anwendungsfall Kreditkartensystem
• Ein Anwendungsfall wird durch eine Ellipse dargestellt, die den Namen des Anwendungsfalls
trägt.
• Zu jedem Anwendungsfall gehört ein Text, der den Anwendungsfall genauer beschreibt.
• Akteure werden durch kleine Strichmännchen dargestellt.
• Ein Anwendungsfall wird stets durch einen Akteur initiiert.
44
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
• In einem Anwendungsfalldiagramm können wiederum die bekannten Assoziationen dargestellt werden.
• Systemgrenzen werden durch ein Rechteck dargestellt.
3.8 Komponentendiagramme
Eine Komponente (component) stellt ein physisches Stück Programmcode dar (Quelltext, Binärcode, Bibliotheken, ausführbare Programme). Komponentendiagramme (component diagrams) zeigen
die Beziehungen zwischen den Komponenten.
«document»
«file»
nvi manual
/etc/vi.exrc
«library»
libc.so.6
«executable»
nvi
«library»
«library»
ld.so.2
ncurses.so.5
Abbildung 3.26: Komponentendiagramm für ein ausführbares Programm
45
3.9. VERTEILUNGSDIAGRAMME
«file»
«file»
gsnmp.h
glib.h
«file»
scli.h
«file»
«file»
«file»
basic.c
system.c
scli.c
Abbildung 3.27: Komponentendiagramm für Quelltextmodule
• Eine Komponente wird als Rechteck dargestellt, das am linke Rand zwei kleine Rechtecke
trägt.
• Innerhalb des Rechtecks wird der Name der Komponente angegeben.
• Komponenten beschreiben im Gegensatz zu Paketen die physische Sicht auf ein Softwaresystem.
• Es gibt vordefinierte Stereotypen für ausführbare Programme (executable), Bibliotheken
(library), Dateien (file), Tabellen (table) und Dokumente (document).
• Abhängigkeiten zwischen Komponenten werden wie üblich durch Pfeile mit gestrichelten
Linien angezeigt.
3.9 Verteilungsdiagramme
Ein Knoten (node) ist ein zur Laufzeit physisch vorhandenes Objekt, daß über Betriebsmittel (z.B.
Prozessorleistung, Speicher) verfügt.
Verteilungsdiagramme (deployment diagrams) visualisieren, welche Komponenten und Objekte auf
welchen Knoten (Prozessoren, Computer) installiert sind und welche Kommunikationsbeziehungen
bestehen.
46
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
external network
firewall
workstation
local network
server
workstation
«executable»
«executable»
apache
nvi
Abbildung 3.28: Verteilungsdiagramm
• Ein Knoten wird durch einen Quader dargestellt.
• Knoten besitzen einen Namen, die typischerweise kurze Nomen sind.
• Knoten, die miteinander kommunizieren, werden durch Assoziationslinien miteinander verbunden.
• Knoten können andere UML-Elemente enthalten.
3.10
Beispiel: Fahrstuhl
Betrachten Sie als Beispiel die Steuerungslogik eines Fahrstuhls:
• Das Gebäude hat m Etagen.
• Im Fahrstuhl befinden sich m Knöpfe für die einzelnen Etagen.
• Die unterste Etage hat einen Knopf up.
• Die oberste Etage hat einen Knopf down.
• Alle anderen Etagen haben jeweils einen Knopf up und einen Knopf down.
• Knöpfe werden von der Steuerungslogik beleuchtet nachdem sie gedrückt wurden und solange der Fahrtstuhl die entsprechende Etage noch nicht erreicht hat.
• Der Fahrstuhl bewegt sich nur sofern dies notwendig ist.
• Die Steuerungslogik muß nach dem Erreichen einer Ziel-Etage die Tür öffnen und wieder
schließen.
47
3.10. BEISPIEL: FAHRSTUHL
• Die Fahrstuhltür bleibt geschlossen solange der Fahrstuhl nicht benutzt wird.
Zuerst sollte man sich über die Aktivitäten klar werden. Dazu erstellt man ein geeignetes Anwendungsfalldiagramm (Abb. 3.29).
Fahrstuhl
feststellen ob ein
Knopf betaetigt wurde
Knopfbeleuchtung
kontrollieren
Benutzer
Fahrstuhl bewegen
Steuerungslogik
Tuer oeffnen und
schliessen
Abbildung 3.29: Anwendungsfalldiagramm für eine Fahrstuhlsteuerung
Es empfiehlt sich als nächstes ein grobes Klassendiagramm zu entwerfen (Abb. 3.30). Offenbar
benötigt man eine Klasse für den Fahrstuhl (Elevator), eine Klasse für die Steuerungslogik
(ElevatorController) und Klassen für die verschiedenen Knöpfe. Da man zwei verschiedene Arten von Knöpfen hat, empfiehlt sich die Definition einer abstrakten Oberklasse (Button)
von der die Klassen für Knöpfe im Fahrstuhl (ElevatorButton) und Knöpfe auf den Fluren
(FloorButton) abgeleitet werden.
Die Klasse Door repräsentiert die Tür eines Fahrstuhls. Die Komposition in Abb. 3.30 mit der Multiplizität 1:1 drückt aus, daß eine Fahrstuhltür immer zu einem Fahrstuhl gehört und ein Fahrstuhl
genau eine Tür besitzt. Die Assoziationen controls beschreibt, welche Elemente die Steuerungslogik kontrolliert. Die Assoziation updates drückt aus, daß Knöpfe jeweils einer Steuerungslogik
zugeordnet sind.
Die Abbildung 3.31 zeigt ein Sequenzdiagramm für die Fahrstuhlsteuerung. Eine Person (bisher
nicht weiter modelliert) drückt auf einen Knopf im Fahrstuhl. Dieser Vorgang findet asynchron
statt. Der Knopf meldet seinen Zustandswechsel an die Steuerungslogik. Die Steuerungslogik veranlaßt die Beleuchtung des Knopfes und instruiert den Fahrstuhl sich in die entsprechende Etage zu
bewegen. Ist die Etage erreicht, so wird die Beleuchtung des Knopfes beendet und die Tür geöffnet.
Nach einer kurzen Wartezeit wird die Tür wieder geschlossen.
48
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
Elevator
1
+currentFloor: Integer
+direction: Boolean
+move(floor:Integer)
1
controls
controls
Door
+closed: Boolean = true
+open()
+close()
ElevatorController
+update()
updates
Button
+illuminated: Boolean = false
+illuminate()
+cancel()
ElevatorButton
+dstFloorNumber: Integer
FloorButton
+direction: Boolean
+floorNumber: Integer
Abbildung 3.30: Klassendiagramm für eine Fahrstuhlsteuerung
aPassenger
anElevatorButton
aController
anElevator
aDoor
pressed
update
illuminate
move
cancel
open
close
Abbildung 3.31: Sequenzdiagramm für eine Fahrstuhlsteuerung
Analog zum Sequenzdiagramm läßt sich ein Kollaborationsdiagramm angeben (Abb. 3.32).
49
3.10. BEISPIEL: FAHRSTUHL
controls
aDoor
aPassenger
1: pressed()
1.1.4: open()
1.1.5: close()
1.1.1: illuminate()
1.1.3: cancel()
updates
aController
1.1: update()
1.1.2: move()
anElevatorButton
controls
anElevator
Abbildung 3.32: Kollaborationsdiagramm für eine Fahrstuhlsteuerung
50
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
Kapitel 4
Entwurfsmuster
Beim Entwurf von (objektorientierten) Softwaresystemen gibt es häufig wiederkehrende Teilprobleme zu lösen. Erfahrene Softwaredesigner greifen zur Lösung dieser Probleme gern auf bereits
erfolgreich angewendete Lösungen zurück, die ggf. weiter verfeinert werden und generalisiert werden.
Entwurfsmuster (design pattern) [8] versuchen bekannte gute Lösungen für spezifische Entwurfsprobleme zu dokumentieren und auf diese Weise die Expertise von Entwurfsexperten zugänglich
zu machen und so die Wiederverwendung von guten Entwurfsmustern zu erhöhen. Entwurfsmuster helfen Entwurfsalternativen zu bewerten und Lösungen zu vermeiden, die sich negativ auf die
Wiederverwendbarkeit und Erweiterbarkeit eines Softwaresystems auswirken.
Ein Entwurfsmuster besitzt vier essentielle Elemente:
1. Name des Entwurfsmusters
2. Problembeschreibung
3. Beschreibung der Lösung
4. Konsequenzen aus der Anwendung des Entwurfsmusters
Zur Dokumentation von Entwurfsmustern wird in [8] eine Schablone vorgeschlagen, die aus folgenden Elementen besteht:
• Name des Entwurfsmusters:
Der Name und die Klassifikation des Entwurfsmusters. Ein guter Name ist wichtig, da er das
Vokabular der Entwickler prägt.
• Aufgabe:
Kurze Beschreibung, was ein Entwurfsmuster leistet und welches spezielle Entwurfsaufgabe
es löst.
• Synonyme:
Alternative Bezeichnungen für das Entwurfsmuster.
• Motivation:
Beschreibung eines Szenarios, das das Entwurfsproblem verdeutlicht und erklärt, wie das
Entwurfsmuster das Problem löst.
51
52
KAPITEL 4. ENTWURFSMUSTER
• Anwendbarkeit:
Beschreibung der Situationen in denen das Entwurfsmuster anwendbar ist, welche Vorteile
die Anwendung bietet und wie man solche Situationen erkennt.
• Struktur:
Eine graphische Darstellung der Struktur in Form eines Klassendiagramms.
• Teilnehmer:
Beschreibung der Elemente, die an dem Entwurfsmuster beteiligt sind.
• Zusammenarbeit:
Erklärung wie die Elemente eines Entwurfsmusters zusammenarbeiten.
• Konsequenzen:
Beschreibung die Konsequenzen aus der Anwendung des Entwurfsmustern.
• Implementation:
Darstellung von möglichen Alternativen bzw. von möglichen Problemen bei der Implementierung.
• Beispiel:
Beschreibung einer Beispielimplementation.
• Anwendungen:
Kurzbeschreibung von realen Systemen in denen das Entwurfsmustern eingesetzt wird.
• Verwandte Entwurfsmuster: Verweise auf verwandte Entwurfsmuster.
Entwurfsmuster lassen sich in drei Kategorieren einteilen:
1. Entwurfsmuster, die sich mit dem Erzeugen von Objekten befassen (creational design pattern).
2. Entwurfsmuster, die sich mit den strukturellen Aspekten der Organisation von Klassen und
Objekten befassen (structural design pattern).
3. Entwurfsmuster, die sich mit den Interaktionen zwischen Objekten und der Verteilung der
Funktionalität zwischen Objekten befassen (behavioral design pattern).
Gute Entwurfsmuster dienen neben dem Hauptziel der Wiederverwendung guter Lösungen dazu,
die Qualität eines Softwareentwurfs zu verbessern. Ziel ist es, Softwareentwürfe flexibel und erweiterbar zu halten, da sich die Anforderungen im Lebenslauf der Software ändern werden (design
for change). Um dieses Ziel zu erreichen benötigt man geeignete Abstraktionen und Mechanismen
und die Abhängigkeiten zwischen Objekten und Softwarekomponenten zu kontrollieren. Wichtige
Maßnahmen sind nach [8]:
• Programmiere zu einem Interface und nicht zu einer Implementation.
• Bevorzuge die Komposition von Objekten vor Vererbung.
Zu den Entwurfsmustern existieren einige verwandte Konzepte:
53
• Ein Gegenmuster (Anti-Pattern) beschreibt, wie man von einem Entwurfsproblem zu einer schlechten Lösung kommt. Ein gutes Anti-Pattern erklärt, warum die schlechte Lösung
zunächst attraktiv ist, warum sie letztlich nicht gut ist und welche Entwurfsmuster stattdessen
anwendbar sind.
• Idiome sind sehr einfache wiederkehrende Muster, die typischerweise spezifisch für eine konkrete Programmiersprache sind.
54
KAPITEL 4. ENTWURFSMUSTER
4.1 Factory Method
Definiert eine Schnittstelle zur Erzeugung eines Objekts wobei abgeleitete Klassen entscheiden
können, von welcher Klasse tatsächlich Objekte instantiiert werden.
Synonyme
Cirtual Constructor
Motivation
xxx
Anwendbarkeit
• Wenn eine Klasse nicht vorhersehen kann, von welchen Klassen sie Objekte erzeugen muß,
• Wenn eine Klasse den abgeleiten Klassen die Freiheit geben will, die Objekte zu spezifizieren
die es erzeugt.
• Wenn Klassen verantwortung an eine oder mehrere Hilfsklassen delegieren und das Wissen
über die verschiedenen Hilfsklassen lokalisiert werden soll.
Struktur
Product
«interface»
Creator
+FactoryMethod(): Product
ConcreteProduct
ConcreteCreator
+FactoryMethod(): ConcreteProduct
Abbildung 4.1: Klassendiagramm für das Factory Method Entwurfsmuster
55
4.2. ITERATOR
4.2 Iterator
Aufgabe
Das Entwurfsmuster Iterator erlaubt es die Elemente eines aggregierenden Objekts sequentiell zu
durchlaufen, ohne die zugrundelegende Implementierung zu freizulegen.
Synonyme
Cursor
Motivation
Oftmals ist es notwendig, die Elemente eines zusammengesetzten Objekts (Aggregat) durchlaufen
zu können ohne die interne Struktur des Aggregats offenlegen zu wollen. Es kann auch sinnvoll
sein, nachträglich verschiedenen Arten des Durchlaufs zu unterstützen, ohne die Implementierung
des Aggregats selbst zu verändern.
Anwendbarkeit
• Wenn die Elemente eines Aggregats durchlaufen werden müssen ohne das die innere Struktur
des Aggregats freigelegt wird.
• Wenn ein Aggregat auf verschiedene Arten durchlaufen werden kann.
• Wenn eine einheitliche Schnittstelle zum durchlaufen verschiedener Aggregate erforderlich
ist (polymorphic iteration).
Struktur
«interface»
Iterator
«interface»
Aggregate
+first()
+next()
+isDone()
+currentItem()
+createIterator()
ConcreteAggregate
1
iteratesOver *
ConcreteIterator
+createIterator()
return new ConcreteIterator(self);
Abbildung 4.2: Klassendiagramm für das Iterator Entwurfsmuster
56
KAPITEL 4. ENTWURFSMUSTER
Teilnehmer
• Ein Iterator definiert eine Schnittstellt zum Durchlaufen und Zugriff auf Elemente.
• Ein ConcreteIterator implementiert die Iterator Schnittstelle und kennt die aktuelle Position
im Aggregate.
• Ein Aggregate definiert eine Schnittstelle zum Erzeugen eines Iterators.
• Ein ConcreteAggregate implementiert die Schnittstelle zum Erzeugen eines passenden Interators.
Zusammenarbeit
• Ein ConcreteIterator kennt das aktuelle Objekt im Aggregate und kann das nächste Objekt
ermitteln.
Konsequenzen
• Es lassen sich verschiedenen Arten des Durchlaufs realisieren indem man weitere Iteratoren
implementiert.
• Iteratoren vereinfachen die Schnittstelle des Aggregats.
• Mehrere Durchläufe können aktiv sein, da jeder Iterator einen eigenen Zustand besitzt.
Implementation
• Interne Iteratoren (internal iterators) führen automatisch eine Operation auf allen Elementen eines Aggregats aus. Externe Iteratoren (external iterators) verlangen, daß der Benutzer
des Iterators das nächste Element explizit verlangt. Externe Iteratoren sind flexibler, da man
beispielsweise zwei Aggregate damit vergleichen kann. Interne Iteratoren sind jedoch manchmal nützlich, da sie die Iterationslogik komplett implementieren und dadurch auch effizienter
sind. Interne Iteratoren sind meist in Programmiersprachen realisiert, die closures oder continuations unterstützen.
• Man kann den Iterationsalgorithmus entweder im Iterator realisieren oder im Aggregat. Im
letzteren Fall spricht man auch von einem Cursor. Die Implementation des Iterationsalgorithmus im Iterator hat den Vorteil, daß verschiedene Algorithmen leicht realisert werden können
und den Nachteil, daß der Iterator Kenntnisse über die Struktur des Aggregats besitzt. Bei einem Cursor kann das notwendige Wissen des Iterators über die Struktur minimiert werden.
• Robuste Iteratoren stellen sicher, daß Änderungen am Aggregat nicht zu unerwartetem Verhalten führen können.
• Es kann sinnvoll sein, zusätzlich Operationen für Iteratoren zu definieren, beispielsweise eine
Previous Operation für geordnete Aggregate.
• Ein NullIterator ist ein degenerierter Iterator, der immer fertig ist, d.h. die Operation
isDone ist immer wahr.
57
4.2. ITERATOR
Beispiel
Object
«interface»
Iterator
«interface»
Enumeration
+hasNext(): boolean
+next(): Object
+remove(): void
+hasMoreElements(): boolean
+nextElement(): Object
«interface»
ListIterator
StringTokenizer
+StringTokenizer(str:String)
+StringTokenizer(str:String,delim:String)
+StringTokenizer(str:String,delim:String,returnDelims:boolean)
+countTokens(): int
+hasMoreElements(): boolean
+hasMoreTokens(): boolean
+nextElement(): Object
+nextToken(): String
+nextToken(delim:String): String
+add(o:Object): void
+hasNext(): boolean
+hasPrevious(): boolean
+next(): Object
+nextIndex(): int
+previous(): Object
+previousIndex(): int
+remove(): void
+set(o:Object): void
Abbildung 4.3: Realisierung des Iterator Entwurfsmusters in Java
Anwendungen
Das Iterator Entwurfsmuster ist in vielen objektorientierten Systemen implementiert. Beispiele sind
die Smalltalk Collection Klassen oder die Enumerator in Java.
Verwandte Entwurfsmuster
Composite, Factory Method
58
KAPITEL 4. ENTWURFSMUSTER
4.3 Composite
Aufgabe
Darstellung einer ist-Teil-von Hierarchie, bei der einzelne Objekte genauso wie Kompositionen von
Objekten behandelt werden.
Motivation
Graphische Editoren stellen eine Zahl primitiver Graphikobjekte bereit. Benutzer können diese Objekte in der Regel gruppieren und dadurch zusammengesetzte Objekte erzeugen. Eine mögliche
Implementierungstechnik ist die Definition von Klassen für die primitiven Objekte und Klassen für
zusammengesetzte Objekte.
Der Nachteil dieses Ansatzes ist, daß im Programm ständig Unterscheidungen zwischen primitiven
und zusammengesetzten Objekten notwendig sind, obwohl der Benutzer die Objekte gleich behandelt (z.B. draw, delete). Das Entwurfsmuster Composite löst dieses Problem indem eine abstrakte
Klasse eingeführt wird, von der Klassen für primitive als auch zusammengesetzte Objekte abgeleitet
werden.
Anwendbarkeit
• Wenn ist-Teil-von Beziehungen dargestellt werden müssen.
• Wenn für der Benutzer Kompositionen und elementare Objekte gleichförmig benutzen möchte,
ohne die Unterschiede kennen zu müssen.
Struktur
«interface»
Component
contains
+operation()
+add(Component)
+remove(Component)
+getChild(int)
Leaf
+operation()
Composite
+operation()
+add(Component)
+remove(Component)
+getChild(int)
for all g in children {
g.operation()
}
Abbildung 4.4: Klassendiagramm für das Composite Entwurfsmuster
4.3. COMPOSITE
59
Teilnehmer
• Ein Component definiert die Schnittstelle für die Objekte in der Komposition, definiert eine
Schnittstelle zum Zugriff auf Teile und optional eine Schnittstelle zum Zugriff auf das enthaltende Objekt. Die Component-Klasse implementiert das gemeinsame Standard-Verhalten.
• Ein Leaf repräsentiert ein Element in der Komposition, das keine anderen Elemente enthalten
kann und definiert das Verhalten von primitiven Objekten in der Komposition.
• EIn Composite repräsentiert Elemente in der Komposition, die andere Elemente enthalten
können. Die Composite-Klasse verwaltet die Referenzen auf enthaltene Elemente und implementiert Operationen zum Zugriff auf die enthaltenen Elemente.
Zusammenarbeit
Programme interagieren mit den Elementen in einer Komposition durch die Component-Schnittstelle.
Leaf-Objekte implementieren eine Operation in der Regel direkt. Composite-Objekte implementieren Funktionen in der Regel dadurch, daß die Operation an alle enthaltenen Objekte weitergeleitet
wird. Dabei können vor und nach dem Weiterleiten noch zusätzliche Berechnungen stattfinden.
Konsequenzen
• Definiert eine Hierarchie bestehend aus primitiven und zusammengesetzten Objekten. Primitive Objekte können in zusammengesetzten Objekten enthalten sein, die selbst wieder Teil
eines zusammengesetzten Objekts sein können.
• Programme benutzen die Objekte ohne die Unterschiede zwischen primitiven und zusammengesetzten Objekten kennen zu müssen.
• Neue primitive und zusammengesetzte Objekte können leicht eingeführt werden.
• Es gibt keine Möglichkeit, die in einem zusammengesetzten Objekt enthaltenen Objekte auf
eine Teilmenge zu beschränken.
Implementation
•
Beispiel
xxx
Anwendungen
Das Entwurfsmuster Composite wird in vielen graphischen Oberflächensystemen angewendet. Eine andere Anwendung findet man in Compilern, die den vom Parser generierten Baum durch ein
Composite darstellen.
60
Verwandte Entwurfsmuster
Iterator
KAPITEL 4. ENTWURFSMUSTER
61
4.4. OBSERVER
4.4 Observer
Aufgabe
Das Entwurfsmuster Observer definiert eine 1:n Abhängigkeit zwischen Objekten mit dem Ziel
Änderungen am Zustand eines Objekt automatisch allen abhängigen Objekten mitzuteilen damit
sich die abhängigen Objekte aktualisieren können.
Synonyme
Dependents, Publish-Subscribe
Motivation
Es ist oft erforderlich die Konsistenz zwischen kooperierenden Objekten zu bewahren. Es ist dabei erstrebenswert, die Klassen zu entkoppeln damit neue abhängige Objekte eingeführt werden
können ohne bestehende Objekte ändern zu müssen. Das Entwurfsmuster Observer führt das Konzept des Subject und des Observer ein. Ein Subject kann mehrere Observer besitzen, die bei einer
Zustandsänderung des Subjects benachrichtigt werden und dann ihren Zustand synchronisieren.
Anwendbarkeit
• Wenn eine Änderung eines Objekts Änderungen in anderen Objekten erfordert.
• Wenn ein Objekt in der Lage sein soll andere Objekte zu benachrichtigen ohne Annahmen
über die Art der anderen Objekte zu machen.
Struktur
«interface»
Subject
1
observes * «interface»
Observer
#observers
+attach(Observer)
+detach(Observer)
+notify()
#subject
+update()
for all o in observers {
o.update()
}
observerState =
subject.getState()
ConcreteSubject
+subjectState
+getState()
+setState()
ConcreteObserver
+observerState
+update()
Abbildung 4.5: Klassendiagramm für das Observer Entwurfsmuster
Teilnehmer
• Ein Subject kennt seine Observer und stellt ein Interface bereit, über das sich Observer anund abmelden können.
62
KAPITEL 4. ENTWURFSMUSTER
• Ein Observer stellt eine Schnittstelle bereit, über die er über Änderungen des Subjects benachrichtigt werden kann.
• Ein ConcreteSubject besitzt einen Zustand und benachrichtigt die Observer wenn der Zustand
sich ändert.
• Ein ConcreteObserver beinhaltet eine Kopie des Zustands des Subjects und besitzt die Fähigkeit, die Kopie mit dem Zustand des Subjects zu synchronisieren.
Zusammenarbeit
• Ein konkretes Subjekt benachrichtigt die Observer wenn eine Zustandsänderung sattfindet.
• Wenn ein konkreter Observer benachrichtigt wird, dann kann er vom Subjekt die neue Zustandsinformationen erfragen.
aConcreteSubject
aConcreteObserver
anotherConcreteObserver
setState()
notify
update()
getState()
update()
getState()
Abbildung 4.6: Sequenzdiagramm für das Observer Entwurfsmuster
Konsequenzen
Das Entwurfsmuster Observer erlaubt es die Subjects und die Observer unabhängig voneinander zu
variieren. Man kann Subjects wiederverwenden ohne die Observer wiederzuverwenden und anders
herum. Man kann neue Observer hinzufügen ohne die Subjects oder andere Observer ändern zu
müssen.
• Abstrakte Koppelung zwischen Subjects und Observern.
• Unterstützung von Multicast-Kommunikation.
• Unerwartete Aktualisierungen können auftreten wenn Abhängigkeiten zwischen Observern
bestehen.
4.4. OBSERVER
63
Implementation
• Subjects können im einfachsten Fall einfach Referenzen für die abhängigen Observer speichern. Dieser Ansatz ist nicht sehr Effizient wenn es sehr viele Subject und relativ wenige
Observer gibt. Alternativ kann man die Abbildung durch eine assoziative Hashtabelle realisieren, wodurch der Platzbedarf sinkt und dafür die Kosten für die Zugriffe steigen.
• Gelegentlich ist es sinnvoll, daß ein Observer von mehreren Subjects abhängig sein kann. In
solchen Fällen muß die Operation update das Subject als Parameter übergeben.
• Es gibt zwei Optionen für die Frage, wer die Benachrichtigungen auslöst:
1. Sie Operation, die den Zustand verändert, benachrichtigt nach der Zustandsänderung
alle Observer. Dieser Ansatz hat den Vorteil, daß die Benutzer eines Subjects sich nicht
um die Propagation von Zustandsänderungen kümmern müssen. Der Nachteil dieses
Ansatzes ist, das es unter Umständen zu sehr vielen kleinen Aktualisierungen kommen
kann.
2. Alternativ kann man die Benutzer eines Subjekt verantwortlich machen, zu geeigneten Zeitpunkten die Benachrichtigung einzuleiten. Damit können Benutzer des Subjects
mehrere Zustandsänderungen machen, die durch nur eine Aktualisierung propagiert
werden. Nachteil ist allerdings, daß nun die Benutzer eines Subjects für die Propagation
verantwortlich sind.
• Beim Löschen von Subjects kann es zu ungültigen Referenzen in den abhängigen Observern
kommen. Eine Lösungsmöglichkeit ist die Observer über das bevorstehende Löschen eines
Subjects zu benachrichtigen.
• Es ist sicherzustellen, daß ein Subject in einem konsistenten Zustand ist bevor er die Observer
benachrichtigt.
• Bei der Benachrichtigung können optional Informationen übergeben werden. Im einen Extremfall wird keine Information übergeben und die Observer müssen alle Informationen erfrage (pull model). Im anderen Extremfall werden alle Informationen übergeben, egal ob die
Observer sie benötigen (push model).
• Die Effizienz der Benachrichtigungen kann verbessert werden, indem Observer beim Subject
registrieren, an welchen Benachrichtigungen sie interessiert sind.
• Wenn die Abhängigkeiten zwischen Subjects und Observern sehr komplex sind, dann kann
man ein spezielles Objekt einführen das diese Abhängigkeiten verwaltet und eine spezielle
Strategie zur Benachrichtigung der Observer realisiert.
Beispiel
Abbildung 4.7 zeigt die Realisierung des Entwurfsmusters Observer in den Klassenbibliothek von
Java. Das Java Interface Observer definiert eine abstrakte Observer-Schnittstelle. Subjects werden
in Java durch die Klasse Observable definiert. Konkrete Subjects müssen von der Klasse Observable
abgeleitet werden.
64
KAPITEL 4. ENTWURFSMUSTER
Object
*
Observable
+Observable()
+addObserver(o:Observer): void
+deleteObserver(o:Observer): void
+countObservers(): int
+notifyObservers(): void
+notifyObservers(arg:Object): void
+hasChanged(): boolean
#setChanged(): void
#clearChanged(): void
observes
«interface»
*
Observer
+update(o:Observable,arg:Object): void
HexNumberObserver
ObservableNumber
-number: int
+setNumber(value:int): void
+getNumber(): int
+update(o:Observable,arg:Object): void
DecimalNumberObserver
+update(o:Observable,arg:Object): void
Abbildung 4.7: Realisierung des Observer Entwurfsmusters in Java
Anwendungen
Das Entwurfsmuster Observer ist Teil des Model/View/Controller Entwurfsmusters, das innerhalb
der Smalltalk-Umgebung für graphische Oberflächen verwendet wird. Das Model entspricht einem
Subject und es kann mehrere Views geben, die das Model darstellen können. Die Views entsprechen
also den Observern. Viele andere Klassenbibliotheken für graphische Oberflächen benutzen seit
dem ebenfalls das Entwurfsmuster Observer.
Verwandte Entwurfsmuster
Command, Iterator
65
4.5. STRATEGY
4.5 Strategy
Kapselt eine Menge von Algorithmen für ein konkretes Problem und macht sie austauschbar. Strategy erlaubt es die Algorithmen zu ändern oder zu erweitern, ohne die Programme zu ändern die sie
benutzen.
Synonyme
Policy
Motivation
xxx
Anwendbarkeit
• Wenn viele verwandte Klasse sich nur im Verhalten unterscheiden.
• Wenn man verschiedene Varianten eines Algorithmus benötigt, z.B. Algorithmen mit verschiedenem Zeit- und Speicherverhalten.
• Wenn Algorithmen Daten benötigen, die gekapselt werden sollen.
• Wenn eine Klasse verschiedene Verhaltensweisen implementiert und dies zu vielen bedingten
Anweisungen in der Implementierung der Operationen führt.
Struktur
«interface»
Strategy
Context
+AlgorithmInterface()
+ContextInterface()
ConcreteStrategyA
ConcreteStrategyA
ConcreteStrategyA
+AlgorithmInterface()
+AlgorithmInterface()
+AlgorithmInterface()
Abbildung 4.8: Klassendiagramm für das Strategy Entwurfsmuster
66
KAPITEL 4. ENTWURFSMUSTER
4.6 Chain of Responsibility
Das Chain of Responsibility Entwurfsmuster schafft eine losse Kopplung von Sender und Empfänger
einer Nachricht. Mehrere Objekte bekommen die Möglichkeit, einen Auftrag auszuführen. Potentielle Empfänger werden aufgereiht und bekommen nacheinander die Möglichkeit, einen Auftrag zu
erledigen.
Motivation
Anwendbarkeit
Struktur
successor
«interface»
Handler
+HandleRequest()
ConcreteHandler1
+HandleRequest()
ConcreteHandler2
+HandleRequest()
Abbildung 4.9: Klassendiagramm für das Chain of Responsibility Entwurfsmuster
4.7. MODEL VIEW CONTROLLER
67
4.7 Model View Controller
Das Model View Controller Entwurfsmuster erlaubt die Darstellung von Daten (Model) in mehreren
Views, wobei die Integrität der Darstellungen durch das Observer Entwurfsmuster gewährleistet
wird. Ein Controller über nimmt die Interaktion mit dem Benutzer, wobei potentiell Änderungen
am Model erzeugt werden. Contoller sind in der Regel austauschbar und werden durch das Strategy
Entwurfsmuster realisiert.
68
KAPITEL 4. ENTWURFSMUSTER
Kapitel 5
Qualitätssicherung
Mit Hilfe von Maßnahmen zur Qualitätssicherung soll eine bestimmte Softwarequalität erreicht
und garantiert werden können. Generell ist das Aufdecken von Fehlern ein destruktiver Prozeß und
sollte daher auch so organisiert werden, daß nicht die Entwickler selbst für die Qualitätssicherung
zuständig sind.
5.1 Typische Softwarefehler
• Berechnungsfehler: Eine Softwarekomponente berechnet eine falsche Funktion (z.B. durch
Verwendung falscher Variablen, Konstanten oder Operatoren).
• Schnittstellenfehler: Syntaktische oder semantische Inkonsistenz zwischen Aufruf und Definition einer Schnittstelle.
• Kontrollflußfehler: Ausführung eines falschen Programmpfads (z.B. Vertauschung von Anweisungen, falsche Kontrollbedingungen). Sehr beliebt sind off by one“ Fehler, bei der eine
”
Schleife einmal zu oft oder einmal zuwenig durchlaufen wird.
• Initialisierungsfehler: Falsche oder fehlende Initialisierungen.
• Datenflußfehler: Falsche Zugriffe auf Variablen und Datenstrukturen (z.B. falsche Indizierung von Arrays, Zuweisung an falsche Variablen, Zeigerfehler, Speicherfehler).
5.2 Programminspektionen
Eine Programminspektion ist ein formales Verfahren, in dem ein Programm durch andere Personen
als den Autor auf Probleme untersucht wird. Dabei wird folgende Vorgehensweise verfolgt:
• Eine Inspektion wird vom Autor ausgelöst, typischerweise bei einer Freigabe für eine weitere
Entwicklungsaktivität.
• Das Programm wird von mehreren Gutachtern beurteilt, wobei jeder Gutachter sich auf einen
oder mehrere Aspekte konzentriert und seine Erkenntnisse dokumentiert.
69
70
KAPITEL 5. QUALITÄTSSICHERUNG
• In einer gemeinsamen Sitzung aller Gutachter mit einem ausgebildeten Moderator werden
gefundene und neu entdeckte Fehler protokolliert. Lösungen werden nicht diskutiert.
• Ergebnis ist ein formalisiertes Inspektionsprotokoll mit Fehlerklassifizierung. Außerdem werden Statistiken über die Fehlerarten und -häufigkeiten erstellt, die zur Verbesserung des Entwicklungsprozesses dienen.
• Der Autor überarbeitet sein Produkt anhand der Erkenntnisse.
• Der Moderator gibt das Produkt frei oder weist es zurück.
Der Inspektionsaufwand muß natürlich im Projektplan eingeplant sein. Generell sind Inspektionsergebnisse sensitives Datenmaterial. Insbesondere dürfen sie nicht zur Beurteilung von Mitarbeitern
verwendet werden und Vorgesetzte und Zuhörer dürfen nicht an den Inspektionen teilnehmen.
Nach empirischen Untersuchungen liegt der Prüfaufwand bei 15-20% des Entwicklungsaufwands,
wobei ca. 60-70% der Fehler in einem Dokument gefunden werden. Der Nettonutzen durch die
frühe Fehlererkennung liegt bei 20% in der Entwicklung und 30% in der Wartung.
Ein Review ist eine Variante der Programminspektion, bei der kein fester Ablauf vorgeschrieben ist
und auch nur ein informelles Prüfprotokoll angefertigt wird. Ein Review ist fast so Effektiv wie eine
Programminspektion. Die Akzeptanz ist in der Regel höher, da keine sensitiven formale Inspektionsprotokolle erzeugt werden. Eine Optimierung des Entwicklungsprozesses ist nicht möglich.
Ein Walkthrough ist eine weiter abgeschwächte Form, in der der Autor das Prüfobjekt Schritt für
Schritt vorstellt und die Gutachter spontane Fragen stellen.
5.3
Testverfahren
Testverfahren können nach der Transparenz des Prüflings klassifiziert werden:
• Black Box: Interne Struktur der Software-Komponente unbekannt
• White Box: Interne Struktur der Software-Komponente bildet Testgrundlage
• Grey Box: Zwischenkategorie
Zur Veranschaulichung verschiedener Testverfahren betrachten wir im folgenden eine Java Klasse
Vowel mit der Methode countVowels():
public class Vowel {
private char[] s;
public Vowel(String sentence) {
s = sentence.toCharArray();
}
/**
*
*/
Counts how many vowels occur in a sentence.
A sentence must be terminated by a dot.
5.3. TESTVERFAHREN
71
public int countVowels() {
int i, count;
i = 0;
count = 0;
while (s[i] != ’.’) {
if (s[i] == ’a’ || s[i] == ’e’ || s[i] == ’i’
|| s[i] == ’o’ || s[i] == ’u’) {
count++;
}
i++;
}
return count;
}
}
5.3.1
Funktionale Testverfahren
Funktionale Testverfahren testen gegen die Spezifikation und lassen die interne Programmstruktur
unberücksichtigt.
• Setzt eine vollständige und widerspruchsfreie Spezifikation voraus.
• Funktionale Testverfahren benötigen ein Orakel, das die richtigen Testergebnisse aufgrund
der Spezifikation ermittelt.
• Konstruktion von Testfällen durch Äquivalenzklassenbildung:
– Wertebereiche von Ein- und Ausgaben werden in Äquivalenzklassen eingeteilt.
– Bildung der Äquivalenzklassen orientiert sich ausschließlich an der Spezifikation.
– Äquivalenzklassen für gültige und ungültige Werte.
– Getestet wird jeweils nur noch für einen Repräsentanten einer Äquivalenzklasse.
• Regeln zur Äquivalenzklassenbildung:
– Jeder spezifizierte Eingabebereich induziert mindestens eine gültige und eine ungültige
Klasse.
– Jede Eingabebedingung induziert eine gültige (Bedingung erfüllt) und eine ungültige
(Bedingung nicht erfüllt) Klasse.
– Bildet eine Eingabebedingung eine Menge von Werten, die unterschiedlich behandelt
werden, ist für jeden Fall eine gültige und eine ungültige Äquivalenzklasse zu bilden.
– Für Ausgaben werden analog Äquivalenzklassen gebildet.
• Wahl der Repräsentanten:
– Zufällige Auswahl
– Wahl der Repräsentanten an den Rändern der Klassen, da Grenzbereiche besonders
häufig fehlerhaft sind.
Konstruktion von Äquivalenzklassen für die Methode countVowels(). Als Spezifikation liegt
lediglich der Kommentar vor, aus dem sich aber schon drei Äquivalenzklassen ableiten lassen:
72
KAPITEL 5. QUALITÄTSSICHERUNG
- K1: Die Zeichenfolge endet nicht mit einem Punkt.
- K2: Die Zeichenfolge endet in einem Punkt und enthält keine Vokale.
- K3: Die Zeichenfolge endet in einem Punkt und enthält Vokale.
Da countVowels() nach Vokalen unterscheiden wird, sollte die Klasse K3 noch weiter unterteilt
werden:
- K3a-K3e: Die Zeichenfolge enthält ein a, e, i, o, u.
- K3f-K3j: Die Zeichenfolge enthält ein A, E, I, O, U.
Entsprechend der Ausgabe können noch zwei weitere Klassen unterschieden werden:
- K4: Die Zeichenfolge enthält mehrere gleiche Vokale.
- K5: Die Zeichenfolge enthält mehrere unterschiedliche Vokale.
Man kann drei Repräsentanten auswählen, die diese Äquivalenzklassen abdecken. Damit erhält man
z.B. folgenden Test:
import junit.framework.*;
public class VowelTest extends TestCase {
Vowel v1, v2, v3;
public VowelTest(String name) {
super(name);
}
protected void setUp() {
v1 = new Vowel("X");
v2 = new Vowel(".");
v3 = new Vowel("XAaEeIiOoUuA.");
}
public void testClass1() {
Assert.assertEquals(0, v1.countVowels());
}
public void testClass2() {
Assert.assertEquals(0, v2.countVowels());
}
public void testClass3() {
Assert.assertEquals(11, v3.countVowels());
}
}
Offensichtlich besitzt countVowels() Fehler und besteht diesen funktionalen Test nicht. Man
beachte aber auch, daß die Spezifikation von countVowels() unvollständig ist.
73
5.3. TESTVERFAHREN
5.3.2
Kontrollfluß-orientierte Testverfahren
Kontrollflußorientierte Testverfahren betrachten bestimmte Strukturelemente eines Programms wie
z.B. Anweisungen, Zweige, Bedingungen oder Pfade und fordern, daß der Kontrollfluß jedes dieser
Elemente mindestens einmal erreicht (Überdeckungstest).
Programmdarstellung als Kontrollflußgraph:
• Knoten stellen Anweisungen oder Bedingungen einer Kontrollstruktur dar.
• Kanten stellen den möglichen Kontrollfluß zwischen zwei Anweisungen dar.
• Ein Pfad ist eine Kombination von Kanten, die vom Startknoten zum Endknoten führt.
Das Methode countVowels() besitzt folgenden Kontrollflußgraphen:
i=0
count = 0
while (s[i] != ’.’)
if (s[i] == ’a’ || s[i] == ’e’ || s[i] == ’i’
|| s[i] == ’o’ || s[i] == ’u’)
count++
i++
Abbildung 5.1: Kontrollflußgraph für countVowels()
Anweisungsüberdeckung
• Jeder Knoten des Kontrollflußgraphen muß einmal ausgeführt werden.
• Nicht sehr leistungsfähiges Minimalkriterium.
• Unentdeckte Fehler in nicht ausgeführten Zweigen.
74
KAPITEL 5. QUALITÄTSSICHERUNG
Zweigüberdeckung
• Jede Kante des Kontrollflußgraphen muß einmal durchlaufen werden.
• Realistisches Minimalkriterium das die Anweisungsüberdeckung einschließt.
• Unentdeckte Fehler bei Kombinationen und Wiederholungen von Schleifen.
Bedingungsüberdeckung
• Jede Bedingung muß mindestens einmal den Wert false und mindestens einmal den Wert true
annehmen.
• Bei atomarer Bedingungsüberdeckung muß jede einfache Bedingung den Wert true oder false
annehmen.
• Die atomare Bedingungsüberdeckung umfaßt nicht die Anweisungsüberdeckung.
• Bei der minimalen Mehrfachbedingungsüberdeckung muß jede Bedingung (ob einfach oder
nicht) einmal den Wert false und einmal den Wert true liefern.
• Die minimale Mehrfachbedingungsüberdeckung orientiert sich an der syntaktischen Struktur
von Bedingungen und ist ein guter Kompromiß und impliziert die Zweigüberdeckung bei
einer Anweisungsüberdeckung.
Pfadüberdeckung
• Jeder Pfad des Kontrollflußgraphen muß einmal durchlaufen werden.
• Theoretisches Kriterium, da in den meisten praktischen Fällen durch Schleifen unendlich
viele Pfade möglich sind.
• Vergleichsmaßstab für andere Überdeckungstests.
• Selbst eine Pfadüberdeckung findet nicht alle Fehler.
5.3.3
Mutationstesten
Beim Mutationstesten werden kleine Änderungen (Mutationen) im Quelltext vorgenommen und es
wird anschließend überprüft, wieviele der so erzeugten Mutanten von den Tests erkannt werden.
• Im Idealfall sollte ein Satz von Testfällen alle Mutanten erkennen.
• Mutanten kann man automatisch erzeugen und so auch automatisch die Qualität der Testfälle
ermitteln.
• Es werden nur einfache“ Fehler erzeugt unter der Annahme, daß sich komplexe Fehler aus
”
einfachen zusammensetzen.
Man unterscheidet folgende Mutationsoperatoren:
5.4. QUALITÄTSSICHERUNG MIT ISO 9000
75
• Berechnugsfehler:
– Ändern von arithmetischen Operationen
– Löschen von arithmetischen (Teil-) Ausdrücken
– Ändern von Konstanten
• Schnittstellenfehler:
– Vertauschen / Ändern von Parametern
– Aufruf anderer Methoden
• Kontrollflußfehler:
– Ersetzen von logischen (Teil-) Ausdrücken durch true und false
– Ändern von logischen und relationalen Operatoren
– Aufruf anderer Methoden
– Löschen von Anweisungen
– Einfügen von HALT-Anweisungen
• Initialisierungsfehler:
– Änderungen von Konstanten
– Löschen von Initialisierungsanweisungen
• Datenflußfehler:
– Vertauschen von Variablen in einem Sichtbarkeitsbereich
– Änderungen in der Indexberechnung
5.3.4 Regressionstesten
Beim Regressionstesten geht es darum, nach Änderungen am Programmtext sicherzustellen, daß die
bisher bestandenen Tests auch von der neuen, veränderten Programmversionen bestanden werden.
Normalerweise wächst die Sammlung an Tests im Laufe der Zeit, da bei jeder Fehlerbehebung
zunächst ein Testfall geschrieben werden sollte, der den Test auslöst. Das konsequente Regressionstesten hat somit zur Folge, daß tendenziell die Qualität der Software im Laufe der Zeit ebenfalls
besser werden sollte.
5.4
Qualitätssicherung mit ISO 9000
Das ISO 9000 Normenwerk legt für das Auftraggeber-Lieferantenverhältnis einen allgemeinen, organisatorischen Rahmen zur Qualitätssicherung fest.
76
KAPITEL 5. QUALITÄTSSICHERUNG
Kapitel 6
Werkzeuge
Zur effzienten Softwareentwicklung ist eine Werkzeugunterstützung notwendig. Im folgenden werden einige ausgewählte Werkzeuge kurz vorgestellt, die insbesondere in den späten Phasen eines
Softwareprojekts sinnvoll eingesetzt werden können. Weitere Informationen findet man in [22].
6.1
Debugger
Zur Fehlersuche ist es gelegentlich nützlich, das Programmverhalten an kritischen Stellen genau
inspizieren zu können. Debugger sind Werkzeuge, mit denen ich Programme in Einzelschritten
ablaufen lassen kann und den jeweils aktuellen Zustand inspizieren und ggf. auch verändern kann.
Voraussetzung ist oftmals, daß bei der Übersetzung spezielle Debug-Informationen erzeugt wurden.
6.1.1
Java Debugger (jdb)
Die Standardumgebung von Java enthält den Debugger jdb. Mit jdb kann ein Java-Programm
beliebig angehalten, in Einzelschritten ausgeführt und der Inhalt von Variablen inspiziert und auch
während der Ausführung verändert werden. Der jdb besitzt eine einfach und sehr minimale Schnittstelle.
$ jdb junit.textui.TestRunner VowelTest
Initializing jdb...
0x405df8f0:class(junit.textui.TestRunner)
> stop in Vowel.countVowels
Breakpoint set in Vowel.countVowels
> run
run junit.textui.TestRunner VowelTest
running ...
main[1] .
Breakpoint hit: Vowel.countVowels (Vowel:15)
main[1] list
11
*/
12
13
public int countVowels() {
14
int i, count;
15
=>
i = 0;
77
78
KAPITEL 6. WERKZEUGE
16
17
18
19
main[1] next
count = 0;
while (s[i] != ’.’) {
if (s[i] == ’a’ || s[i] == ’e’ || s[i] == ’i’
|| s[i] == ’o’ || s[i] == ’u’) {
Breakpoint hit: Vowel.countVowels (Vowel:16)
main[1] main[1] list
12
13
public int countVowels() {
14
int i, count;
15
i = 0;
16
=>
count = 0;
17
while (s[i] != ’.’) {
18
if (s[i] == ’a’ || s[i] == ’e’ || s[i] == ’i’
19
|| s[i] == ’o’ || s[i] == ’u’) {
20
count++;
main[1] next
Breakpoint hit: Vowel.countVowels (Vowel:17)
main[1] main[1] locals
Method arguments:
this = Vowel@80bc241
Local variables:
i = 0
count = 0
6.1.2
Data Display Debugger (ddd)
Etwa mehr Komfort liefern graphische Debugger, die zum Teil nur eine graphische Benutzungsoberfläche auf einen existierenden Debugger wie z.B. jdb oder gdb aufsetzen. Ein gutes Beispiel
ist der Data Display Debugger ddd, der insbesondere auch in der Lage ist, komplexere Datenstrukturen zu visualisieren.
6.2 Programmkonstruktion
Bei der Implementierung ist es oftmals erforderlich, komplexere Artefakte (Software, Dokumentation) zu erstellen, die sich aus einzelnen Komponenten zusammensetzen. Zwischen den einzelnen
Komponenten bestehen in der Regel Abhängigkeiten, so daß die Änderung an einem Dokument
Änderungen an daraus erzeugten Dokumenten erfordert.
Werkzeuge zur Programmkonstruktion beschreiben die Abhängigkeiten und unterstützen die automatische Aktualisierung von Dokumenten.
6.2.1
make
Die Abhängigkeiten zwischen Komponenten eines Systems können durch einen Abhängigkeitsgraphen dargestellt werden. Ein Makefile ist eine textuelle Beschreibung des Abhängigkeitsgraphen nebst Kommandos zur Erzeugung der einzelnen Komponenten.
6.2. PROGRAMMKONSTRUKTION
79
Abbildung 6.1: Data Display Debugger
Beim Aufruf von make wird ein interner Abhängigkeitsgraph konstruiert und es wird geprüft, ob
es abhängige Dateien gibt, die älter sind als die Dateien, von denen sie abhängen. Ist das der Fall,
so wird die abhängige Datei durch Ausführung des zugeordneten Kommandos aktualisiert.
CLASSPATH =
.:/usr/share/java/junit.jar
JAVA =
JAVAC =
JAVADOC =
JFLAGS =
java -classpath $(CLASSPATH)
javac -classpath $(CLASSPATH)
javadoc -classpath $(CLASSPATH)
-g
SOURCES =
CLASSES =
Vowel.java VowelTest.java
$(SOURCES:.java=.class)
%.class: %.java
80
KAPITEL 6. WERKZEUGE
$(JAVAC) $(JFLAGS) $<
all: $(CLASSES)
test: $(CLASSES)
$(JAVA) junit.textui.TestRunner VowelTest
test-awt: $(CLASSES)
$(JAVA) $(JFLAGS) junit.awtui.TestRunner VowelTest
doc:
rm -rf $@; mkdir $@
$(JAVADOC) -d $@ $(SOURCES)
clean:
rm -f $(CLASSES)
rm -rf doc
VowelTest.class: VowelTest.java Vowel.java
Vowel.class: Vowel.java
6.3
Versionsmanagement
Bei der Entwicklung von Software fallen viele verschiedene Versionen der einzelnen Dokumente
an. Außerdem ist es nicht unüblich, daß Entwickler gleichzeit an einem Dokument arbeiten. Ein
Werkzeug zur Versionskontrolle verwaltet die verschiedenen Versionen und erlaubt es, jederzeit
eine ganz bestimmte Menge von bestimmten Versionen zu erhalten.
6.3.1
Revision Control System (RCS)
Das Revision Control System (RCS) verwaltet Revisionen und Varianten.
• Revisionen sind zeitlich aufeinanderfolgende Versionen eines Dokuments.
• Varianten sind gleichzeitig existierende alternative Ausprägungen eines Dokuments
• Die Ordnung der Versionen wird durch einen Versionsgraphen dargestellt:
2.0.1.1.1
2.0.1.1
2.0.1.2 2.0.1.3 2.0.1.4
1.0
1.1
2.0
2.1
2.2
2.3
Abbildung 6.2: RCS Versionsgraph eines Dokuments
6.3. VERSIONSMANAGEMENT
81
• RCS speichert alle Versionen eines Dokuments in einer Datei.
• Alle Versionen sind jederzeit rekonstruierbar.
• Um den Platzbedarf für eine potentiell große Zahl von Versionen gering zu halten, speichert
RCS zu jeder Version nur die Unterschiede zum Vorgänger. Allein die letzte Version wird
vollständig gespeichert.
• Zu jeder Version werde Attribute wie Datum, Uhrzeit, Autor, Beschreibung und die Versionsnummer gespeichert.
• Zusätzlich können bestimmte Versionen eines Dokuments markiert werden.
• RCS verhindert, daß zwei Entwickler gleichzeitig eine Datei modifizieren (pessimistischer
Ansatz).
• Die wichtigsten Befehle sind:
co
ci
rcsmerge
6.3.2
Arbeitsdatei (bestimmte Revision) aus der RCS-Datei nehmen.
Arbeitsdatei als neue Revision in die RCS-Datei übernehmen.
Integration zweier Varianten.
Concurrent Versions System (CVS)
Das Concurrent Versions System unterstützt im Gegensatz zum RCS die Versionierung ganzer Dateibäume. Insbesondere wird das Hinzufügen und Löschen von Dateien berücksichtigt.
Vowel.java
Makefile
VowelTest.java
Abbildung 6.3: CVS Konfigurationen
• Ein Schnitt durch die Versionen der verschiedenen Dokumente wird als Konfiguration bezeichnet.
• Die einzelnen Versionen eines Dokuments werden via RCS verwaltet.
• Konfigurationen können markiert werden.
82
KAPITEL 6. WERKZEUGE
• Mehrere Entwickler können sich Kopien derselben Konfiguration anlegen lassen und darauf
unabhängig arbeiten.
• Erst beim Einbringen der jeweiligen Änderungen wird geprüft, ob sich die einzelnen Änderungen überlappen (optimistischer Ansatz).
• Die wichtigsten Befehle sind:
cvs
cvs
cvs
cvs
cvs
cvs
6.4
Lokale Kopie eines CVS-Moduls anlegen.
Änderungen in das CVS-Archiv zurückschreiben.
Neue Datei in das CVS-Archiv aufnehmen.
Existierende Datei aus dem CVS-Archiv löschen.
Kopie mit der aktuellen CVS-Version synchronisieren.
Unterschiede relativ zur aktuellen CVS-Version zeigen.
checkout
commit
add
remove
update
diff
Testwerkzeuge
Testwerkzeuge haben die Aufgabe, die automatische Durchführung von Komponententests oder
Regressionstests zu unterstützen.
6.4.1
JUnit
Hinter JUnit [7, 6] verbirgt sich ein Rahmen für automatisierte Tests von Java Komponenten (in
der Regel Java Klassen) []. Beim Entwurf von JUnit wurde insbesondere darauf geachtet, dass es
für Java-Entwickler keine besondere Mühe ist, Tests zu schreiben und zu pflegen.
Assert
+assertEquals(): void
+assertNull(): void
+assertNotNull(): void
+assertSame(): void
+assertTrue(): void
+fail(): void
TestResult
«interface»
Test
contains
+run(result:TestResult): void
TestCase
+run(result:TestResult): void
+setUp(): void
+tearDown(): void
+runTest(): void
+suite(): Test
TestSuite
+fTests: Vector
+run(result:TestResult): void
+runTest(): void
Abbildung 6.4: UML Klassendiagramm für JUnit
Abbildung 6.4 zeigt die grundlegenden Klassen von JUnit:
• Die Klasse TestCase beschreibt einen Testfall. Die Implementation von Testfällen erfolgt
durch das Ableiten von speziellen Klassen aus der Klasse TestCase.
6.4. TESTWERKZEUGE
83
• Die Klasse TestSuite beschreibt eine Menge von Tests, wobei die Menge wiederum aus
TestSuites oder TestCases bestehen kann.
• Die Klasse Assert stellt eine Reihe von primitiven Vergleichen zur Verfügung, die der
Entwickler zur Konstruktion seiner Testfälle benutzen kann.
• Die Klasse TestResult protokolliert den Testablauf.
• Die Operation setUp stellt eine spezielle Testumgebung für einen Testfall bereit (fixture).
• Die Operation tearDown löscht die Testumgebung.
• Die Operation suite dient als Einstiegspunkt zur Abarbeitung eines Testfalls.
• Die einzelnen Tests eines Testfalls werden als Operationen implementiert, die mit dem Namenspräfix test“ beginnen.
”
Beispiel: Betrachten Sie eine Klasse Temperature, die Temperaturen repräsentiert und insbesondere die Umrechnung von Grad Celsius (c) in Grad Fahrenheit (f ) und umgekehrt unterstützt
(f = 95 c + 32 bzw. c = 59 (f − 32)). Zu dieser Klasse sollen Testfälle in JUnit implementiert
werden.
Eine einfache Implementation der Klasse Temperature in Java wäre etwa:
public class Temperature {
private float celsius;
public Temperature() {
celsius = 0;
}
public void setCelsius(float degrees) {
celsius = degrees;
}
public float getCelsius() {
return celsius;
}
public void setFahrenheit(float degrees) {
celsius = (degrees - 32) * 5 / 9;
}
public float getFahrenheit() {
return (celsius * 9 / 5 + 32);
}
}
Eine Sammlung von grundlegenden Tests in JUnit:
import junit.framework.*;
public class TemperatureTest extends TestCase {
84
KAPITEL 6. WERKZEUGE
private
private
private
private
Temperature
Temperature
Temperature
Temperature
zeroCelsius;
zeroFahrenheit;
hundredCelsius;
hundredFahrenheit;
private static final float precision = (float) 0.0001;
public TemperatureTest(String name) {
super(name);
}
protected void setUp() {
zeroCelsius = new Temperature();
zeroCelsius.setCelsius(0);
zeroFahrenheit = new Temperature();
zeroFahrenheit.setFahrenheit(0);
hundredCelsius = new Temperature();
hundredCelsius.setCelsius(100);
hundredFahrenheit = new Temperature();
hundredFahrenheit.setFahrenheit(100);
}
protected void tearDown() {
zeroCelsius = null;
zeroFahrenheit = null;
hundredCelsius = null;
hundredFahrenheit = null;
}
public static Test suite() {
return new TestSuite(TemperatureTest.class);
}
public void testZeroCelsius() {
Assert.assertEquals(0,
zeroCelsius.getCelsius(),
precision);
Assert.assertEquals(32,
zeroCelsius.getFahrenheit(),
precision);
}
public void testHundredCelsius() {
Assert.assertEquals(100,
hundredCelsius.getCelsius(),
precision);
Assert.assertEquals(212,
hundredCelsius.getFahrenheit(),
precision);
}
public void testZeroFahrenheit() {
Assert.assertEquals(-17.7777,
zeroFahrenheit.getCelsius(),
6.5. INTEGRIERTE ENTWICKLUNGSUMGEBUNGEN
85
precision);
Assert.assertEquals(0,
zeroFahrenheit.getFahrenheit(),
precision);
}
public void testHundredFahrenheit() {
Assert.assertEquals(37.7777,
hundredFahrenheit.getCelsius(),
precision);
Assert.assertEquals(100,
hundredFahrenheit.getFahrenheit(),
precision);
}
}
6.5
Integrierte Entwicklungsumgebungen
Integrierte Entwicklungsumgebungen (integrated development environments, IDE) stellen eine Entwicklungsumgebung bereit, bei der die Funktionen vieler einzelner Werkzeuge integriert sind. Der
Vorteil liegt in einer konsistenteren Benutzungsoberfläche. Andererseits sind die einzelnen Werkzeuge in integrierten Entwicklungsumgebungen häufig unterschiedlich ausgereift und lassen sich
nur bedingt durch andere Komponenten austauschen.
Für Java gibt es unter anderem die folgenden integrierten Entwicklungsumgebungen:
• JBuilder (Borland)
• NetBeans (Open Source)
• Forte4J (Sun Microsystems), basiert auf NetBeans
• VisualAge for Java (IBM)
• Visual J++ (Microsoft)
• JDEE (Open Source), basiert auf Emacs
86
KAPITEL 6. WERKZEUGE
Teil II
Graphische Benutzungsoberflächen
87
Kapitel 7
Ergonomische Aspekte
Ziel der Software-Ergonomie ist die Entwicklung und Evaluierung gebrauchstauglicher SoftwareProdukte, die Benutzer zur Erreichung ihrer Arbeitsergebnisse befähigen und dabei ihre Belange im
jeweiligen Nutzungskontext beachten [1, 9]. Software-Ergonomie ist ein relativ junges Fachgebiet,
das starke Berührungspunkte zu anderen Fachgebieten besitzt:
• Kognitionspsychologie: Gedächtnis, Verstehen, Lernen, Handeln, Problemlösen
• Wahrnehmungspsychologie: visuelle, akustische, taktile Wahrnehmung
• Physiologie: Sensorik, Motorik, Körperhaltung
• Arbeitspsychologie: Arbeitsorganisation, Schulung, Benutzerpartizipation, Evaluation, Arbeitssicherheit
• Arbeitsplatzgestaltung: Möbel, Anordnung, Raumgestaltung, Beleuchtung
• Software-Engineering: Analyse, Modellierung, Entwurf, Prototyping, Werkzeuge
• Unterstützungssysteme: Hilfesysteme, Individualisierungssysteme, Aktivitätenmanagement,
Tutorielle Systeme
• Dialogparadigmen: Menüs und Masken, Kommandos, Direkte Manipulation, Hypermedia
• Dialogtechniken: Informationsdarstellung, Interaktionsformen, Dialogablauf
Eine optimale Anpassung zwischen Software-System und Benutzer erhält man, wenn das SoftwareSystem über ein explizites Benutzermodell anlegt und die Interaktion mit dem Benutzer entsprechend dies Modells steuert. In der Regel unterscheiden die meisten heutigen Softwaresysteme nur
Benutzergruppen:
• Anfänger (novices, beginners)
• Gelegenheitsbenutzer (casual users, infrequent users)
• Experten (experts, frequent users, sophisticated users)
89
90
KAPITEL 7. ERGONOMISCHE ASPEKTE
7.1 Dialogarten und Dialogmodi
• Ein Primärdialog ist ein Dialog, der erst bei der Fertigstellung einer Aufgabe beendet wird
(z.B. Bearbeitung eines UML-Diagramms).
• Ein Sekundärdialog ist ein kurzzeitiger Dialog, der zur Erledigung einer Aufgabe erforderliche Informationen anzeigt oder erfragt (z.B. Dateiauswahldialog).
• Bei einem modalen Dialog kann die Applikation erst dann weiter benutzt werden, wenn der
Dialog beendet worden ist.
• Ein nicht-modaler Dialog gestattet es dem Benutzer, den aktuellen Dialog zu Unterbrechen
(d.h. andere Funktionen auszuführen) und später den Dialog fortzusetzen.
• Generell gilt es modale Dialoge möglichst zu vermeiden, da sie die Flexibilität extrem einschränken. Dies gilt insbesondere für modale Dialoge, die auch die Benutzung anderer Applikationen unmöglich machen oder stark behindern.
• Bei der funktionsorientierten Bedienung wählt der Benutzer zunächst eine Funktion aus und
bestimmt anschließend auf welche Objekte diese Funktion angewendet werden soll (z.B.
Kommondosprachen).
• Bei der objektorientierten Bedienung wählt der Benutzer zuerst die Objekte aus, die er bearbeiten will, und anschließend die Funktion, die er auf den Objekten ausführen will (z.B.
graphische Benutzungsoberflächen mit direkter Manipulation).
7.2
Grundsätze zur Dialoggestaltung
Die europäische ISO-Norm 9241-10 [17] definiert allgemeine Anforderungen an die Gestaltung
von Dialogen. Die folgende Darstellung ist an [1] angelehnt.
7.2.1
Aufgabenangemessenheit
Ein Dialog ist aufgabenangemessen, wenn er den Benutzer unterstützt, seine Arbeitsaufgabe effektiv und effizient zu erledigen.
• Es sind keine zusätzlichen technischen Vor- und Nachbereitungen durch den Benutzer erforderlich.
• Der Dialog ist an die zu erledigenden Arbeitsaufgaben angepaßt. Art, Umfang und Komplexität der vom Benutzer zu verarbeitenden Informationen sind berücksichtigt.
• Regelmäßig wiederkehrende Arbeitsaufgaben werden unterstützt, z.B. durch Makrokommandos.
• Eingabevorbelegungen sind — soweit sinnvoll möglich — vorzunehmen; sie sind vom Benutzer änderbar.
7.2. GRUNDSÄTZE ZUR DIALOGGESTALTUNG
7.2.2
91
Selbstbeschreibungsfähigkeit
Ein Dialog ist selbstbeschreibungsfähig, wenn jeder einzelne Dialogschritt durch Rückmeldung des
Dialogsystems unmittelbar verständlich ist oder dem Benutzer auf Anfrage erklärt wird.
• Der Benutzer muß sich zweckmäßige Vorstellungen von den Systemzusammenhängen machen können (Unterstützung beim Aufbau mentaler Modelle).
• Erläuterungen sind an allgemein übliche Kenntnisse der zu erwartenden Benutzer angepaßt.
• Wahl zwischen kurzen und ausführlichen Erläuterungen.
• Kontextabhängige Erläuterungen.
• Dynamische Einblendung von Kurzbeschreibungen bei der Verwendung von Piktogrammen
(tool tips).
7.2.3
Steuerbarkeit
Ein Dialog ist steuerbar, wenn der Benutzer in der Lage ist, den Dialogablauf zu starten sowie seine
Richtung und Geschwindigkeit zu beeinflussen, bis das Ziel erreicht ist.
• Bedienung kann der eigenen Arbeitsgeschwindigkeit angepaßt werden.
• Arbeitsmittel und -wege sind durch den Benutzer frei wählbar.
• Vorgehen in leicht durchschaubaren Dialogschritten, wobei mehrere Schritte zusammenfassbar sein sollten.
• Der Benutzer erhält Informationen, die für die Arbeitswegplanung benötigt werden (z.B. Anzeige, welche Funktionen als nächstes wählbar sind).
• Ein Dialog kann beliebig unterbrochen und wieder aufgenommen werden.
• Mehrstufiges undo, d.h. Rücknehmbarkeit zusammenhängender Dialogschritte (Wideruf).
• Mehrstufiges redo, d.h. rückgängig gemachte Funktionen wieder ausführen ohne erneute Dateneingabe.
• Sicherheitsabfragen bei Aktionen von großer Tragweite.
• Steuerung der Menge der angezeigten Informationen.
7.2.4
Erwartungskonformität
Ein Dialog ist erwartungskonform, wenn er konsistent ist und den Merkmalen des Benutzers entspricht, z.B. seinen Kenntnissen aus dem Arbeitsgebiet, seiner Ausbildung und seiner Erfahrung
sowie den allgemein anerkannten Konventionen.
• Das Dialogverhalten ist einheitlich (z.B. konsistente Anordnung von Bedienungselementen).
92
KAPITEL 7. ERGONOMISCHE ASPEKTE
• Bei ähnlichen Arbeitsaufgaben ist der Dialog ähnlich gestaltet (z.B. Standarddialoge zum
öffnen von Dateien).
• Zustandsänderungen des Systems, die für die Dialogführung relevant sind, werden dem Benutzer mitgeteilt.
• Eingaben in Kurzform werden im Klartext bestätigt.
• Systemantwortzeiten sind den Erwartungen des Benutzers angepaßt, sonst erfolgt eine Meldung.
• Die Benutzer sind über den Stand der Bearbeitung informiert.
• Die Interaktionselemente zur Steuerung der Applikation funktionieren immer auf die gleiche
Art und Weise.
7.2.5
Fehlertoleranz
Ein Dialog ist fehlertolerant, wenn das beabsichtigte Arbeitsergebnis trotz erkennbar fehlerhafter
Eingaben entweder mit keinem oder mit minimalen Korrekturaufwand seitens des Benutzers erreicht werden kann.
• Benutzereingaben dürfen nicht zu Systemabstürzen oder undefinierten Systemzuständen führen.
• Automatisch korrigierbare Fehler können korrigiert werden. Der Benutzer ist darüber zu informieren.
• Die automatische Korrektur ist abschaltbar.
• Korrekturalternativen für Fehler werden dem Benutzer angezeigt.
• Fehlermeldungen weisen auf den Ort des Fehlers hin, z.B. durch Markierung der Fehlerstelle.
• Fehlermeldungen sind verständlich, sachlich und konstruktiv zu formulieren und sind einheitlich zu strukturieren (z.B. Fehlerart, Fehlerursachen, Fehlerbehebung).
• Wird ein Vorgang vom Benutzer versehentlich abgebrochen, so kann er später an der aktuellen
Stelle fortgesetzt werden, so dass der Benutzer nicht zum Anfang zurückkehren muß.
7.2.6
Individualisierbarkeit
Ein Dialog ist individualisierbar, wenn das Dialogsystem Anpassungen an die Erfordernisse der
Arbeitsaufgabe sowie an die individuellen Fähigkeiten und Vorliebend des Benutzers zuläßt.
• Anpassbarkeit an Sprache und kulturelle Eigenheiten des Benutzers, z.B. durch unterschiedliche Tastenbelegungen.
• Anpassbarkeit an das Wahrnehmungsvermögen und die sensomotorischen Fähigkeiten, z.B.
durch Wahl der Schriftgröße, Wahl der Farben für farbenfehlsichtige Benutzer, Zuordnung
der linken und rechten Maustaste.
7.3. ACHT GOLDENE REGELN FÜR DIE DIALOGGESTALTUNG
93
• Wahl unterschiedlicher Informations-Darstellungsformen.
• Möglichkeit, eigenes Vokabular zu benutzen, um eigene Bezeichnungen für Objekte und Arbeitsabläufe festzulegen.
• Möglichkeit, eigene Kommandos zu ergänzen, z.B. durch programmierbare Funktionstasten
und Aufzeichnung von Kommandofolgen.
• Die Benutzer können Präferenzen setzen oder Lesezeichen und Anmerkungen verwenden.
7.2.7
Lernförderlichkeit
Ein Dialog ist lernförderlich, wenn er den Benutzer beim Erlernen des Dialogsystems unterstützt
und anleitet.
• Darstellung der zugrundelegenden Regeln und Konzepte, die für das Erlernen nützlich sind.
• Unterstützung relevanter Lernstrategien, z.B. learning by doing.
• Wiederauffrischen von Gelerntem unterstützen, z.B. selbsterklärende Gestaltung selten benutzter Kommandos.
• Regelhaft und einheitliche Benutzungsoberfläche, z.B. gleichartige Hinweismeldungen erscheinen immer am gleichen Ort.
• Dem Benutzer stehen verschiedene Navigationsmöglichkeiten zur Verfügung, die er singulär
oder kombiniert verwenden kann.
7.3 Acht goldene Regeln für die Dialoggestaltung
Die folgenden acht goldenen Regeln fassen die wichtigsten Eigenschaften guter Dialogsysteme
nochmal zusammen [18]:
1. Versuche Konsistenz zu erreichen.
Aus ähnlichen Situationen sollten ähnliche Aktionsfolgen resultieren. In Prompts, Menüs und
Hilfeinformationen sollten identische Begriffe verwendet werden.
2. Biete erfahrenen Benutzern Abkürzungen an.
Je häufiger ein System benutzt wird, desto größer ist der Wunsch, nach weniger Interaktionen,
um schneller vorwärts zu kommen. Abkürzungen, Funktionstasten, versteckte Kommandos
und Makros sind nützliche Hilfsmittel, dies zu erreichen.
3. Biete informatives Feedback.
Jede Aktion sollte eine sichtbare Systemreaktion bewirken, wobei sich der Umfang des Feedbacks an der Tragweite der Aktion orientieren sollte. Die Visualisierung der Arbeitsobjekte
ist eine gute Möglichkeit, Änderungen zu verdeutlichen.
4. Dialoge sollten abgeschlossen sein.
Aktionsfolgen sollten einen Beginn, eine Mitte und ein Ende besitzen. Benutzer erkennen
dadurch einzelne Abfolgen und können sich nach dem Ende einer solchen Abfolge ganz auf
die nächste Abfolge konzentrieren.
94
KAPITEL 7. ERGONOMISCHE ASPEKTE
5. Biete einfache Fehlerbehandlung.
Es sollte grundsätzlich nicht möglich sein, schwerwiegende Fehler zu begehen. Falls ein Fahler passiert ist, sollte das System diesen erkennen und eine einfache Fehlerbehebung anbieten.
6. Biete einfache Rücksetzmöglichkeiten.
Aktionen sollte zurücknehmbar sein. Die nimmt dem Benutzer die Angst bei der Arbeit, da
jederzeit die Sicherheit besteht, zum Zustand vor einer Aktion zurückkehren zu können.
7. Unterstütze benutzergesteuerten Dialog.
Erfahrene Benutzer wollen das Gefühl haben, den Dialog im Griff zu haben. Unerwartete
Systemreaktionen, lange Dateneingabesequenzen, Schwierigkeiten, benötigte Informationen
abzurufen oder gewünschte Aktionen zu initiieren, führen zu Angst und Unzufriedenheit.
8. Reduziere die Belastung des Kurzzeitgedächtnisses.
Die Beschränkung des menschlichen Kurzzeitgedächtnisses erfordert relativ einfache Bildschirminhalte sowie Hilfemöglichkeiten für Syntaxanforderungen, Abkürzungen und Codes.
Kapitel 8
Java Abstract Windowing Toolkit
(AWT)
Das Abstract Windowing Toolkit (AWT) ist eine Sammlung von Java-Klassen zur Konstruktion graphischer Benutzungsoberflächen, das in frühen Java-Versionen benutzt wurde. Die aktuellen Versionen benutzen eine Weiterentwicklung mit dem Namen Swing, die im nächsten Kapitel vorgestellt
wird. Da AWT nur noch von älteren Java-Programmen bzw. in Umgebungen verwendet wird, in
denen nur eine alte Java-Version zur Verfügung steht, beschränkt sich dieses Kapitel lediglich auf
einige Grundlagen.
8.1 AWT Komponenten
Abbildung 8.1 zeigt die einzelnen Komponenten des AWT, aus denen graphische Benutzungsoberflächen zusammengesetzt werden.
Abbildung 8.1: AWT-Komponenten für graphische Benutzungsoberflächen
Die entsprechenden Klassen für die Komponenten sind in Abbildung 8.2 dargestellt:
95
96
KAPITEL 8. JAVA ABSTRACT WINDOWING TOOLKIT (AWT)
Button
Canvas
Checkbox
Dialog
Choice
FileDialog
Window
Frame
Container
Panel
Label
ScrollPane
Component
List
Scrollbar
Object
TextArea
TextComponent
TextField
CheckboxMenuItem
MenuItem
MenuComponent
Menu
PopupMenu
Menubar
Abbildung 8.2: AWT-Klassen zur Konstruktion graphischer Benutzungsoberflächen
Die Implementierung von AWT setzt auf die APIs der verschiedenen Plattformen auf. Dadurch
bekommt man das auf der Plattform typische Look and Feel“. Allerdings ist die Abbildung von
”
AWT auf die zugrundeliegende API nicht immer einfach und unproblematisch.
8.2 Ereignisorientiere Programmierung
• Ein klassisches“ Programm liest Eingaben, die es durch eine sequentielle (nicht-nebenläufi”
ge) Abarbeitung von Anweisungen verarbeitet und dabei Ausgaben erzeugt (z.B. Sortieren
einer Menge von Datensätzen, Bearbeitung einer Datenbankanfrage).
• Ein ereignisorientiertes Programm wartet in einer Hauptschleife auf Ereignisse (Taste gedrückt, Maus bewegt, Daten empfangen) und führt, abhängig vom eingetretenen Ereignis und
dem aktuellen Zustand des Programms, eine Sequenz von Anweisungen aus und kehrt danach
wieder in die Hauptschleife zurück (z.B. Graphische Oberflächen, Simulationsprogramme).
• Die Hauptschleife, die man meist nicht selbst programmieren muß, hat im wesentlichen folgenden logischen Aufbau:
97
8.2. EREIGNISORIENTIERE PROGRAMMIERUNG
Object
EventObject
AWTEvent
ActionEvent
ContainerEvent
AdjustmentEvent
ComponentEvent
FocusEvent
KeyEvent
InputEvent
ItemEvent
PaintEvent
TextEvent
WindowEvent
MouseEvent
Abbildung 8.3: AWT Ereignisse
while (true) {
EventObject e = waitForNextEvent();
processEvent(e);
}
• Das Programm sollte in ein kleine“ Funktionen zerlegt werden, die beim Eintreffen von
”
Ereignissen ausgeführt werden und meist nur relativ einfache Aktionen bewirken.
• Beim Verarbeiten von Ereignissen ist zu bestimmen, welche der implementierten kleinen“
”
Funktionen für das Ereignis zuständig ist.
• Komplexere Aktionen oder Aktionen die potentiell blockieren können sind mit besonderer
Sorgfalt zu behandeln, da ansonsten die Hauptschleife zu lange unterbrochen wird und das
Programm einfriert“.
”
Ereignisse werden in Java durch spezielle Klassen dargestellt (Abbildung 8.3). Ein kleines BeispielProgramm...
import java.awt.*;
import java.awt.event.*;
public class ActionExample extends Frame {
public ActionExample() {
Button button = new Button("activate me");
add(button);
}
}
98
KAPITEL 8. JAVA ABSTRACT WINDOWING TOOLKIT (AWT)
• Das Programm implementiert die Klasse ActionExample als eine Erweiterung der Klasse
Frame (ein einfacher Rahmen).
• Im Konstruktor wird ein Button erzeugt und in den Rahmen eingefügt.
• Frage: Was passiert wenn der Button gedrückt wird?
Klassen, die sogenannte EventListener Interfaces (Abbildung 8.4) implementieren, können
auf passende Ereignisse reagieren. Damit läßt sich das Beispielprogramm vervollständigen:
«interface»
ActionListener
«interface»
AdjustmentListener
«interface»
ComponentListener
«interface»
FocusListener
«interface»
ContainerListener
«interface»
EventListener
«interface»
ItemListener
«interface»
KeyListener
«interface»
TextListener
«interface»
MouseMotionListener
«interface»
MouseListener
«interface»
WindowListener
Abbildung 8.4: AWT Listener Interfaces
import java.awt.*;
import java.awt.event.*;
public class ActionExample extends Frame {
public ActionExample() {
Button button = new Button("activate me");
add(button);
button.addActionListener(new ButtonActionListener());
}
class ButtonActionListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
8.2. EREIGNISORIENTIERE PROGRAMMIERUNG
99
System.out.println("button action");
}
}
}
• Die Klasse ButtonActionListener implementiert das ActionListener Interface.
• Das ActionListener Interface erfordert von der Klasse ActionExample die Implementierung der Methode actionPerformed(ActionEvent e).
• Der Konstruktor ActionExample erzeugt eine Instanz der ButtonActionListener
Klasse zum Verarbeiten von ActionEvents.
• Die ButtonActionListener Instanz wird beim button eingetragen, damit später die
entsprechenden ActionEvents des button bearbeitet werden können.
Kann ein Button auch andere Events generieren?
import java.awt.*;
import java.awt.event.*;
public class ActionExample extends Frame {
public ActionExample() {
Button button = new Button("activate me");
add(button);
button.addMouseListener(new ButtonMouseListener());
}
class ButtonMouseListener implements MouseListener {
public void mouseEntered(MouseEvent e) {
System.out.println("mouse entered button");
}
public void mouseExited(MouseEvent e) {
System.out.println("mouse exited button");
}
public void mousePressed(MouseEvent e) {}
public void mouseClicked(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
}
}
• Das MouseListener Interface verlangt die Implementation von fünf Methoden:
– Eintritt des Mauszeigers: mouseEntered(MouseEvent e)
– Austritt des Mauszeigers: mouseExited(MouseEvent e)
– Drücken einer Maustaste: mousePressed(MouseEvent e)
– Klicken einer Maustaste: mouseClicked(MouseEvent e)
100
KAPITEL 8. JAVA ABSTRACT WINDOWING TOOLKIT (AWT)
– Loslassen einer Maustaste: mouseReleased(MouseEvent e)
• Die meisten der MouseListener Methoden sind hier offenbar leer“.
”
• Frage: Kann man sich die Implementation leerer“ Methoden nicht irgendwie sparen?
”
Adapter-Klassen sind leere“ EventListener.
”
import java.awt.*;
import java.awt.event.*;
public class ActionExample extends Frame {
public ActionExample() {
Button button = new Button("activate me");
add(button);
button.addMouseListener(new ButtonMouseAdapter());
}
class ButtonMouseAdapter extends MouseAdapter {
public void mouseEntered(MouseEvent e) {
System.out.println("mouse entered button");
}
public void mouseExited(MouseEvent e) {
System.out.println("mouse exited button");
}
}
}
Mit Hilfe von anonymen Klassen läßt sich das Programm noch etwas kompakter schreiben:
import java.awt.*;
import java.awt.event.*;
public class ActionExample extends Frame {
public ActionExample() {
Button button = new Button("activate me");
add(button);
button.addMouseListener(new MouseAdapter() {
public void mouseEntered(MouseEvent e) {
System.out.println("mouse entered button");
}
public void mouseExited(MouseEvent e) {
System.out.println("mouse exited button");
}
});
}
}
Kapitel 9
Java Foundation Classes (Swing)
Hinter dem Namen Swing [21] verbirgt sich eine Sammlung von Java-Klassen zur Implementation
von graphischen Benutzungsoberflächen, die Teil der Java Foundation Classes (JFC) ist. Die SwingKlassen, deren Namen (fast) alle mit einem großen J beginnen, sollen die vorher benutzten AWTKomponenten ersetzen. Die Swing-Klassen selbst basieren jedoch auf Teilen der grundlegenden
AWT-Infrastruktur. Eine Übersicht über die Klassen gibt die Abbildung 9.1.
Button
Canvas
JDialog
Dialog
Checkbox
FileDialog
Window
Choice
Frame
JFrame
Applet
JApplet
JMenu
Container
Panel
JMenuItem
JCheckBoxMenuItem
Component
Label
ScrollPane
JMenuBar
AbstractButton
List
JComponent
JRadioButtonMenuItem
JButton
JComboBox
JLabel
Scrollbar
JCheckButton
JToggleButton
JList
Object
TextArea
JOptionPane
TextField
JPanel
TextComponent
JRadioButton
JScrollPane
JSplitPane
CheckboxMenuItem
JTabbedPane
JFormattedTextField
JTextField
JTextComponent
JPasswordField
MenuItem
JToolBar
MenuComponent
Menu
JTextArea
PopupMenu
JEditorPane
JTextPane
Menubar
Abbildung 9.1: Swing-Klassen zur Konstruktion graphischer Benutzungsoberflächen
In den folgenden Abschnitten werden anhand von Beispielen Swing Konzepte und Klassen vorgestellt. Die Beispiele wurden alle mit Java Version 1.3 getestet. Einige Beispiele laufen nur mit
Änderungen auf älteren Java Versionen.
101
102
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
9.1 Einführende Beispiele
Zunächst ein paar kleine einführende Beispiele, die ein grundlegendes Verständnis für die Programmierung mit Java Swing wecken sollen.
9.1.1
Hello World
Mit einem Hello World“-Programm fängt immer alles an...
”
Abbildung 9.2: HelloWorldSwing
import javax.swing.JFrame;
import javax.swing.JLabel;
public class HelloWorldSwing {
public static void main(String[] args) {
JFrame frame = new JFrame("HelloWorldSwing");
final JLabel label = new JLabel("Hello World");
frame.getContentPane().add(label);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
}
• Erzeugt ein top-level Fenster (JFrame) mit dem Title HelloWorldSwing“.
”
• Erzeugt ein JLabel mit dem Inhalt Hello World“ und fügt das Objekt in die Content Pane
”
des JFrame-Objekts ein.
• Beim Löschen des Fensters soll das Programm beendet werden.
• Zum Schluß werden die Elemente im Frame angeordnet und sichtbar gemacht.
• Achtung: Labels können auch HTML enthalten!?!
9.1.2
Hello World mit Ereignisverarbeitung
Das Hello World“-Programm nochmal mit expliziter Behandlung von WindowEvents.
”
import
import
import
import
java.awt.event.WindowEvent;
java.awt.event.WindowAdapter;
javax.swing.JFrame;
javax.swing.JLabel;
public class HelloWorldSwing {
public static void main(String[] args) {
9.1. EINFÜHRENDE BEISPIELE
103
JFrame frame = new JFrame("HelloWorldSwing");
final JLabel label = new JLabel("Hello World");
frame.getContentPane().add(label);
frame.pack();
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}
• Explizite Reaktion auf WindowEvents durch eine anonyme innere Klasse, die die Methode
windowClosing der Adapter-Klasse WindowAdapter überschreibt.
• Ereignisorientierte Programmierung basiert auf AWT, was sich auch in den import-Zeilen
ausdrückt.
9.1.3
Celsius Converter
Ein einfaches Programm, das Werte in Grad Celsius entgegen nimmt und den Wert in Grad Fahrenheit ermittelt.
Abbildung 9.3: CelsiusConverter
import
import
import
import
import
import
import
import
import
import
java.awt.event.ActionEvent;
java.awt.event.ActionListener;
java.awt.BorderLayout;
java.awt.GridLayout;
javax.swing.JFrame;
javax.swing.JPanel;
javax.swing.JLabel;
javax.swing.JButton;
javax.swing.JTextField;
javax.swing.SwingConstants;
public class CelsiusConverter {
JFrame frame;
JPanel panel;
JLabel celLabel, fahLabel, resLabel;
JButton convButton, exitButton;
JTextField inputTextField;
public CelsiusConverter() {
104
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
frame = new JFrame("Convert Celsius to Fahrenheit");
panel = new JPanel();
panel.setLayout(new GridLayout(3, 2));
addWidgets();
frame.getContentPane().add(panel, BorderLayout.CENTER);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
private void addWidgets() {
celLabel = new JLabel("Celsius:", SwingConstants.LEFT);
fahLabel = new JLabel("Fahrenheit:", SwingConstants.LEFT);
resLabel = new JLabel("", SwingConstants.CENTER);
convButton = new JButton("Convert");
exitButton = new JButton("Exit");
inputTextField = new JTextField(2);
convButton.addActionListener(new ConvertListener());
exitButton.addActionListener(new ExitListener());
panel.add(celLabel);
panel.add(inputTextField);
panel.add(fahLabel);
panel.add(resLabel);
panel.add(convButton);
panel.add(exitButton);
}
class ConvertListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
double f;
try {
f = Double.parseDouble(inputTextField.getText());
f = f * 1.8 + 32;
} catch (Exception e) {
resLabel.setText("illegal input");
return;
}
resLabel.setText((int) f + " F");
}
}
class ExitListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
System.exit(0);
}
}
public static void main(String[] args) {
CelsiusConverter converter = new CelsiusConverter();
}
9.1. EINFÜHRENDE BEISPIELE
105
}
• Ein JPanel kann andere Komponenten aufnehmen.
• Die Anordnung der Komponenten im JPanel wird durch ein Layout Manager bestimmt
(GridLayout in diesem Beispiel).
• Ein JButton stellt eine einfache Schaltfläche zur Verfügung.
• Ein JTextField nimmt textuelle Eingaben von Benutzer entgegen.
• Für die beiden JButton wird jeweils ein ActionListener definiert.
• Man beachte die Fehlerbehandlung bei ungültigen Eingaben.
9.1.4
Celsius Converter mit Dialog
Tauscht man im CelsiusConverter Beispiele die Klasse ConvertListener durch folgende Implementation aus, so wird im Fehlerfall ein modaler Dialog angezeigt.
class ConvertListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
double f;
try {
f = Double.parseDouble(inputTextField.getText());
f = f * 1.8 + 32;
} catch (Exception e) {
JOptionPane.showMessageDialog(frame,
"illegal input " + inputTextField.getText(),
"Illegal Input", JOptionPane.ERROR_MESSAGE);
resLabel.setText("");
return;
}
resLabel.setText((int) f + " F");
}
}
• Mit Hilfe der Klasse JOptionPane können Standarddialoge erzeugt werden.
• Die Methode showMessageDialog ist eine Klassenmethode.
• Ein Dialog wird an einem top-level Object (hier ein JFrame) verankert.
9.1.5
Look And Feel
Swing Programme können ein unterschiedliches Erscheinungsbild haben (Look and Feel). Das folgende Programm demonstriert diese Fähigkeit.
Abbildung 9.4: LookAndFeelJava
106
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
Abbildung 9.5: LookAndFeelMotif
import
import
import
import
import
import
import
import
import
import
import
java.awt.event.ActionEvent;
java.awt.event.ActionListener;
java.awt.event.WindowEvent;
java.awt.event.WindowAdapter;
javax.swing.JFrame;
javax.swing.JPanel;
javax.swing.JButton;
javax.swing.JRadioButton;
javax.swing.ButtonGroup;
javax.swing.SwingUtilities;
javax.swing.UIManager;
public class LookAndFeel extends JPanel {
static JFrame frame;
static final String metal= "Metal";
static final String metalClassName =
"javax.swing.plaf.metal.MetalLookAndFeel";
static String motif = "Motif";
static String motifClassName =
"com.sun.java.swing.plaf.motif.MotifLookAndFeel";
static String windows = "Windows";
static String windowsClassName =
"com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
JRadioButton metalButton, motifButton, windowsButton;
public LookAndFeel() {
JButton button = new JButton("Hello, world");
button.setMnemonic(’h’);
metalButton = new JRadioButton(metal);
metalButton.setMnemonic(’t’);
metalButton.setActionCommand(metalClassName);
motifButton = new JRadioButton(motif);
motifButton.setMnemonic(’m’);
motifButton.setActionCommand(motifClassName);
windowsButton = new JRadioButton(windows);
windowsButton.setMnemonic(’w’);
windowsButton.setActionCommand(windowsClassName);
ButtonGroup group = new ButtonGroup();
group.add(metalButton);
9.1. EINFÜHRENDE BEISPIELE
107
group.add(motifButton);
group.add(windowsButton);
RadioListener myListener = new RadioListener();
metalButton.addActionListener(myListener);
motifButton.addActionListener(myListener);
windowsButton.addActionListener(myListener);
add(button);
add(metalButton);
add(motifButton);
add(windowsButton);
}
class RadioListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
String lnfName = e.getActionCommand();
try {
UIManager.setLookAndFeel(lnfName);
SwingUtilities.updateComponentTreeUI(frame);
frame.pack();
}
catch (Exception exc) {
JRadioButton button = (JRadioButton) e.getSource();
button.setEnabled(false);
updateState();
System.err.println("LookAndFeel: Can not load " + lnfName);
}
}
}
public void updateState() {
String lnfName = UIManager.getLookAndFeel().getClass().getName();
if (lnfName.indexOf(metal) >= 0) {
metalButton.setSelected(true);
} else if (lnfName.indexOf(windows) >= 0) {
windowsButton.setSelected(true);
} else if (lnfName.indexOf(motif) >= 0) {
motifButton.setSelected(true);
} else {
System.err.println("LookAndFeel: Unknown LookAndFeel "
+ lnfName);
}
}
public static void main(String s[]) {
LookAndFeel panel = new LookAndFeel();
frame = new JFrame("LookAndFeel");
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {System.exit(0);}
108
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
});
frame.getContentPane().add("Center", panel);
frame.pack();
frame.setVisible(true);
panel.updateState();
}
}
• Die Klasse LookAndFeel ist eine Spezialisierung des JPanel.
• Neben einem JButton werden drei JRadioButton erzeugt, die in einer ButtonGroup
zusammengefaßt werden.
• Jeder JRadioButton bekommt einen ActionListener installiert.
• Der RadioListener versucht das verwendete Look and Feel zu setzen. Im Fehlerfall wird
der entsprechende Knopf ausgeschaltet.
• Die updateState Methode selektiert den JRadioButton, der dem aktuellen Look and
Feel entspricht.
• Die main Methode erzeugt einen JFrame und eine LookAndFeel Instanz, die in die
content pane des JFrame eingetragen wird.
9.2
9.2.1
Grundlegende Konzepte
Root Panes
Abbildung 9.6: Swing Root Panes
• Jede top-level Komponente stellt ein sogenanntes root pane zur Verfügung, das selbst aus
mehreren Panes besteht.
• Das GlassPane ist normalerweise vollkommen transparent. Es dient lediglich dazu, Ereignisse für das gesamte root pane abzufangen.
109
9.3. LAYOUT MANAGER
• In dem ContentPane werden die eigentlichen Komponenten der Applikation untergebracht.
• Die Menüs einer Applikation werden im optionalen JMenuBar unterbracht und nicht etwa
in der ContentPane.
• Das LayeredPane wird benutzt, um die ContentPane und das optionale Menü zu positionieren. Man kann es auch benutzen, um andere Komponenten (z.B. Popup-Menüs) über
anderen Komponenten zu plazieren.
9.2.2
Threads
• Es gibt einen speziellen event dispatching thread“, der für die Abarbeitung von Swing”
Ereignissen zuständig ist.
• Die Methoden, die bei der Bearbeitung von Ereignissen aufgerufen werden, sollten keine
längeren Berechnungen anstellen, da sonst die Oberfläche bis zum Ende der Berechnung
einfriert“.
”
• Nachdem eine Swing-Komponente realisiert wurde (d.h., sie wurde auf dem Ausgabegerät
dargestellt), darf generell nur der event dispatching thread“ die Komponente aktualisieren.
”
Mit anderen Worten, Swing ist bis auf wenige Methoden nicht thread-safe.
• Beliebige Threads können mit den Methoden SwingUtilities.invokeLater und
SwingUtilities.invokeAndWait den event dispatching thread“ zur Ausführung
”
von bestimmten Code zu veranlassen, sobald alle übrigen Ereignisse abgearbeitet worden
sind.
9.3 Layout Manager
Ein Layout Manager arrangiert Komponenten in einem Container. Die Schnittstelle eines Layout
Managers ist durch ein Interface festgelegt.
FlowLayout
«interface»
LayoutManager
GridLayout
+addLayoutComponent(name:String,comp:Component): void
+layoutContainer(parent:Container): void
+minimumLayoutSize(parent:Container): Dimension
+preferredLayoutSize(parent:Container): Dimension
+removeLayoutComponent(comp:Component): void
BorderLayout
Object
BoxLayout
«interface»
LayoutManager2
GridBagLayout
+addLayoutComponent(comp:Component,constraints:Object): void
+invalidateLayout(target:Container): void
+maximumLayoutSize(target:Container): Dimension
+getLayoutAlignmentX(target:Container): float
+addLayoutAlignmentY(target:Container): float
CardLayout
Abbildung 9.7: Swing Layout Manager Klassen und Interfaces
110
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
• Manche Layout-Algorithmen laufen in zwei getrennten Phasen ab.
• Im Interface definierte Methoden minimumLayoutSize, preferredLayoutSize und
maximumLayoutSize liefern die minimale, normale oder maximale Größe für einen Container.
• Die Layout-Algorithmen können diese Werte benutzen, um für ineinander geschachtelte Container ein Layout zu ermitteln, wobei jeder Container einen anderen Layout-Algorithmus benutzen kann.
• Die Methode layoutContainer bewirkt schließlich die Berechnung eines Layouts.
• Es gibt eine Vielzahl von Layout-Manager Klassen, die verschiedene Algorithmen realisieren.
9.3.1
Border Layout
Die Klasse BorderLayout realisiert einen sehr einfachen Algorithmus, bei dem bis zu fünf Komponenten in den Positionen north, south, west, east und center plaziert werden können.
Abbildung 9.8: BorderLayoutDemo
import
import
import
import
java.awt.BorderLayout;
java.awt.Container;
javax.swing.JFrame;
javax.swing.JButton;
public class BorderLayoutDemo extends JFrame {
public BorderLayoutDemo() {
Container contentPane = getContentPane();
// install a new BorderLayout with hgap = 0 and vgap = 10
contentPane.setLayout(new BorderLayout(0, 10));
contentPane.add(new JButton("Button 1 (NORTH)"),
BorderLayout.NORTH);
contentPane.add(new JButton("2 (CENTER)"),
BorderLayout.CENTER);
contentPane.add(new JButton("Button 3 (WEST)"),
BorderLayout.WEST);
contentPane.add(new JButton("Long-Named Button 4 (SOUTH)"),
BorderLayout.SOUTH);
contentPane.add(new JButton("Button 5 (EAST)"),
BorderLayout.EAST);
111
9.3. LAYOUT MANAGER
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String args[]) {
BorderLayoutDemo window = new BorderLayoutDemo();
window.setTitle("BorderLayoutDemo");
window.pack();
window.setVisible(true);
}
}
• Übrig bleibender Platz wird der Mitte zugeordnet, d.h. die größte Fläche entsteht in der Mitte.
• Die optionalen Argumente definieren einen horizontalen bzw. einen vertikalen Abstand zwischen den Komponenten.
9.3.2
Box Layout
Die Klasse BoxLayout realisiert einen Algorithmus, bei dem Komponenten entweder übereinander (von oben nach unten) oder nebeneinander (von links nach rechts) angeordnet werden, wobei
die Ausrichtung der Komponenten und deren minimale, bevorzugte und maximale Größe berücksichtigt wird.
Abbildung 9.9: BoxLayoutDemo
import
import
import
import
import
java.awt.Container;
java.awt.Component;
javax.swing.BoxLayout;
javax.swing.JFrame;
javax.swing.JButton;
public class BoxLayoutDemo extends JFrame {
public BoxLayoutDemo() {
Container contentPane = getContentPane();
contentPane.setLayout(new BoxLayout(contentPane,
BoxLayout.Y_AXIS));
addAButton("Button 1");
addAButton("2");
addAButton("Button 3");
addAButton("Long-Named Button 4");
112
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
addAButton("Button 5");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private void addAButton(String text) {
JButton button = new JButton(text);
button.setAlignmentX(Component.CENTER_ALIGNMENT);
getContentPane().add(button);
}
public static void main(String args[]) {
BoxLayoutDemo window = new BoxLayoutDemo();
window.setTitle("BoxLayoutDemo");
window.pack();
window.setVisible(true);
}
}
• Verfügbarer Platz, der nicht von den Komponenten benötigt wird, wird am Ende (in LayoutRichtung gesehen) untergebracht.
• Mit Hilfe der setAlignmentX und setAlignmentY Methoden können Komponenten
bestimmen, ob sie linksbündig, zentriert oder rechtsbündig dargestellt werden sollten. Die definierten Konstanten LEFT ALIGNMENT, CENTER ALIGNMENT und RIGHT ALIGNMENT
entsprechen den Werten 0.0, 0.5 und 1.0.
• Zusätzliche unsichtbare Komponenten (sogenannte Boxen) können benutzt werden, um die
gewünschten Abstände zwischen benachbarten Komponenten festzulegen.
• Boxen mit einer festen Größe (rigid area) erzeugen einen festen Abstand zwischen Komponenten.
• Boxen mit variabler horizontaler oder vertikaler Größe (glue area) nehmen übrigen Platz auf.
• Füllboxen (filler) sind selbstdefinierte Boxen, die eine minimale, bevorzugte und maximale
Größe festlegen.
Eine Erweiterung des Beispiels mit Boxen:
public BoxLayoutDemo() {
Container contentPane = getContentPane();
contentPane.setLayout(new BoxLayout(contentPane,
BoxLayout.Y_AXIS));
addAButton("Button 1");
// rigid area
contentPane.add(Box.createRigidArea(new Dimension(0, 10)));
addAButton("2");
// custom filler
Dimension minSize = new Dimension(222, 5);
Dimension prefSize = new Dimension(222, 10);
Dimension maxSize = new Dimension(222, 20);
9.3. LAYOUT MANAGER
113
contentPane.add(new Box.Filler(minSize, prefSize, maxSize));
addAButton("Button 3");
// glue
contentPane.add(Box.createVerticalGlue());
addAButton("Long-Named Button 4");
// glue
contentPane.add(Box.createVerticalGlue());
addAButton("Button 5");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
• Die selbstdefinierte Füllbox erzwingt eine Breite von mindestens 222 Punkten. Die Höhe
bewegt sich im Bereich 5-20 Punkte mit einem bevorzugten Wert von 10 Punkten.
• Sind mehrere variable Boxen vorhanden, dann wird der übrige Platz zwischen den Boxen
aufgeteilt.
9.3.3
Card Layout
Die Klasse CardLayout realisiert einen Algorithmus, bei dem mehrere Komponenten quasi auf
einem Stapel übereinander angeordnet werden, wobei immer nur die oberste Komponente sichtbar
ist.
Abbildung 9.10: CardLayoutDemo (Buttons)
Abbildung 9.11: CardLayoutDemo (Textfield)
import
import
import
import
import
import
import
import
import
import
java.awt.CardLayout;
java.awt.BorderLayout;
java.awt.Container;
java.awt.event.ItemEvent;
java.awt.event.ItemListener;
javax.swing.JFrame;
javax.swing.JButton;
javax.swing.JPanel;
javax.swing.JTextField;
javax.swing.JComboBox;
114
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
public class CardLayoutDemo extends JFrame implements ItemListener {
JPanel cards;
final static String BUTTONPANEL = "JPanel with JButtons";
final static String TEXTPANEL = "JPanel with JTextField";
public CardLayoutDemo() {
Container contentPane = getContentPane();
String comboBoxItems[] = { BUTTONPANEL, TEXTPANEL };
JComboBox c = new JComboBox(comboBoxItems);
c.setEditable(false);
c.addItemListener(this);
JPanel cbp = new JPanel();
cbp.add(c);
contentPane.add(cbp, BorderLayout.NORTH);
cards = new JPanel();
cards.setLayout(new CardLayout());
JPanel p1 = new JPanel();
p1.add(new JButton("Button 1"));
p1.add(new JButton("Button 2"));
p1.add(new JButton("Button 3"));
JPanel p2 = new JPanel();
p2.add(new JTextField("TextField", 20));
cards.add(p1, BUTTONPANEL);
cards.add(p2, TEXTPANEL);
contentPane.add(cards, BorderLayout.CENTER);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void itemStateChanged(ItemEvent e) {
CardLayout cl = (CardLayout) (cards.getLayout());
cl.show(cards, (String) e.getItem());
}
public static void main(String args[]) {
CardLayoutDemo window = new CardLayoutDemo();
window.setTitle("CardLayoutDemo");
window.pack();
window.setVisible(true);
}
}
• Die einzelnen Komponenten auf dem Stapel werden über einen Namen identifiziert.
• Komponenten können über den Namen direkt oben auf den Stapel gelegt werden.
• Alternativ kann durch den Stapel vorwärts und rückwärts hindurch gelaufen werden.
• Wird kaum noch benutzt, da ein JTabbedPane in den meisten Fällen einfacher zu benutzen
ist.
115
9.3. LAYOUT MANAGER
Abbildung 9.12: TabbedPaneDemo
import
import
import
import
import
import
import
import
java.awt.Container;
java.awt.BorderLayout;
java.awt.Dimension;
javax.swing.JFrame;
javax.swing.JButton;
javax.swing.JPanel;
javax.swing.JTextField;
javax.swing.JTabbedPane;
public class TabbedPaneDemo extends JFrame {
final static String BUTTONPANEL = "JPanel with JButtons";
final static String TEXTPANEL = "JPanel with JTextField";
public TabbedPaneDemo() {
Container contentPane = getContentPane();
JTabbedPane tabbedPane = new JTabbedPane();
JPanel p1 = new JPanel();
p1.add(new JButton("Button 1"));
p1.add(new JButton("Button 2"));
p1.add(new JButton("Button 3"));
tabbedPane.addTab(BUTTONPANEL, p1);
JPanel p2 = new JPanel();
p2.add(new JTextField("TextField", 20));
tabbedPane.addTab(TEXTPANEL, p2);
contentPane.add(tabbedPane, BorderLayout.CENTER);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String args[]) {
TabbedPaneDemo window = new TabbedPaneDemo();
window.setTitle("TabbedPaneDemo");
window.pack();
// Force the window to be 400 x 120 pixels wide.
window.setSize(400, 120);
window.setVisible(true);
}
}
116
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
9.3.4
Flow Layout
Die Klasse FlowLayout realisiert einen einfachen Algorithmus, bei dem die Komponenten in
ihrer bevorzugten Größe in einer Zeile von links nach rechts angeordnet werden.
Abbildung 9.13: FlowLayoutDemo
import
import
import
import
java.awt.FlowLayout;
java.awt.Container;
javax.swing.JFrame;
javax.swing.JButton;
public class FlowLayoutDemo extends JFrame {
public FlowLayoutDemo() {
Container contentPane = getContentPane();
contentPane.setLayout(new FlowLayout(FlowLayout.LEFT));
contentPane.add(new
contentPane.add(new
contentPane.add(new
contentPane.add(new
contentPane.add(new
JButton("Button 1"));
JButton("2"));
JButton("Button 3"));
JButton("Long-Named Button 4"));
JButton("Button 5"));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String args[]) {
FlowLayoutDemo window = new FlowLayoutDemo();
window.setTitle("FlowLayoutDemo");
window.pack();
window.setVisible(true);
}
}
• Wenn nicht alle Komponenten in einer Zeile untergebracht werden können, dann werden
zusätzliche Zeilen angelegt.
• Der optionale Parameter im Konstruktor FlowLayout bestimmt, wie die Komponenten in
den Zeilen zueinander angeordnet werden.
9.3.5
Grid Layout
Die Klasse GridLayout implementiert einen Algorithmus, der Komponenten tabellenförmig darstellt. Die einzelnen Zellen bekommen alle dieselbe Größe zugeordnet.
117
9.3. LAYOUT MANAGER
Abbildung 9.14: GridLayoutDemo
import
import
import
import
java.awt.Container;
java.awt.GridLayout;
javax.swing.JFrame;
javax.swing.JButton;
public class GridLayoutDemo extends JFrame {
public GridLayoutDemo() {
Container contentPane = getContentPane();
contentPane.setLayout(new GridLayout(0, 2, 10, 10));
contentPane.add(new
contentPane.add(new
contentPane.add(new
contentPane.add(new
contentPane.add(new
JButton("Button 1"));
JButton("2"));
JButton("Button 3"));
JButton("Long-Named Button 4"));
JButton("Button 5"));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String args[]) {
GridLayoutDemo window = new GridLayoutDemo();
window.setTitle("GridLayoutDemo");
window.pack();
window.setVisible(true);
}
}
• Die ersten zwei Parameter definieren die Anzahl der Zeilen (rows) und Spalten (columns) der
Tabelle und legen somit das Format der Tabelle (aber nicht deren Größe) fest.
• Die optionalen Parameter drei und vier definieren einen festen horizontalen bzw. vertikalen
Abstand zwischen den Zellen.
9.3.6
Grid Bag Layout
Die Klasse GridBagLayout implementiert den flexibelsten Layout Algorithmus. Er ordnet Komponenten in einer Tabelle an, wobei die Spalten und Zeilen unterschiedliche Breite bzw. Höhe haben
können. Einzelne Komponenten können sich auch über mehrere Zellen ausdehnen.
118
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
Abbildung 9.15: GridBagLayoutDemo
import
import
import
import
import
import
java.awt.Container;
java.awt.GridBagLayout;
java.awt.GridBagConstraints;
java.awt.Insets;
javax.swing.JFrame;
javax.swing.JButton;
public class GridBagLayoutDemo extends JFrame {
public GridBagLayoutDemo() {
JButton button;
Container contentPane = getContentPane();
GridBagLayout gridbag = new GridBagLayout();
contentPane.setLayout(gridbag);
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
button = new JButton("Button 1");
c.weightx = 0.5;
c.gridx = 0;
c.gridy = 0;
gridbag.setConstraints(button, c);
contentPane.add(button);
button = new JButton("2");
c.gridx = 1;
c.gridy = 0;
gridbag.setConstraints(button, c);
contentPane.add(button);
button = new JButton("Button 3");
c.gridx = 2;
c.gridy = 0;
gridbag.setConstraints(button, c);
contentPane.add(button);
button = new JButton("Long-Named Button 4");
c.ipady = 40;
// make this component tall
c.weightx = 0.0;
c.gridwidth = 3;
c.gridx = 0;
9.3. LAYOUT MANAGER
119
c.gridy = 1;
gridbag.setConstraints(button, c);
contentPane.add(button);
button = new JButton("Button 5");
c.ipady = 0;
c.weighty = 1.0;
// request any extra vertical space
c.anchor = GridBagConstraints.SOUTH; // bottom of space
c.insets = new Insets(10,0,0,0);
// top padding
c.gridx = 1;
c.gridwidth = 2;
c.gridy = 2;
gridbag.setConstraints(button, c);
contentPane.add(button);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String args[]) {
GridBagLayoutDemo window = new GridBagLayoutDemo();
window.setTitle("GridBagLayoutDemo");
window.pack();
window.setVisible(true);
}
}
• Für jede Komponente bestimmt ein GridBagConstraints Objekt die Parameter für das
GridBagLayout Objekt.
• Die GridBagConstraints Instanzvariablen gridx und gridy definieren die Position
der Komponente im Gitter.
• Die Konstante GridBagConstraints.RELATIVE kann für die nächste Position rechts
(bzw. unten) benutzt werden.
• Die GridBagConstraints Instanzvariablen gridwidth und gridheight bestimmen die Anzahl Zellen, die von der Komponente belegt werden sollen.
• Die GridBagConstraints Instanzvariable fill bestimmt, ob bzw. wie eine Komponente übrigen Platz aufnehmen soll. Es gibt hierfür die Konstanten NONE, HORIZONTAL,
VERTICAL und BOTH.
• Ein interner Abstand (internal padding) kann mit Hilfe der ipadx und ipady Instanzvariable definiert werden.
• Ein externer Abstand (external padding) kann mit Hilfe der inset Instanzvariablen definiert
werden. Der Wert ist ein Objekt der Klasse Insets.
• Ein Insets Objekt repräsentiert den Rand eine Containers. Der Insets-Konstruktor erwartet die Parameter top, left, bottom,
textttright.
• Ist die Komponente kleiner als der verfügbare Platz, dann bestimmt die anchor Instanzvariable, wo die Komponente plaziert wird. Dazu dienen die Konstanten NORTH, NORTHEAST,
EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST und NORTHWEST.
120
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
• Mit Hilfe der weightx und weighty Variablen wird definiert, wie freier Platz auf die Spalten einer Zeile bzw. auf die Zeilen einer Spalte verteilt wird. Wichtig ist dabei das Verhältnis
der Gewichte der einzelnen Komponenten zueinander und nicht der absolute Wert. Die Werte
liegen typischerweise zwischen 0.0 und 1.0, wobei ein größerer Wert mehr Gewicht bedeutet.
9.3.7
Absolute Positionierung
In einigen Spezialfällen ist es nicht sinnvoll, daß das Layout dynamisch erfolgt. In diesen Fällen
kann man die Komponenten absolut plazieren.
Abbildung 9.16: AbsoluteLayoutDemo
import
import
import
import
java.awt.Container;
java.awt.Insets;
javax.swing.JFrame;
javax.swing.JButton;
public class AbsoluteLayoutDemo extends JFrame {
public AbsoluteLayoutDemo() {
JButton b1, b2, b3;
Container contentPane = getContentPane();
contentPane.setLayout(null);
b1 = new JButton("one");
contentPane.add(b1);
b2 = new JButton("two");
contentPane.add(b2);
b3 = new JButton("three");
contentPane.add(b3);
Insets insets = contentPane.getInsets();
b1.setBounds(25 + insets.left, 5 + insets.top, 75, 20);
b2.setBounds(55 + insets.left, 35 + insets.top, 75, 20);
b3.setBounds(150 + insets.left, 15 + insets.top, 75, 30);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String args[]) {
AbsoluteLayoutDemo window = new AbsoluteLayoutDemo();
Insets insets = window.getInsets();
window.setTitle("AbsoluteLayoutDemo");
window.setSize(250 + insets.left + insets.right,
90 + insets.top + insets.bottom);
window.setVisible(true);
}
121
9.4. RÄNDER
}
• Die Position wird mit der Methode setBounds direkt als Eigenschaft der Komponenten
festgelegt.
9.4 Ränder
Oftmals ist es sinnvoll, einen Rand um eine Komponente zu zeichnen. Das Interface Border definiert die Schnittstelle für einen Rand. Die abstrake Klasse AbstractBorder implementiert diese
Schnittstelle und dient als Basisklasse für verschiedene Arten von Rändern. Applikationen erzeugen normalerweise konkrete Ränder durch den Aufruf von entsprechenden Methoden der Klasse
BorderFactory.
«interface»
Border
EmptyBorder
AbstractBorder
MatteBorder
LineBorder
EtchedBorder
Object
BevelBorder
SoftBevelBorder
BorderFactory
TitledBorder
CompoundBorder
Abbildung 9.17: Swing Border Klassenhierarchie
9.4.1
Einfache Ränder
Einfache Ränder definieren eine Rahmen, der unterschiedlich hervorgehoben oder begrenzt sein
kann.
import
import
import
import
import
import
import
import
import
java.awt.Container;
java.awt.Color;
java.awt.Dimension;
java.awt.BorderLayout;
javax.swing.BorderFactory;
javax.swing.border.Border;
javax.swing.JLabel;
javax.swing.JPanel;
javax.swing.JFrame;
122
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
Abbildung 9.18: BorderSimpleDemo
import javax.swing.Box;
import javax.swing.BoxLayout;
public class BorderSimpleDemo extends JFrame {
public BorderSimpleDemo() {
Border border;
Container contentPane = getContentPane();
contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
border = BorderFactory.createLineBorder(Color.black);
addLabel(border, "line border");
border = BorderFactory.createEtchedBorder();
addLabel(border, "etched border");
border = BorderFactory.createRaisedBevelBorder();
addLabel(border, "raised bevel border");
border = BorderFactory.createLoweredBevelBorder();
addLabel(border, "lowered bevel border");
border = BorderFactory.createEmptyBorder();
addLabel(border, "empty border");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
void addLabel(Border border, String description) {
JPanel panel = new JPanel();
JLabel label = new JLabel(description, JLabel.CENTER);
panel.add(label);
panel.setBorder(border);
getContentPane().add(Box.createRigidArea(new Dimension(0, 10)));
getContentPane().add(panel);
}
public static void main(String[] args) {
123
9.4. RÄNDER
JFrame window = new BorderSimpleDemo();
window.setTitle("BorderSimpleDemo");
window.pack();
window.setVisible(true);
}
}
• Jede Swing-Komponente, die von JComponent abgeleitet ist, kann einen Rand besitzen.
Ränder selbst sind jedoch keine eigenständigen Swing-Komponenten.
• Die setBorder Methode der Klasse JComponent erlaubt es, den Rand zu definieren.
9.4.2
Ränder mit Titeln
Ränder lassen sich mit Titeln zu versehen, um auf diese Weise zusammenhängende Komponenten
unter einem Begriff zusammenzufassen.
Abbildung 9.19: BorderTitleDemo
import
import
import
import
import
import
import
import
import
import
java.awt.Container;
java.awt.Color;
java.awt.BorderLayout;
javax.swing.BorderFactory;
javax.swing.border.Border;
javax.swing.border.TitledBorder;
javax.swing.JLabel;
javax.swing.JPanel;
javax.swing.JFrame;
javax.swing.BoxLayout;
public class BorderTitleDemo extends JFrame {
public BorderTitleDemo() {
Border border;
TitledBorder titled;
124
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
Container contentPane = getContentPane();
contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
titled = BorderFactory.createTitledBorder("title");
addLabel(titled, "default titled border",
TitledBorder.DEFAULT_JUSTIFICATION,
TitledBorder.DEFAULT_POSITION);
border = BorderFactory.createLineBorder(Color.black);
titled = BorderFactory.createTitledBorder(border, "title");
addLabel(titled, "titled line border",
TitledBorder.CENTER,
TitledBorder.DEFAULT_POSITION);
border = BorderFactory.createEtchedBorder();
titled = BorderFactory.createTitledBorder(border, "title");
addLabel(titled, "titled etched border",
TitledBorder.RIGHT,
TitledBorder.DEFAULT_POSITION);
border = BorderFactory.createLoweredBevelBorder();
titled = BorderFactory.createTitledBorder(border, "title");
addLabel(titled, "titled lowered bevel border",
TitledBorder.DEFAULT_JUSTIFICATION,
TitledBorder.ABOVE_TOP);
border = BorderFactory.createEmptyBorder();
titled = BorderFactory.createTitledBorder(border, "title");
addLabel(titled, "titled empty border",
TitledBorder.DEFAULT_JUSTIFICATION,
TitledBorder.BOTTOM);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
void addLabel(TitledBorder border, String description,
int justification, int position) {
border.setTitleJustification(justification);
border.setTitlePosition(position);
JPanel panel = new JPanel();
JLabel label = new JLabel(description, JLabel.CENTER);
panel.add(label);
panel.setBorder(border);
getContentPane().add(panel);
}
public static void main(String[] args) {
JFrame window = new BorderTitleDemo();
window.setTitle("BorderTitleDemo");
window.pack();
window.setVisible(true);
}
}
125
9.4. RÄNDER
• Ein TitledBorder benutzt einen beliebigen Border um darauf einen Titel anzuzeigen.
• Was passiert wohl, wenn der zugrundeliegende Border schon ein TitledBorder ist?
• Für den Titel läßt sich die Ausrichtung und die Position festlegen.
9.4.3
Zusammengesetzte Ränder
Komplexe Ränder lassen sich aus einfacheren Ränder zusammensetzen.
Abbildung 9.20: BorderCompoundDemo
import
import
import
import
import
import
import
import
import
import
import
import
java.awt.Container;
java.awt.Dimension;
java.awt.Color;
java.awt.BorderLayout;
javax.swing.BorderFactory;
javax.swing.border.Border;
javax.swing.border.TitledBorder;
javax.swing.JLabel;
javax.swing.JPanel;
javax.swing.JFrame;
javax.swing.Box;
javax.swing.BoxLayout;
public class BorderCompoundDemo extends JFrame {
public BorderCompoundDemo() {
Container contentPane = getContentPane();
contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
Border raisedbevel = BorderFactory.createRaisedBevelBorder();
addLabel(raisedbevel, "raised bevel border");
Border loweredbevel = BorderFactory.createLoweredBevelBorder();
addLabel(loweredbevel, "lowered bevel border");
Border compound
= BorderFactory.createCompoundBorder(raisedbevel, loweredbevel);
126
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
addLabel(compound, "compound border (two bevels)");
Border redline = BorderFactory.createLineBorder(Color.red);
compound = BorderFactory.createCompoundBorder(redline, compound);
addLabel(compound, "compound border (add a red outline)");
TitledBorder titled
= BorderFactory.createTitledBorder(compound, "title");
addLabel(titled, "compound border (add a title)");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
void addLabel(Border border, String description) {
JPanel panel = new JPanel();
JLabel label = new JLabel(description, JLabel.CENTER);
panel.add(label);
panel.setBorder(border);
getContentPane().add(Box.createRigidArea(new Dimension(0, 10)));
getContentPane().add(panel);
}
public static void main(String[] args) {
JFrame window = new BorderCompoundDemo();
window.setTitle("BorderCompoundDemo");
window.pack();
window.setVisible(true);
}
}
• Einige der Swing-Komponenten benutzen Border um sich selbst darzustellen.
• Ein Aufruf der setBorder Methode kann bei diesen Klassen u.U. nicht den gewünschten
Effekt liefern.
9.5 Menüs
Menüs sind ein platzsparender Mechanismus, mit dem ein Benutzer eine von vielen möglichen
Funktionen auswählen kann.
import
import
import
import
import
import
import
import
import
import
import
import
java.awt.event.KeyEvent;
java.awt.event.ActionEvent;
java.awt.event.ActionListener;
javax.swing.Box;
javax.swing.JFrame;
javax.swing.JMenu;
javax.swing.JMenuItem;
javax.swing.JCheckBoxMenuItem;
javax.swing.JRadioButtonMenuItem;
javax.swing.ButtonGroup;
javax.swing.JMenuBar;
javax.swing.KeyStroke;
127
9.5. MENÜS
Abbildung 9.21: MenuDemo
import javax.swing.ImageIcon;
public class MenuDemo extends JFrame {
public MenuDemo() {
JMenuBar menuBar;
JMenu menu, submenu;
JMenuItem menuItem;
JCheckBoxMenuItem cbMenuItem;
JRadioButtonMenuItem rbMenuItem;
menuBar = new JMenuBar();
setJMenuBar(menuBar);
menu = new JMenu("Menu");
menu.setMnemonic(KeyEvent.VK_M);
menuBar.add(menu);
menuItem = new JMenuItem("A text-only menu item");
menuItem.setMnemonic(KeyEvent.VK_T);
menuItem.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_1, ActionEvent.ALT_MASK));
menu.add(menuItem);
menuItem = new JMenuItem("Both text and icon",
new ImageIcon("middle.gif"));
menuItem.setMnemonic(KeyEvent.VK_B);
menu.add(menuItem);
menuItem = new JMenuItem(new ImageIcon("middle.gif"));
menuItem.setMnemonic(KeyEvent.VK_D);
menu.add(menuItem);
menu.addSeparator();
128
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
ButtonGroup group = new ButtonGroup();
rbMenuItem = new JRadioButtonMenuItem("A radio button menu item");
rbMenuItem.setSelected(true);
rbMenuItem.setMnemonic(KeyEvent.VK_R);
group.add(rbMenuItem);
menu.add(rbMenuItem);
rbMenuItem = new JRadioButtonMenuItem("Another one");
rbMenuItem.setMnemonic(KeyEvent.VK_O);
group.add(rbMenuItem);
menu.add(rbMenuItem);
menu.addSeparator();
cbMenuItem = new JCheckBoxMenuItem("A check box menu item");
cbMenuItem.setMnemonic(KeyEvent.VK_C);
menu.add(cbMenuItem);
cbMenuItem = new JCheckBoxMenuItem("Another one");
cbMenuItem.setMnemonic(KeyEvent.VK_H);
menu.add(cbMenuItem);
menu.addSeparator();
submenu = new JMenu("A submenu");
submenu.setMnemonic(KeyEvent.VK_S);
menuItem = new JMenuItem("An item in the submenu");
menuItem.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_2, ActionEvent.ALT_MASK));
submenu.add(menuItem);
menuItem = new JMenuItem("Another item");
submenu.add(menuItem);
menu.add(submenu);
menu.addSeparator();
menuItem = new JMenuItem("Exit");
menuItem.setMnemonic(KeyEvent.VK_E);
menuItem.addActionListener(new ExitListener());
menu.add(menuItem);
menu = new JMenu("AnotherMenu");
menu.setMnemonic(KeyEvent.VK_A);
menu.setEnabled(false);
menuBar.add(menu);
menu = new JMenu("Help");
menu.setMnemonic(KeyEvent.VK_H);
menuBar.add(Box.createHorizontalGlue());
menuBar.add(menu);
menuItem = new JMenuItem("About");
129
9.5. MENÜS
menuItem.setEnabled(false);
menu.add(menuItem);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
class ExitListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
System.exit(0);
}
}
public static void main(String[] args) {
MenuDemo window = new MenuDemo();
window.setTitle("MenuDemo");
window.setSize(450, 260);
window.setVisible(true);
}
}
• Ein Objekt der Klasse JMenuBar wird mit Hilfe der setJMenuBar Methode einem JFrame
zugeordnet.
• Ein Objekt der Klasse JMenuBar enthält ein oder mehrere Objekte der Klasse JMenu, die
selbst Objekte der Klassen JMenuItem, JRadioButtonMenuItem, JCheckBoxMenuItem
oder JMenu enthalten.
• Die Methode addSeparator erzeugt im Menü einen Trennstrich.
• Alle Elemente eines Menüs können einzeln mit der Methode setEnabled gesperrt werden.
• Auf die einzelnen Menüpunkte kann durch die Definition von ActionListener reagiert
werden.
9.5.1
Toolbars
Häufig gebrauchte Menüpunkte können in sogenannten Toolbars untergebracht werden.
Abbildung 9.22: ToolBarDemo
import
import
import
import
import
java.awt.Dimension;
java.awt.BorderLayout;
java.awt.event.ActionEvent;
java.awt.event.ActionListener;
javax.swing.JToolBar;
130
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
import
import
import
import
import
import
javax.swing.JButton;
javax.swing.ImageIcon;
javax.swing.JFrame;
javax.swing.JTextArea;
javax.swing.JScrollPane;
javax.swing.JPanel;
public class ToolBarDemo extends JFrame {
protected JTextArea textArea;
public ToolBarDemo() {
JToolBar toolBar = new JToolBar();
addButtons(toolBar);
textArea = new JTextArea(5, 30);
JScrollPane scrollPane = new JScrollPane(textArea);
JPanel contentPane = new JPanel();
contentPane.setLayout(new BorderLayout());
contentPane.setPreferredSize(new Dimension(400, 100));
contentPane.add(toolBar, BorderLayout.NORTH);
contentPane.add(scrollPane, BorderLayout.CENTER);
setContentPane(contentPane);
}
protected void addButtons(JToolBar toolBar) {
JButton button = null;
button = new JButton(new ImageIcon("left.gif"));
button.setToolTipText("left button");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
displayResult("Action for left button");
}
});
toolBar.add(button);
button = new JButton(new ImageIcon("middle.gif"));
button.setToolTipText("middle button");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
displayResult("Action for middle button");
}
});
toolBar.add(button);
button = new JButton(new ImageIcon("right.gif"));
button.setToolTipText("right button");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
displayResult("Action for right button");
}
});
9.5. MENÜS
131
toolBar.add(button);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
protected void displayResult(String actionDescription) {
textArea.append(actionDescription + "\n");
}
public static void main(String[] args) {
ToolBarDemo window = new ToolBarDemo();
window.setTitle("ToolBarDemo");
window.pack();
window.setVisible(true);
}
}
• Die Elemente einer JToolBar werden in einer Zeile oder Spalte angeordnet.
• Die JToolBar kann vom Benutzer an die verschiedenen Kanten des Containers geschoben
werden oder ganz abgenommen werden.
• Damit das Verschieben funktioniert, muß der Container den BorderLayout Algorithmus
verwenden und sich im Zentrum befinden.
• Mit Hilfe der setFloatable Methode kann das Verschieben eines Objekts der Klasse
JToolBar verhindert werden.
• Die Methode addSeparator erzeugt einen kleinen Abstand zwischen Elementen einer
JToolBar.
9.5.2
Actions
Wenn mehrere JButtons bzw. JMenuItems dieselbe Aktion ausführen, dann ist die Implementation einer gemeinsamen Aktion sinnvoll um die Konsistenz der Oberfläche zu gewährleisten.
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
java.awt.Dimension;
java.awt.BorderLayout;
java.awt.event.ActionEvent;
java.awt.event.ItemEvent;
java.awt.event.ItemListener;
javax.swing.AbstractAction;
javax.swing.Action;
javax.swing.JToolBar;
javax.swing.JButton;
javax.swing.ImageIcon;
javax.swing.JMenuItem;
javax.swing.JCheckBoxMenuItem;
javax.swing.JMenu;
javax.swing.JMenuBar;
javax.swing.JFrame;
javax.swing.JTextArea;
132
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
import javax.swing.JScrollPane;
import javax.swing.JPanel;
public class ActionDemo extends JFrame {
protected JTextArea textArea;
protected Action leftAction;
protected Action middleAction;
protected Action rightAction;
public ActionDemo() {
JToolBar toolBar = new JToolBar();
toolBar.setFloatable(false);
JMenu mainMenu = new JMenu("Menu");
createButtons(toolBar, mainMenu);
JMenu actionMenu = new JMenu("Action State");
createStateMenu(actionMenu);
textArea = new JTextArea(5, 30);
JScrollPane scrollPane = new JScrollPane(textArea);
JPanel contentPane = new JPanel();
contentPane.setLayout(new BorderLayout());
contentPane.setPreferredSize(new Dimension(400, 150));
contentPane.add(toolBar, BorderLayout.NORTH);
contentPane.add(scrollPane, BorderLayout.CENTER);
setContentPane(contentPane);
JMenuBar mb = new JMenuBar();
mb.add(mainMenu);
mb.add(actionMenu);
setJMenuBar(mb);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
protected void createButtons(JToolBar toolBar, JMenu mainMenu) {
JButton button = null;
JMenuItem menuItem = null;
leftAction = new AbstractAction("Left",
new ImageIcon("left.gif")) {
public void actionPerformed(ActionEvent e) {
displayResult("Left button/menu action", e);
}
};
button = toolBar.add(leftAction);
button.setText("");
button.setToolTipText("This is the left button");
menuItem = mainMenu.add(leftAction);
menuItem.setIcon(null);
9.5. MENÜS
middleAction = new AbstractAction("Middle",
new ImageIcon("middle.gif")) {
public void actionPerformed(ActionEvent e) {
displayResult("Middle button/menu action", e);
}
};
button = toolBar.add(middleAction);
button.setText("");
button.setToolTipText("This is the middle button");
menuItem = mainMenu.add(middleAction);
menuItem.setIcon(null);
rightAction = new AbstractAction("Right",
new ImageIcon("right.gif")) {
public void actionPerformed(ActionEvent e) {
displayResult("Right button/menu action", e);
}
};
button = toolBar.add(rightAction);
button.setText("");
button.setToolTipText("This is the right button");
menuItem = mainMenu.add(rightAction);
menuItem.setIcon(null);
}
protected void createStateMenu(JMenu actionMenu) {
JCheckBoxMenuItem cbmi = null;
cbmi = new JCheckBoxMenuItem("Left action enabled");
cbmi.setSelected(true);
cbmi.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
boolean selected =
(e.getStateChange() == ItemEvent.SELECTED);
leftAction.setEnabled(selected);
}
});
actionMenu.add(cbmi);
cbmi = new JCheckBoxMenuItem("Middle action enabled");
cbmi.setSelected(true);
cbmi.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
boolean selected =
(e.getStateChange() == ItemEvent.SELECTED);
middleAction.setEnabled(selected);
}
});
actionMenu.add(cbmi);
cbmi = new JCheckBoxMenuItem("Right action enabled");
cbmi.setSelected(true);
cbmi.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
133
134
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
boolean selected =
(e.getStateChange() == ItemEvent.SELECTED);
rightAction.setEnabled(selected);
}
});
actionMenu.add(cbmi);
}
protected void displayResult(String description, ActionEvent e) {
textArea.append("Action description: " + description + "\n"
+ "Event source: " + e.getSource() + "\n");
}
public static void main(String[] args) {
ActionDemo window = new ActionDemo();
window.setTitle("ActionDemo");
window.pack();
window.setVisible(true);
}
}
9.6
Scrolling und Splitting
Mit Hilfe der Klasse JScrollPane und des Interfaces Scrollable lassen sich verschiebbare Ausschnitte einer Komponente darstellen. Der Aussschnitt wird durch ein Objekt der Klasse
JViewport dargestellt.
Abbildung 9.23: Swing JScrollPane Funktionsmodell
• Eine JScrollPane nimmt eine Komponente auf, die das Interface Scrollable implementieren muß (JList, JTextComponent, JTree, JTable).
• An der rechten und unteren Seite des sichtbaren Ausschnitts der Komponente können ein
vertikale bzw. ein horizontaler Rollbalken dargestellt werden.
9.6. SCROLLING UND SPLITTING
135
• An der linken und oberen Seite des sichtbaren Ausschnitts der Komponente können Komponenten als Zeilen- bzw. Spalterüberschriften dargestellt werden. Mit den Methoden setColumnHeaderView
und setRowHeaderView können die Überschriften gesetzt werden.
• Die sich ergebenden Ecken können ebenfalls mit Komponenten belegt werden.
Abbildung 9.24: ScrollDemo
import
import
import
import
import
import
import
java.awt.Toolkit;
java.awt.event.ActionEvent;
java.awt.event.ActionListener;
javax.swing.JButton;
javax.swing.JFrame;
javax.swing.JTextArea;
javax.swing.JScrollPane;
public class ScrollDemo extends JFrame {
private JScrollPane scrollPane;
public ScrollDemo() {
JTextArea textArea = new JTextArea(5, 30);
scrollPane = new JScrollPane(textArea);
getContentPane().add(scrollPane);
int policy = JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS;
scrollPane.setHorizontalScrollBarPolicy(policy);
JButton button = new JButton();
scrollPane.setCorner(JScrollPane.LOWER_RIGHT_CORNER, button);
button.addActionListener(new ButtonListener());
for (int i = 0; i < 8; i++) {
textArea.append("Line " + i + "\n");
}
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
class ButtonListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
Toolkit.getDefaultToolkit().beep();
}
}
public static void main(String[] args) {
136
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
ScrollDemo window = new ScrollDemo();
window.setTitle("ScrollDemo");
window.pack();
window.setVisible(true);
}
}
Mit Hilfe der Klasse JSplitPane können zwei Komponenten nebeneinander oder übereinander
dargestellt werden, wobei die Aufteilung des zur Verfügung stehenden Platzes durch den Benutzer
manipuliert werden kann.
Abbildung 9.25: SplitDemo
import
import
import
import
import
import
import
import
import
java.awt.Dimension;
javax.swing.JLabel;
javax.swing.JFrame;
javax.swing.JTextArea;
javax.swing.JList;
javax.swing.DefaultListModel;
javax.swing.JScrollPane;
javax.swing.JOptionPane;
javax.swing.JSplitPane;
public class SplitDemo extends JFrame {
public SplitDemo() {
DefaultListModel listModel = new DefaultListModel();
JList listArea = new JList(listModel);
JTextArea textArea = new JTextArea(5, 30);
JScrollPane textScrollPane = new JScrollPane(textArea);
JScrollPane listScrollPane = new JScrollPane(listArea);
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
listScrollPane, textScrollPane);
splitPane.setOneTouchExpandable(true);
splitPane.setDividerLocation(0.25);
JLabel label = new JLabel("Another useless label");
JSplitPane topSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
137
9.7. TEXT KOMPONENTEN
splitPane, label);
getContentPane().add(topSplitPane);
Dimension minimumSize = new Dimension(100, 50);
listScrollPane.setMinimumSize(minimumSize);
textScrollPane.setMinimumSize(minimumSize);
for (int i = 0; i < 10; i++) {
textArea.append("Text line " + i + "\n");
listModel.addElement("List item " + i);
}
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
SplitDemo window = new SplitDemo();
window.setTitle("SplitDemo");
window.pack();
window.setVisible(true);
}
}
• Das explizite Setzen von setOneTouchExpandable stellt sicher, daß die kleinen Knöpfe
zum Öffnen/Schließen der Teile sichtbar sind. Wenn man diesen Parameter nicht explizit setzt
hängt das Erscheinen vom verwendeten LookAndFeel ab.
• Mit setDividerLocation kann die Position der Trennlinie definiert werden. Ein Wert
zwischen 0 und 1 definiert die relative Größe der linken/oberen Hälfte während eine ganze
Zahl die Größe der linken/oberen Hälfte in Pixeln festlegt.
• Das Setzen der minimalen Größe der Komponenten eines JSplitPane bewirkt, daß die
einzelnen Teile nicht beliebig klein werden können.
• Bei der Konstruktion der JList Komponente wird eine Trennung von Model und View
benutzt. Die Instanz der Klasse DefaultListModel enthält die eigentlichen Daten und
realisiert das Model während die Instanz der Klasse JList den ensprechenden View realisiert.
9.7 Text Komponenten
Die Swing-Klassen stellen verschiedenen Komponenten zur Darstellung und Manipulation von Texten bereit. Diese Komponenten trennen den eigentlichen Text (ein Dokument) von seiner Darstellung.
9.7.1
Dokumente
Ein Dokument wird durch eine Klasse realisiert, die das Interface Document realisiert. Jeder Teilabschnitt eines Dokuments besteht aus einem Element. Eine Erweiterung des Interfaces Document
138
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
existiert für Dokumente, die über eine logische Struktur verfügen und in denen einzelne Textabschnitte verschiedene Attribute besitzen können.
«interface»
«interface»
Document
StyledDocument
«interface»
Element
DefaultStyledDocument
Object
HTMLDocument
AbstractDocument
PlainDocument
«interface»
«interface»
EventListener
DocumentListener
Abbildung 9.26: Swing Klassen und Interfaces für Dokumente
• Ein PlainDocument wird für einfache Textfelder benutzt, die den gesamten Text in demselben Zeichensatz darstellen.
• Jedem Dokument kann mit addDocumentListener ein DocumentListener zugeordnet werden.
• Das DocumentListener Interface erfordert die Implementation der Methoden insertUpdate,
removeUpdate und changedUpdate.
• Instanzen der Klasse PlainDocument erzeugen lediglich Events für das Einfügen und
Löschen.
9.7.2
Ereignisse von Dokumenten
Abbildung 9.27: DocumentEventDemo
9.7. TEXT KOMPONENTEN
import
import
import
import
import
import
import
import
import
import
import
import
139
java.awt.event.ActionEvent;
java.awt.event.ActionListener;
java.awt.Dimension;
javax.swing.BoxLayout;
javax.swing.JPanel;
javax.swing.JFrame;
javax.swing.JScrollPane;
javax.swing.JTextField;
javax.swing.JTextArea;
javax.swing.text.Document;
javax.swing.event.DocumentEvent;
javax.swing.event.DocumentListener;
public class DocumentEventDemo extends JFrame {
JTextField textField;
JTextArea displayArea;
public DocumentEventDemo() {
textField = new JTextField(40);
textField.addActionListener(new MyTextActionListener());
textField.getDocument().addDocumentListener(new MyDocumentListener());
displayArea = new JTextArea(10,40);
displayArea.setEditable(false);
JScrollPane displayScrollPane = new JScrollPane(displayArea);
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.add(textField);
panel.add(displayScrollPane);
setContentPane(panel);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
class MyDocumentListener implements DocumentListener {
public void insertUpdate(DocumentEvent e) {
updateLog(e, "inserted into");
}
public void removeUpdate(DocumentEvent e) {
updateLog(e, "removed from");
}
public void changedUpdate(DocumentEvent e) {
}
public void updateLog(DocumentEvent e, String action) {
Document doc = (Document) e.getDocument();
int changeLength = e.getLength();
displayArea.append(changeLength + " character"
+ ((changeLength == 1) ? " " : "s ")
+ action + " text field; text length = "
+ doc.getLength() + "\n");
140
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
}
}
class MyTextActionListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
displayArea.append("Contents: "
+ textField.getText() + "\n");
displayArea.append("Selected: "
+ textField.getSelectedText() + "\n");
textField.select(0, 0);
}
}
public static void main(String args[]) {
DocumentEventDemo window = new DocumentEventDemo();
window.setTitle("DocumentEventDemo");
window.pack();
window.setVisible(true);
}
}
9.7.3
Dokument Editor
Mit Hilfe der Klasse JEditorPane bekommt man einen kleinen Editor, mit dem man strukturierte
Dokumente anzeigen und edieren kann.
Abbildung 9.28: EditorPaneDemo
import
import
import
import
import
import
import
java.awt.Dimension;
java.awt.BorderLayout;
java.awt.event.ActionEvent;
java.awt.event.ActionListener;
javax.swing.JFrame;
javax.swing.JTextField;
javax.swing.JPanel;
9.7. TEXT KOMPONENTEN
141
import javax.swing.JEditorPane;
import javax.swing.JScrollPane;
import javax.swing.JOptionPane;
import java.net.URL;
import java.net.MalformedURLException;
import java.io.IOException;
public class EditorPaneDemo extends JFrame {
private JEditorPane editorPane;
private JTextField textField;
public EditorPaneDemo() {
final String start = "http://www.vorlesungen.uos.de/informatik/c01/";
textField = new JTextField(start);
textField.addActionListener(new MyTextActionListener());
editorPane = new JEditorPane();
editorPane.setEditable(false);
JScrollPane editorScrollPane = new JScrollPane(editorPane);
editorScrollPane.setVerticalScrollBarPolicy(
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
editorScrollPane.setPreferredSize(new Dimension(250, 145));
editorScrollPane.setMinimumSize(new Dimension(10, 10));
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.add(textField, BorderLayout.NORTH);
panel.add(editorScrollPane, BorderLayout.CENTER);
setContentPane(panel);
showUrl(start);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void showUrl(String s) {
URL url;
try {
url = new URL(s);
} catch (MalformedURLException e) {
showErrorMsg("malformed URL \"" + s + "\"");
return;
}
try {
editorPane.setPage(url);
} catch (IOException e) {
showErrorMsg("IO exception while reading URL \"" + s + "\"");
}
}
public void showErrorMsg(String s) {
142
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
JOptionPane.showMessageDialog(editorPane, s, "Error",
JOptionPane.ERROR_MESSAGE);
}
class MyTextActionListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
showUrl(textField.getText());
}
}
public static void main(String[] args) {
JFrame window = new EditorPaneDemo();
window.setTitle("EditorPaneDemo");
window.pack();
window.setVisible(true);
}
}
• Das Program nutzt die Möglichkeit, einer Instanz der Klasse JEditorPane mit Hilfe der
setPage Methode einen URL zu geben. Das JEditorPane holt sich darauf das HTMLDokument und erzeugt ein neues HTMLDocument.
• Offenbar ist die Implementation sogar in der Lage, automatisch weitere eingebettete Dokumente (z.B. Bilder) zu laden.
9.7.4
Edieren von Dokumenten
Das folgende etwas längere Beispiel demonstriert, wie ein einfacher Dokumenten-Editor realisiert
werden kann.
Abbildung 9.29: TextComponentDemo
import
import
import
import
import
import
import
import
java.util.Hashtable;
java.awt.Color;
java.awt.Insets;
java.awt.Dimension;
java.awt.Event;
java.awt.BorderLayout;
java.awt.event.KeyEvent;
java.awt.event.ActionEvent;
9.7. TEXT KOMPONENTEN
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
javax.swing.JFrame;
javax.swing.JPanel;
javax.swing.JScrollPane;
javax.swing.JMenu;
javax.swing.JMenuBar;
javax.swing.JTextPane;
javax.swing.KeyStroke;
javax.swing.Action;
javax.swing.AbstractAction;
javax.swing.text.SimpleAttributeSet;
javax.swing.text.StyleConstants;
javax.swing.text.StyledDocument;
javax.swing.text.StyledEditorKit;
javax.swing.text.DefaultEditorKit;
javax.swing.text.DefaultStyledDocument;
javax.swing.text.Keymap;
javax.swing.text.JTextComponent;
javax.swing.text.BadLocationException;
javax.swing.event.UndoableEditEvent;
javax.swing.event.UndoableEditListener;
javax.swing.undo.UndoManager;
javax.swing.undo.CannotUndoException;
javax.swing.undo.CannotRedoException;
public class TextComponentDemo extends JFrame {
JTextPane textPane;
StyledDocument doc;
private final String newline = "\n";
private Hashtable actions;
protected UndoAction undoAction;
protected RedoAction redoAction;
protected UndoManager undo = new UndoManager();
public TextComponentDemo() {
textPane = new JTextPane(doc);
textPane.setCaretPosition(0);
textPane.setMargin(new Insets(5,5,5,5));
JScrollPane scrollPane = new JScrollPane(textPane);
scrollPane.setPreferredSize(new Dimension(600, 200));
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.add(scrollPane, BorderLayout.CENTER);
setContentPane(contentPane);
createActionTable(textPane);
JMenu editMenu = createEditMenu();
JMenu styleMenu = createStyleMenu();
JMenuBar mb = new JMenuBar();
mb.add(editMenu);
mb.add(styleMenu);
setJMenuBar(mb);
143
144
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
addKeymapBindings();
doc = textPane.getStyledDocument();
initDocument();
doc.addUndoableEditListener(new UndoableListener());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
protected class UndoableListener implements UndoableEditListener {
public void undoableEditHappened(UndoableEditEvent e) {
undo.addEdit(e.getEdit());
undoAction.updateUndoState();
redoAction.updateRedoState();
}
}
protected void addKeymapBindings() {
Keymap keymap = JTextComponent.addKeymap("MyEmacsBindings",
textPane.getKeymap());
Action action = getActionByName(DefaultEditorKit.backwardAction);
KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_B, Event.CTRL_MASK);
keymap.addActionForKeyStroke(key, action);
action = getActionByName(DefaultEditorKit.forwardAction);
key = KeyStroke.getKeyStroke(KeyEvent.VK_F, Event.CTRL_MASK);
keymap.addActionForKeyStroke(key, action);
action = getActionByName(DefaultEditorKit.upAction);
key = KeyStroke.getKeyStroke(KeyEvent.VK_P, Event.CTRL_MASK);
keymap.addActionForKeyStroke(key, action);
action = getActionByName(DefaultEditorKit.downAction);
key = KeyStroke.getKeyStroke(KeyEvent.VK_N, Event.CTRL_MASK);
keymap.addActionForKeyStroke(key, action);
textPane.setKeymap(keymap);
}
protected JMenu createEditMenu() {
JMenu menu = new JMenu("Edit");
undoAction = new UndoAction();
menu.add(undoAction);
redoAction = new RedoAction();
menu.add(redoAction);
menu.addSeparator();
menu.add(getActionByName(DefaultEditorKit.cutAction));
menu.add(getActionByName(DefaultEditorKit.copyAction));
menu.add(getActionByName(DefaultEditorKit.pasteAction));
9.7. TEXT KOMPONENTEN
145
menu.addSeparator();
menu.add(getActionByName(DefaultEditorKit.selectAllAction));
return menu;
}
protected JMenu createStyleMenu() {
JMenu menu = new JMenu("Style");
Action action = new StyledEditorKit.BoldAction();
action.putValue(Action.NAME, "Bold");
menu.add(action);
action = new StyledEditorKit.ItalicAction();
action.putValue(Action.NAME, "Italic");
menu.add(action);
action = new StyledEditorKit.UnderlineAction();
action.putValue(Action.NAME, "Underline");
menu.add(action);
menu.addSeparator();
menu.add(new StyledEditorKit.FontSizeAction("12", 12));
menu.add(new StyledEditorKit.FontSizeAction("14", 14));
menu.add(new StyledEditorKit.FontSizeAction("18", 18));
menu.addSeparator();
menu.add(new StyledEditorKit.FontFamilyAction("Serif", "Serif"));
menu.add(new StyledEditorKit.FontFamilyAction("SansSerif", "SansSerif"));
menu.addSeparator();
menu.add(new
menu.add(new
menu.add(new
menu.add(new
StyledEditorKit.ForegroundAction("Red", Color.red));
StyledEditorKit.ForegroundAction("Green", Color.green));
StyledEditorKit.ForegroundAction("Blue", Color.blue));
StyledEditorKit.ForegroundAction("Black", Color.black));
return menu;
}
protected void initDocument() {
String initString[] =
{ "Use the mouse to place the caret.",
"Use the edit menu to cut, copy, paste, and select text.",
"Also to undo and redo changes.",
"Use the style menu to change the style of the text.",
"Use these emacs key bindings to move the caret:",
"ctrl-f, ctrl-b, ctrl-n, ctrl-p." };
SimpleAttributeSet[] attrs = initAttributes(initString.length);
try {
for (int i = 0; i < initString.length; i ++) {
doc.insertString(doc.getLength(), initString[i] + newline,
146
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
attrs[i]);
}
} catch (BadLocationException ble) {
System.err.println("Couldn’t insert initial text.");
}
}
protected SimpleAttributeSet[] initAttributes(int length) {
SimpleAttributeSet[] attrs = new SimpleAttributeSet[length];
attrs[0] = new SimpleAttributeSet();
StyleConstants.setFontFamily(attrs[0], "SansSerif");
StyleConstants.setFontSize(attrs[0], 16);
attrs[1] = new SimpleAttributeSet(attrs[0]);
StyleConstants.setBold(attrs[1], true);
attrs[2] = new SimpleAttributeSet(attrs[0]);
StyleConstants.setItalic(attrs[2], true);
attrs[3] = new SimpleAttributeSet(attrs[0]);
StyleConstants.setFontSize(attrs[3], 20);
attrs[4] = new SimpleAttributeSet(attrs[0]);
StyleConstants.setFontSize(attrs[4], 12);
attrs[5] = new SimpleAttributeSet(attrs[0]);
StyleConstants.setForeground(attrs[5], Color.red);
return attrs;
}
private void createActionTable(JTextComponent textComponent) {
actions = new Hashtable();
Action[] actionsArray = textComponent.getActions();
for (int i = 0; i < actionsArray.length; i++) {
Action a = actionsArray[i];
actions.put(a.getValue(Action.NAME), a);
}
}
private Action getActionByName(String name) {
return (Action) (actions.get(name));
}
class UndoAction extends AbstractAction {
public UndoAction() {
super("Undo");
setEnabled(false);
}
public void actionPerformed(ActionEvent e) {
try {
undo.undo();
9.7. TEXT KOMPONENTEN
147
} catch (CannotUndoException ex) {
System.out.println("Unable to undo: " + ex);
ex.printStackTrace();
}
updateUndoState();
redoAction.updateRedoState();
}
protected void updateUndoState() {
if (undo.canUndo()) {
setEnabled(true);
putValue(Action.NAME, undo.getUndoPresentationName());
} else {
setEnabled(false);
putValue(Action.NAME, "Undo");
}
}
}
class RedoAction extends AbstractAction {
public RedoAction() {
super("Redo");
setEnabled(false);
}
public void actionPerformed(ActionEvent e) {
try {
undo.redo();
} catch (CannotRedoException ex) {
System.out.println("Unable to redo: " + ex);
ex.printStackTrace();
}
updateRedoState();
undoAction.updateUndoState();
}
protected void updateRedoState() {
if (undo.canRedo()) {
setEnabled(true);
putValue(Action.NAME, undo.getRedoPresentationName());
} else {
setEnabled(false);
putValue(Action.NAME, "Redo");
}
}
}
public static void main(String[] args) {
TextComponentDemo window = new TextComponentDemo();
window.setTitle("TextComponentDemo");
window.pack();
window.setVisible(true);
}
}
148
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
• Zum Edieren von Dokumenten werden passende Actions von sogenannten EditorKits
bereitgestellt.
• Die Aktionen können benutzt werden, um entsprechende Menüs zu erzeugen.
• Die Aktionen können durch die Definition von Keymaps an spezielle Eingabezeichen gebunden werden.
• Zur Realisierung von undo/redo Funktionen stellt Swing einen UndoManager bereit, der
die notwendigen Zustandsinformationen verwaltet.
• Die Implementation von undo/redo Aktionen kapselt diese neuen Aktionen.
• Mit Hilfe des UndoableEditListener wird der UndoManager mit Informationen versorgt und es werden die undo/redo Aktionen auf den aktuellen Zustand gebracht.
9.8 Dateidialoge
Die Klasse JFileChooser realisiert Dialoge, mit denen ein Programm einen Benutzer nach Dateien fragen kann.
Abbildung 9.30: FileChooserDemo
import
import
import
import
import
import
import
import
import
import
import
import
import
import
java.io.File;
java.awt.Insets;
java.awt.BorderLayout;
java.awt.event.KeyEvent;
java.awt.event.ActionEvent;
java.awt.event.ActionListener;
javax.swing.JFrame;
javax.swing.JPanel;
javax.swing.JMenu;
javax.swing.JMenuBar;
javax.swing.JMenuItem;
javax.swing.JTextArea;
javax.swing.JScrollPane;
javax.swing.JFileChooser;
9.8. DATEIDIALOGE
import javax.swing.filechooser.*;
public class FileChooserDemo extends JFrame {
private JFileChooser fc;
private JTextArea log;
private JMenuItem saveMenuItem;
public FileChooserDemo() {
JMenuItem openMenuItem, exitMenuItem;
log = new JTextArea(5,20);
log.setMargin(new Insets(5,5,5,5));
log.setEditable(false);
JScrollPane logScrollPane = new JScrollPane(log);
fc = new JFileChooser();
fc.addChoosableFileFilter(new TextFilter());
JMenuBar menuBar = new JMenuBar();
setJMenuBar(menuBar);
JMenu menu = new JMenu("File");
menu.setMnemonic(KeyEvent.VK_F);
menuBar.add(menu);
openMenuItem = new JMenuItem("Open...");
openMenuItem.setMnemonic(KeyEvent.VK_O);
openMenuItem.addActionListener(new OpenListener());
menu.add(openMenuItem);
saveMenuItem = new JMenuItem("Save As...");
saveMenuItem.setMnemonic(KeyEvent.VK_C);
saveMenuItem.setEnabled(false);
saveMenuItem.addActionListener(new SaveListener());
menu.add(saveMenuItem);
menu.addSeparator();
exitMenuItem = new JMenuItem("Exit");
exitMenuItem.setMnemonic(KeyEvent.VK_E);
exitMenuItem.addActionListener(new ExitListener());
menu.add(exitMenuItem);
getContentPane().add(logScrollPane, BorderLayout.CENTER);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
class TextFilter extends FileFilter {
public boolean accept(File f) {
if (f.isDirectory()) {
return true;
}
String extension = getExtension(f);
149
150
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
if (extension != null) {
return (extension.equals("txt"));
}
return false;
}
public String getExtension(File f) {
String ext = null;
String s = f.getName();
int i = s.lastIndexOf(’.’);
if (i > 0 && i < s.length() - 1) {
ext = s.substring(i+1).toLowerCase();
}
return ext;
}
public String getDescription() {
return "Text Files (*.txt)";
}
}
class OpenListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
int status = fc.showOpenDialog(FileChooserDemo.this);
if (status == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
log.append("Opening: " + file.getName() + ".\n");
saveMenuItem.setEnabled(true);
} else {
log.append("Open command cancelled by user.\n");
}
}
}
class SaveListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
int status = fc.showSaveDialog(FileChooserDemo.this);
if (status == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
log.append("Saving: " + file.getName() + ".\n");
} else {
log.append("Save command cancelled by user.\n");
}
}
}
class ExitListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
System.exit(0);
}
}
public static void main(String[] args) {
JFrame window = new FileChooserDemo();
151
9.9. BÄUME
window.setTitle("FileChooserDemo");
window.pack();
window.setVisible(true);
}
}
• Die Klasse TextFilter implementiert einen eigenen Filter, mit dem die Menge der Dateien eingeschränkt wird.
• Der Dialog zur Dateiauswahl ist modal.
• Es gibt verschiedene Möglichkeiten, die Erscheinung des Dialogs (z.B. Beschriftung der
Schaltflächen und verwendete Icons) anzupassen.
9.9 Bäume
Die Klasse JTree stellt die Funktionalität bereit, Baumstrukturen darzustellen. Ein Modell für
die Klasse JTree muß das Interface TreeModel implementieren. Eine Standardimplementation
wird durch die Klasse DefaultTreeModel zur Verfügung gestellt.
Abbildung 9.31: TreeDemo
import
import
import
import
import
import
import
import
import
import
import
java.awt.Dimension;
java.awt.BorderLayout;
javax.swing.JTree;
javax.swing.JFrame;
javax.swing.JTextArea;
javax.swing.JScrollPane;
javax.swing.JSplitPane;
javax.swing.tree.DefaultMutableTreeNode;
javax.swing.tree.TreeSelectionModel;
javax.swing.event.TreeSelectionEvent;
javax.swing.event.TreeSelectionListener;
public class TreeDemo extends JFrame {
private JTree tree;
152
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
private JTextArea text;
public TreeDemo() {
DefaultMutableTreeNode top
= new DefaultMutableTreeNode(new Entity
(1, "chassis", "7206VXR chassis Hw Serial#: 12345"));
createNodes(top);
tree = new JTree(top);
tree.getSelectionModel().setSelectionMode
(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.addTreeSelectionListener(new TreeListener());
tree.putClientProperty("JTree.lineStyle", "Angled");
final JScrollPane treeView = new JScrollPane(tree);
text = new JTextArea();
text.setEditable(false);
JScrollPane textView = new JScrollPane(text);
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
splitPane.setTopComponent(treeView);
splitPane.setBottomComponent(textView);
Dimension minimumSize = new Dimension(100, 50);
textView.setMinimumSize(minimumSize);
treeView.setMinimumSize(minimumSize);
splitPane.setDividerLocation(200);
splitPane.setPreferredSize(new Dimension(500, 300));
getContentPane().add(splitPane, BorderLayout.CENTER);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private class TreeListener implements TreeSelectionListener {
public void valueChanged(TreeSelectionEvent e) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)
tree.getLastSelectedPathComponent();
if (node == null) return;
Entity entity = (Entity) node.getUserObject();
text.setText("Index:\t" + entity.index + "\n" +
"Type:\t" + entity.type + "\n" +
"Descr:\t" + entity.descr + "\n");
}
}
private class Entity {
public String descr;
public String type;
public int index;
public Entity(int entIndex, String entType, String entDescr) {
descr = entDescr;
153
9.9. BÄUME
type = entType;
index = entIndex;
}
public String toString() {
return descr;
}
}
private void createNodes(DefaultMutableTreeNode top) {
DefaultMutableTreeNode lev1 = null,
lev2 = null, lev2_1 = null, lev2_1_1 = null,
lev3 = null, lev3_1 = null, lev3_1_1 = null, lev3_1_2 = null;
lev1 = new DefaultMutableTreeNode(new Entity
(2, "module", "NPE 3000 Card, Hw Serial#: 12345, Hw Revision: D"));
top.add(lev1);
lev2 = new DefaultMutableTreeNode(new Entity
(3, "container", "Chassis Slot"));
top.add(lev2);
lev2_1 = new DefaultMutableTreeNode(new Entity
(4, "module", "I/O FastEthernet (TX-ISL)"));
lev2.add(lev2_1);
lev2_1_1 = new DefaultMutableTreeNode(new Entity
(5, "port", "DEC21140A"));
lev2_1.add(lev2_1_1);
lev3 = new DefaultMutableTreeNode(new Entity
(6, "container", "Chassis Slot"));
top.add(lev3);
lev3_1 = new DefaultMutableTreeNode(new Entity
(7, "module", "2 Port Fast Ethernet/ISL 100BaseTX Port Adapter"));
lev3.add(lev3_1);
lev3_1_1 = new DefaultMutableTreeNode(new Entity
(8, "port", "AmdFE"));
lev3_1.add(lev3_1_1);
lev3_1_2 = new DefaultMutableTreeNode(new Entity
(9, "port", "AmdFE"));
lev3_1.add(lev3_1_2);
}
public static void main(String[] args) {
JFrame window = new TreeDemo();
window.setTitle("TreeDemo");
window.pack();
window.setVisible(true);
}
}
154
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
9.10 Tabellen
Die Klasse JTable realisiert eine Komponente, mit der Tabellen dargestellt und modifiziert werden können. Das Interface TableModel definiert wiederum eine Schnittstelle für das Modell, das
von einer Instanz der Klasse JTable dargestellt wird. Eine abstrakte Implementation des Interface
stellt die Klasse AbstractTableModel bereit.
Abbildung 9.32: SimpleTableDemo
Import
import
import
import
import
import
java.awt.Dimension;
java.awt.BorderLayout;
javax.swing.JTable;
javax.swing.JScrollPane;
javax.swing.JPanel;
javax.swing.JFrame;
public class SimpleTableDemo extends JFrame {
public SimpleTableDemo() {
Object[][] data = {
{"Mary", "Campione",
"Snowboarding", new Integer(5), new Boolean(false)},
{"Alison", "Huml",
"Rowing", new Integer(3), new Boolean(true)},
{"Kathy", "Walrath",
"Chasing toddlers", new Integer(2), new Boolean(false)},
{"Mark", "Andrews",
"Speed reading", new Integer(20), new Boolean(true)},
{"Angela", "Lih",
"Teaching high school", new Integer(4), new Boolean(false)}
};
String[] columnNames = {"First Name",
"Last Name",
"Sport",
"# of Years",
"Vegetarian"};
JTable table = new JTable(data, columnNames);
table.setPreferredScrollableViewportSize(new Dimension(500, 70));
JScrollPane scrollPane = new JScrollPane(table);
getContentPane().add(scrollPane, BorderLayout.CENTER);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
SimpleTableDemo window = new SimpleTableDemo();
155
9.10. TABELLEN
window.setTitle("SimpleTableDemo");
window.pack();
window.setVisible(true);
}
}
• Spalten lassen sich vertauschen und die Breiten der Spalten lassen sich interaktiv anpassen.
• Alle Zellen besitzen zunächst dieselbe Breite.
• Die Texte in den Zellen werden geeignet abgekürzt.
• Die Zellen sind alle einzeln edierbar.
• Offensichtlich spielt der Datentyp keine Rolle, da einfach die textuelle Darstellung (toString)
benutzt wird.
• Entsprechend erscheinen die Zahlen linksbündig statt rechtsbündig.
Mehr Kontrolle über die Darstellung bekommt man, indem man selbst eine eigene Klasse von der
Klasse AbstractTableModel ableitet und dann als Modell benutzt.
Abbildung 9.33: TableDemo
import
import
import
import
import
import
import
import
import
java.awt.Dimension;
java.awt.Component;
java.awt.BorderLayout;
javax.swing.JTable;
javax.swing.JScrollPane;
javax.swing.JFrame;
javax.swing.table.AbstractTableModel;
javax.swing.table.TableColumn;
javax.swing.table.TableCellRenderer;
public class TableDemo extends JFrame {
public TableDemo() {
SportsTableModel model = new SportsTableModel();
JTable table = new JTable(model);
initColumnSizes(table, model);
table.setPreferredScrollableViewportSize(new Dimension(500, 70));
JScrollPane scrollPane = new JScrollPane(table);
getContentPane().add(scrollPane, BorderLayout.CENTER);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
class SportsTableModel extends AbstractTableModel {
156
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
final String[] columnNames = {"First Name",
"Last Name",
"Sport",
"# of Years",
"Vegetarian"};
final Object[][] data = {
{"Mary", "Campione",
"Snowboarding", new Integer(5), new Boolean(false)},
{"Alison", "Huml",
"Rowing", new Integer(3), new Boolean(true)},
{"Kathy", "Walrath",
"Chasing toddlers", new Integer(2), new Boolean(false)},
{"Mark", "Andrews",
"Speed reading", new Integer(20), new Boolean(true)},
{"Angela", "Lih",
"Teaching high school", new Integer(4), new Boolean(false)}
};
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
return data.length;
}
public String getColumnName(int col) {
return columnNames[col];
}
public Object getValueAt(int row, int col) {
return data[row][col];
}
public Class getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
public boolean isCellEditable(int row, int col) {
return (col > 1);
}
public Object getLongestColumnValue(int col) {
Object longest = null;
for (int i = 0; i < getRowCount(); i++) {
Object val = getValueAt(i, col);
if ((longest == null)
|| (val.toString().length()
> longest.toString().length())) {
longest = val;
}
}
return longest;
}
9.10. TABELLEN
157
public void setValueAt(Object value, int row, int col) {
data[row][col] = value;
fireTableCellUpdated(row, col);
}
}
private void initColumnSizes(JTable table, SportsTableModel model) {
Component comp = null;
int headerWidth = 0;
int cellWidth = 0;
TableCellRenderer renderer;
for (int i = 0; i < model.getColumnCount(); i++) {
TableColumn column = table.getColumnModel().getColumn(i);
renderer = table.getTableHeader().getDefaultRenderer();
comp = renderer.getTableCellRendererComponent(
null, column.getHeaderValue(),
false, false, 0, 0);
headerWidth = comp.getPreferredSize().width;
renderer = table.getDefaultRenderer(model.getColumnClass(i));
comp = renderer.getTableCellRendererComponent(
table, model.getLongestColumnValue(i),
false, false, 0, i);
cellWidth = comp.getPreferredSize().width;
column.setPreferredWidth(Math.max(headerWidth, cellWidth));
}
}
public static void main(String[] args) {
TableDemo window = new TableDemo();
window.setTitle("TableDemo");
window.pack();
window.setVisible(true);
}
}
• Die Methode getColumnClass wird offensichtlich benutzt, um abhängig vom Datentyp
der Spalte eine passende Darstellung zu wählen.
• Eine Vertauschung der Spalten durch den Benutzer wirkt sich nicht auf die Numerierung der
Spalten im Modell aus.
• Die fireTableCellUpdated Methode wird benutzt, um nach Änderungen passende Ereignisse zu erzeugen.
• Die Ereignisse können mit Hilfe eines TableModelListener eingefangen werden.
• Spezielle Hilfsklassen sind für die Darstellung der Zellen verantwortlich. Die allgemeine
Schnittstelle ist im Interface TableCellRenderer festgelegt.
• Andere Hilfsklassen sind für das Edieren der Zellen verantwortlich. Die allgemeine Schnittstelle ist im Interface TableCellEditor definiert.
158
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
9.11 Zweidimensionale Graphik
Die Java 2D Programmierschnittstelle erlaubt die Darstellung von zwei-dimensionalen Graphiken,
Texten und Bildern. Die Schnittstelle ist eine Erweiterung der Graphikklassen des AWT. Die Java
2D Schnittstelle trennt die Beschreibung von graphischen Objekten wie z.B. Linien von der eigentlichen Darstellung (rendering). Dadurch ist möglich, Graphiken durch die Verwendung verschiedener
Darstellungsalgorithmen (renderer) auf unterschiedlichen Geräten und Druckern zu reproduzieren.
Object
Graphics
Graphics2D
Abbildung 9.34: Java 2D Graphik-Klassen
Java 2D unterscheidet zwei Koordinatensysteme:
• User Space: Das Koordinatensystem in dem ein Java Programm graphische Objekte erzeugt
und verwaltet. Dieses Koordinatensystem ist geräteunabhängig und hat seinen Ursprung (der
Punkt (0,0)) in der oberen linken Ecke.
• Device Space: Das Koordinatensystem eines konkreten Ausgabegeräts (z.B. Drucker, Bildschirm). Die Abbildung vom geräteunabhängigen Koordinatensystem auf das geräteabhängige Koordinatensystem erfolgt automatisch beim rendering.
Bei der Transformation auf ein Ausgabegerät kann die Darstellungsweise durch einen sogenannten
rendering context beeinflusst werden:
• pen style: Wird auf den Umriß eines graphischen Objekts angewendet und erlaubt die Darstellung von Linien in unterschiedlicher Breite, mit verschiedenen Strichmustern und mit
unterschiedlichen Darstellungen der Enden.
• fill style: Wird auf die Fläche eines graphischen Objekts angewendet. Flächen können mit
einfachen Farben, mit verlaufenden Farben oder Mustern gefüllt werden.
• composition style: Bestimmt das Verhalten, wenn sich Objekte überlappen.
• transform: Bestimmt die Abbildung der Koordinaten auf das Koordinatensystem des Darstellungsgeräts und ermöglicht die Translation, Rotation, Skalierung.
9.11. ZWEIDIMENSIONALE GRAPHIK
159
• clip: Beschränkt die Fläche die neu dargestellt wird.
• font: Texte werden mit Hilfe des Fonts zu darstellbaren Glyphs“ konvertiert.
”
• rendering hints: Weitere Parameter, die insbesondere den trade-off zwischen Geschwindigkeit und Qualität beeinflussen.
Der rendering context einer Instanz der Klasse Graphics2D kann durch entsprechende Methoden (setStroke, setPaint, setComposite, setTransform, setClip, setFont,
setRenderingHints) manipuliert werden.
Swing Komponenten stellen sich generell selbst dar. Dazu dienen die repaint Methoden, die
in der Regel automatisch aufgerufen werden wenn ein graphisches Objekt dargestellt werden muß
oder wenn sich der Zustand eines graphischen Objekts verändert hat. Ein replaint ist rekursiv
und führt automatisch dazu, daß alle enhaltenen Objekte ebenfalls ein repaint ausführen. Die
eigentliche graphische Darstellung eines graphischen Objekts erfolgt in der paintComponent
Methode bzw. in der grundlegenden paint Methode.
Abbildung 9.35: SwingPaintDemo
import
import
import
import
import
import
java.awt.Color;
java.awt.Container;
java.awt.Graphics;
java.awt.Dimension;
javax.swing.JFrame;
javax.swing.JPanel;
class SwingPaintDemo extends JPanel {
public SwingPaintDemo() {
super();
setOpaque(false);
}
public Dimension getPreferredSize() {
// Figure out what the layout manager needs and
// then add 100 to the largest of the dimensions
// in order to enforce a ’round’ bullseye
Dimension layoutSize = super.getPreferredSize();
int max = Math.max(layoutSize.width,layoutSize.height);
return new Dimension(max+100,max+100);
}
protected void paintComponent(Graphics g) {
Dimension size = getSize();
int x = 0;
160
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
int y = 0;
int i = 0;
while (x < size.width && y < size.height) {
g.setColor((i%2 == 0) ? Color.red : Color.white);
g.fillOval(x, y, size.width-(2*x), size.height-(2*y));
x+=10; y+=10; i++;
}
}
public static void main(String[] args) {
JFrame f = new JFrame("SwingPaintDemo");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container panel = new SwingPaintDemo();
f.getContentPane().add(panel);
f.pack();
f.show();
}
}
9.11.1
Primitive geometrische Formen (Shapes)
Die graphischen Primitive sind in dem Paket java.awt.geom definiert. Das Interface Shape
definiert eine allgemeine geometrische Form.
Arc2D.Double
Arc2D
Arc2D.Float
«interface»
Shape
Ellipse2D.Double
Ellipse2D
Ellipse2D.Float
Rectangle
RectangularShape
Rectangle2D
Rectangle2D.Double
Rectangle2D.Float
Object
Area
RoundRectangle2D.Double
RoundRectangle2D
RoundRectangle2D.Float
Polygon
GeneralPath
Line2D.Double
Line2D
Line2D.Float
QuadCurve2D.Double
QuadCurve2D
QuadCurve2D.Float
CubicCurve2D.Double
CubicCurve2D
CubicCurve2D.Float
Abbildung 9.36: Java 2D Shapes
9.11. ZWEIDIMENSIONALE GRAPHIK
161
Das folgende Programm demonstriert wie diese Shapes dargestellt werden können und wie die
Darstellungsweise beeinflußt werden kann.
Abbildung 9.37: ShapesDemo
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
java.awt.Color;
java.awt.Dimension;
java.awt.Graphics;
java.awt.Graphics2D;
java.awt.BasicStroke;
java.awt.Font;
java.awt.FontMetrics;
java.awt.RenderingHints;
java.awt.GradientPaint;
java.awt.Container;
java.awt.geom.Arc2D;
java.awt.geom.Line2D;
java.awt.geom.Rectangle2D;
java.awt.geom.RoundRectangle2D;
java.awt.geom.Ellipse2D;
java.awt.geom.GeneralPath;
javax.swing.JFrame;
javax.swing.JPanel;
public class ShapesDemo extends JPanel {
final static int maxCharHeight = 15;
final static int minFontSize = 6;
final
final
final
final
static
static
static
static
Color
Color
Color
Color
bg = Color.white;
fg = Color.black;
red = Color.red;
white = Color.white;
final static BasicStroke stroke = new BasicStroke(2.0f);
final static BasicStroke wideStroke = new BasicStroke(8.0f);
final static float dash1[] = {10.0f};
final static BasicStroke dashed
= new BasicStroke(1.0f,
162
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
10.0f, dash1, 0.0f);
Dimension totalSize;
FontMetrics fontMetrics;
public ShapesDemo() {
super();
setBackground(bg);
setForeground(fg);
}
public FontMetrics pickFont(Graphics2D g2, String longString, int xSpace) {
boolean fontFits = false;
Font font = g2.getFont();
FontMetrics fontMetrics = g2.getFontMetrics();
int size = font.getSize();
String name = font.getName();
int style = font.getStyle();
while (! fontFits) {
if ( (fontMetrics.getHeight() <= maxCharHeight)
&& (fontMetrics.stringWidth(longString) <= xSpace) ) {
fontFits = true;
} else {
if ( size <= minFontSize ) {
fontFits = true;
} else {
g2.setFont(font = new Font(name, style, --size));
fontMetrics = g2.getFontMetrics();
}
}
}
return fontMetrics;
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Dimension d = getSize();
int gridWidth = d.width / 6;
int gridHeight = d.height / 2;
fontMetrics = pickFont(g2, "Filled and Stroked GeneralPath",
gridWidth);
Color fg3D = Color.lightGray;
g2.setPaint(fg3D);
g2.draw3DRect(0, 0, d.width - 1, d.height - 1, true);
g2.draw3DRect(3, 3, d.width - 7, d.height - 7, false);
g2.setPaint(fg);
9.11. ZWEIDIMENSIONALE GRAPHIK
int
int
int
int
int
163
x = 5;
y = 7;
rectWidth = gridWidth - 2*x;
stringY = gridHeight - 3 - fontMetrics.getDescent();
rectHeight = stringY - fontMetrics.getMaxAscent() - y - 2;
// draw Line2D.Double
g2.draw(new Line2D.Double(x, y+rectHeight-1, x + rectWidth, y));
g2.drawString("Line2D", x, stringY);
x += gridWidth;
// draw Rectangle2D.Double
g2.setStroke(stroke);
g2.draw(new Rectangle2D.Double(x, y, rectWidth, rectHeight));
g2.drawString("Rectangle2D", x, stringY);
x += gridWidth;
// draw RoundRectangle2D.Double
g2.setStroke(dashed);
g2.draw(new RoundRectangle2D.Double(x, y, rectWidth,
rectHeight, 10, 10));
g2.drawString("RoundRectangle2D", x, stringY);
x += gridWidth;
// draw Arc2D.Double
g2.setStroke(wideStroke);
g2.draw(new Arc2D.Double(x, y, rectWidth, rectHeight, 90,
135, Arc2D.OPEN));
g2.drawString("Arc2D", x, stringY);
x += gridWidth;
// draw Ellipse2D.Double
g2.setStroke(stroke);
g2.draw(new Ellipse2D.Double(x, y, rectWidth, rectHeight));
g2.drawString("Ellipse2D", x, stringY);
x += gridWidth;
// draw GeneralPath (polygon)
int x1Points[] = {x, x+rectWidth, x, x+rectWidth};
int y1Points[] = {y, y+rectHeight, y+rectHeight, y};
GeneralPath polygon = new GeneralPath(GeneralPath.WIND_EVEN_ODD,
x1Points.length);
polygon.moveTo(x1Points[0], y1Points[0]);
for (int index = 1; index < x1Points.length; index++) {
polygon.lineTo(x1Points[index], y1Points[index]);
};
polygon.closePath();
g2.draw(polygon);
g2.drawString("GeneralPath (polygon)", x, stringY);
// NEW ROW
x = 5;
164
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
y += gridHeight;
stringY += gridHeight;
// draw GeneralPath (polyline)
int x2Points[] = {x, x+rectWidth, x, x+rectWidth};
int y2Points[] = {y, y+rectHeight, y+rectHeight, y};
GeneralPath polyline = new GeneralPath(GeneralPath.WIND_EVEN_ODD,
x2Points.length);
polyline.moveTo (x2Points[0], y2Points[0]);
for (int index = 1; index < x2Points.length; index++) {
polyline.lineTo(x2Points[index], y2Points[index]);
};
g2.draw(polyline);
g2.drawString("GeneralPath (polyline)", x, stringY);
x += gridWidth;
// fill Rectangle2D.Double (red)
g2.setPaint(red);
g2.fill(new Rectangle2D.Double(x, y, rectWidth, rectHeight));
g2.setPaint(fg);
g2.drawString("Filled Rectangle2D", x, stringY);
x += gridWidth;
// fill RoundRectangle2D.Double
GradientPaint redtowhite;
redtowhite = new GradientPaint(x, y, red, x+rectWidth, y, white);
g2.setPaint(redtowhite);
g2.fill(new RoundRectangle2D.Double(x, y, rectWidth,
rectHeight, 10, 10));
g2.setPaint(fg);
g2.drawString("Filled RoundRectangle2D", x, stringY);
x += gridWidth;
// fill Arc2D
g2.setPaint(red);
g2.fill(new Arc2D.Double(x, y, rectWidth, rectHeight, 90,
135, Arc2D.OPEN));
g2.setPaint(fg);
g2.drawString("Filled Arc2D", x, stringY);
x += gridWidth;
// fill Ellipse2D.Double
redtowhite = new GradientPaint(x, y, red, x+rectWidth, y, white);
g2.setPaint(redtowhite);
g2.fill (new Ellipse2D.Double(x, y, rectWidth, rectHeight));
g2.setPaint(fg);
g2.drawString("Filled Ellipse2D", x, stringY);
x += gridWidth;
// fill and stroke GeneralPath
int x3Points[] = {x, x+rectWidth, x, x+rectWidth};
int y3Points[] = {y, y+rectHeight, y+rectHeight, y};
9.11. ZWEIDIMENSIONALE GRAPHIK
165
GeneralPath filledPolygon = new GeneralPath(GeneralPath.WIND_EVEN_ODD,
x3Points.length);
filledPolygon.moveTo(x3Points[0], y3Points[0]);
for (int index = 1; index < x3Points.length; index++) {
filledPolygon.lineTo(x3Points[index], y3Points[index]);
};
filledPolygon.closePath();
g2.setPaint(red);
g2.fill(filledPolygon);
g2.setPaint(fg);
g2.draw(filledPolygon);
g2.drawString("Filled and Stroked GeneralPath", x, stringY);
}
public static void main(String s[]) {
JFrame f = new JFrame("ShapesDemo");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container panel = new ShapesDemo();
f.getContentPane().add(panel);
f.pack();
f.setSize(new Dimension(550,100));
f.show();
}
}
9.11.2
Beispiel: Animation springender Bälle
Die folgende Applikation zeigt ein paar springende Bälle, wobei sich die Farbverläufe auf den
Bällen stetig leicht ändert. Das Beispiel demonstriert, wie man selbst in einem Puffer eine beliebige
Graphik erstellen kann.
Abbildung 9.38: BallsDemo
Die Implementation besteht aus drei wesentlichen Klassen:
1. Die Klasse BallsDemo hat die Aufgabe, das Fenster und die Menüs aufzubauen und zu
verwalten.
2. Die Klasse BallsThread realisiert einen Thread, der periodisch die Darstellung der Bälle
anstößt und in der paint-Methode darstellt.
166
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
«interface»
Runnable
JPanel
0..*
+Ball(color:Color,name:String,bsize:int)
+getName(): String
+getRadius(): int
+setRadius(radius:int): void
-makeImages(bsize:int): void
-blend(fg:int,bg:int,fgfactor:float): int
+step(deltaT:long,w:int,h:int): void
bounces
BallsThread
Ball
1
+BallsThread()
+step(): void
+paint(g:Graphics): void
+start(): void
+stop(): void
1
+run(): void
1
BallsDemo
contains
+BallsDemo()
+start(): void
+stop(): void
+exit(): void
#makeFileMenu(menuBar:JMenuBar): void
#makeBallsMenu(menuBar:JMenuBar): void
#makeSizeMenu(menuBar:JMenuBar): void
Abbildung 9.39: UML Diagramm für die Animation springender Bälle
3. Die Klasse Ball repräsentiert einen Ball. Zu jedem Ball existieren mehrere Darstellungen
in leicht verschiedenen Farbverläufen.
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
java.awt.Color;
java.awt.Dimension;
java.awt.Graphics;
java.awt.Graphics2D;
java.awt.image.IndexColorModel;
java.awt.image.DataBufferByte;
java.awt.image.Raster;
java.awt.image.WritableRaster;
java.awt.event.ActionEvent;
java.awt.event.ActionListener;
java.awt.event.ItemEvent;
java.awt.event.ItemListener;
java.awt.event.WindowEvent;
java.awt.event.WindowAdapter;
java.awt.image.BufferedImage;
javax.swing.JMenuBar;
javax.swing.JMenu;
javax.swing.JMenuItem;
javax.swing.JCheckBoxMenuItem;
javax.swing.JRadioButtonMenuItem;
javax.swing.ButtonGroup;
javax.swing.JPanel;
javax.swing.JFrame;
/**
* The BallsDemo class demonstrates animated colored bouncing balls.
*/
public class BallsDemo extends JFrame {
private BallsThread bThread;
9.11. ZWEIDIMENSIONALE GRAPHIK
167
private JMenuItem stopItem;
private JMenuItem stepItem;
private JMenuItem startItem;
public BallsDemo() {
bThread = new BallsThread();
getContentPane().add(bThread);
JMenuBar menuBar = new JMenuBar();
setJMenuBar(menuBar);
makeFileMenu(menuBar);
makeBallsMenu(menuBar);
makeSizeMenu(menuBar);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) { exit(); }
public void windowDeiconified(WindowEvent e) { start(); }
public void windowIconified(WindowEvent e) { stop(); }
});
}
public void start() {
bThread.start();
startItem.setEnabled(false);
stopItem.setEnabled(true);
stepItem.setEnabled(false);
}
public void stop() {
bThread.stop();
startItem.setEnabled(true);
stopItem.setEnabled(false);
stepItem.setEnabled(true);
}
public void exit() {
System.exit(0);
}
private void makeFileMenu(JMenuBar menuBar) {
JMenu menu = new JMenu("File");
menuBar.add(menu);
startItem = new JMenuItem("Start");
startItem.addActionListener(new StartStopListener(startItem));
menu.add(startItem);
stopItem = new JMenuItem("Stop");
stopItem.addActionListener(new StartStopListener(stopItem));
menu.add(stopItem);
stepItem = new JMenuItem("Step");
stepItem.addActionListener(new StartStopListener(stepItem));
168
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
menu.add(stepItem);
menu.addSeparator();
JMenuItem item = new JMenuItem("Exit");
item.addActionListener(new ExitListener());
menu.add(item);
}
private class StartStopListener implements ActionListener {
JMenuItem item;
public StartStopListener(JMenuItem item) {
this.item = item;
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == startItem) start();
if (e.getSource() == stopItem) stop();
if (e.getSource() == stepItem) {
bThread.step(bThread.getSize());
bThread.repaint();
}
}
}
private class ExitListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
exit();
}
}
private void makeBallsMenu(JMenuBar menuBar) {
JCheckBoxMenuItem item;
JMenu menu = new JMenu("Balls");
menuBar.add(menu);
for (int i = 0; i < bThread.balls.length; i++) {
item = new JCheckBoxMenuItem(bThread.balls[i].getName());
item.addItemListener(new BallsListener(bThread.balls[i]));
item.setState(true);
menu.add(item);
}
}
private class BallsListener implements ItemListener {
private Ball ball;
public BallsListener(Ball ball) {
this.ball = ball;
}
public void itemStateChanged(ItemEvent e) {
ball.isSelected = (e.getStateChange() == ItemEvent.SELECTED);
}
}
9.11. ZWEIDIMENSIONALE GRAPHIK
169
private void makeSizeMenu(JMenuBar menuBar) {
JRadioButtonMenuItem item;
JMenu menu = new JMenu("Radius");
menuBar.add(menu);
ButtonGroup group = new ButtonGroup();
for (int i = 10; i < 90; i += 10) {
item = new JRadioButtonMenuItem(i + " pt");
item.addActionListener(new SizeListener(i));
if (i == bThread.balls[0].getRadius()) item.setSelected(true);
group.add(item);
menu.add(item);
}
}
private class SizeListener implements ActionListener {
private int size;
public SizeListener(int size) {
this.size = size;
}
public void actionPerformed(ActionEvent e) {
JRadioButtonMenuItem item = (JRadioButtonMenuItem) e.getSource();
for (int i = 0; i < bThread.balls.length; i++) {
bThread.balls[i].setRadius(size);
}
}
}
/**
* The BallsThread class performs the animation and painting.
*/
static class BallsThread extends JPanel implements Runnable {
private static Color colors[] =
{ Color.red, Color.orange, Color.yellow, Color.green,
Color.blue, Color.magenta, Color.gray };
private static String names[] =
{ "red", "orange", "yellow", "green", "blue", "magenta", "gray" };
protected Ball balls[] = new Ball[colors.length];
private Thread thread;
private BufferedImage bimg;
private long now, lasttime;
/**
* Creates a new ball object for each color in the colors array.
*/
public BallsThread() {
setBackground(Color.white);
for (int i = 0; i < colors.length; i++) {
balls[i] = new Ball(colors[i], names[i], 40);
}
}
170
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
/**
* Move the balls one step further and kick them randomly if
* they are not active enough anymore.
*/
public void step(Dimension d) {
if (lasttime == 0) {
lasttime = System.currentTimeMillis();
}
now = System.currentTimeMillis();
long delta = now - lasttime;
boolean active = false;
for (int i = 0; i < balls.length; i++) {
balls[i].step(delta, d.width, d.height);
if (Math.abs(balls[i].Vy) > .02
|| (balls[i].y + 2 * balls[i].getRadius() < d.height)) {
active = true;
}
}
if (! active) {
for (int i = 0; i < balls.length; i++) {
balls[i].Vx = (float) Math.random() / 4.0f - 0.125f;
balls[i].Vy = -(float) Math.random() / 4.0f - 0.2f;
}
}
}
/**
* Indirectly called via repaint() to update the image. This is
* implemented by creating an image buffer with its own graphics
* context and drawing all selected balls with their current image.
* Finally, the image buffer is displayed on the screen.
*/
public void paint(Graphics g) {
Dimension d = getSize();
BufferedImage bimg = (BufferedImage)
createImage(d.width, d.height);
Graphics2D g2 = bimg.createGraphics();
g2.setBackground(getBackground());
g2.clearRect(0, 0, d.width, d.height);
for (int i = 0; i < balls.length; i++) {
Ball b = balls[i];
if (b.imgs[b.index] != null && b.isSelected) {
g2.drawImage(b.imgs[b.index], (int) b.x, (int) b.y, this);
}
}
lasttime = now;
g2.dispose();
g.drawImage(bimg, 0, 0, this);
}
9.11. ZWEIDIMENSIONALE GRAPHIK
/**
* Create a new thread to draw the bouncing balls.
*/
public void start() {
thread = new Thread(this);
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
}
/**
* Stop the thread by setting the variable which references
* the thread to null.
*/
public synchronized void stop() {
thread = null;
}
/**
* Repaint the window every 10 milliseconds as long as the
* variable references this thread.
*/
public void run() {
Thread me = Thread.currentThread();
while (thread == me) {
step(getSize());
repaint();
try {
thread.sleep(10);
} catch (InterruptedException e) { break; }
}
thread = null;
}
}
/**
* The Ball class creates 5 ball images. Each ball image
* increasingly blends the color white with the specified color.
*/
static class Ball {
public float x, y;
public float Vx = 0.1f;
public float Vy = 0.05f;
final private int nImgs = 5;
public BufferedImage imgs[];
public int index = (int) (Math.random() * (nImgs-1));
static
static
static
static
static
private
private
private
private
private
final
final
final
final
final
float inelasticity = .96f;
float Ax = 0.0f;
float Ay = 0.0002f;
int UP = 0;
int DOWN = 1;
171
172
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
private
private
private
private
private
int indexDirection = UP;
final Color color;
final String name;
int radius;
boolean isSelected;
public Ball(Color color, String name, int radius) {
this.color = color;
this.name = name;
setRadius(radius);
}
public String getName() {
return name;
}
public int getRadius() {
return radius;
}
public void setRadius(int radius) {
makeImages(radius);
}
/**
* Manually compute the circle and the colored images
* for the ball.
*/
private void makeImages(int bsize) {
radius = bsize;
int R = bsize;
byte[] data = new byte[R * 2 * R * 2];
int maxr = 0;
for (int Y = 2 * R; --Y >= 0;) {
int x0 = (int) (Math.sqrt(R * R - (Y - R) * (Y - R)) + 0.5);
int p = Y * (R * 2) + R - x0;
for (int X = -x0; X < x0; X++) {
int x = X + 15;
int y = Y - R + 15;
int r = (int) (Math.sqrt(x * x + y * y) + 0.5);
if (r > maxr) {
maxr = r;
}
data[p++] = r <= 0 ? 1 : (byte) r;
}
}
imgs = new BufferedImage[nImgs];
int bg = 255;
byte red[] = new byte[256];
red[0] = (byte) bg;
byte green[] = new byte[256];
green[0] = (byte) bg;
173
9.11. ZWEIDIMENSIONALE GRAPHIK
byte blue[] = new byte[256];
blue[0] = (byte) bg;
// for each image, set its color...
for (int r = 0; r < imgs.length; r++) {
float b = 0.5f + (float) ((r+1f)/imgs.length/2f);
for (int i = maxr; i >= 1; --i) {
float d = (float) i / maxr;
red[i] = (byte) blend(blend(color.getRed(), 255, d), bg, b);
green[i] = (byte) blend(blend(color.getGreen(), 255, d), bg, b);
blue[i] = (byte) blend(blend(color.getBlue(), 255, d), bg, b);
}
IndexColorModel icm;
DataBufferByte dbb;
WritableRaster wr;
int bandOffsets[] = {0};
icm = new IndexColorModel(8, maxr + 1, red, green, blue, 0);
dbb = new DataBufferByte(data, data.length);
wr = Raster.createInterleavedRaster(dbb, R*2, R*2, R*2, 1,
bandOffsets,null);
imgs[r] = new BufferedImage(icm, wr,
icm.isAlphaPremultiplied(), null);
}
}
/**
* Compute the blending of foreground and background colors.
*/
private final int blend(int fg, int bg, float fgfactor) {
return (int) (bg + (fg - bg) * fgfactor);
}
/**
* Move the ball and bounce it off the window "walls".
* Select the next ball image by updating the image index.
*/
public void step(long deltaT, int w, int h) {
float jitter = (float) Math.random() * .01f - .005f;
int diameter = radius * 2;
x += Vx * deltaT + (Ax / 2.0)
y += Vy * deltaT + (Ay / 2.0)
if (x <= 0.0f) {
x = 0.0f;
Vx = -Vx * inelasticity +
}
if (x + diameter >= w) {
x = w - diameter;
Vx = -Vx * inelasticity +
}
if (y <= 0) {
* deltaT * deltaT;
* deltaT * deltaT;
jitter;
jitter;
174
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
y = 0;
Vy = -Vy * inelasticity + jitter;
}
if (y + diameter >= h) {
y = h - diameter;
Vx *= inelasticity;
Vy = -Vy * inelasticity + jitter;
}
Vy = Vy + Ay * deltaT;
Vx = Vx + Ax * deltaT;
if (indexDirection == UP) {
index++;
}
if (indexDirection == DOWN) {
--index;
}
if (index+1 == nImgs) {
indexDirection = DOWN;
}
if (index == 0) {
indexDirection = UP;
}
}
}
/**
* The usual entry point...
*/
public static void main(String argv[]) {
final BallsDemo window = new BallsDemo();
window.setTitle("BallsDemo");
window.pack();
window.setSize(new Dimension(400,300));
window.setVisible(true);
window.start();
}
}
• Ein ColorModel hat die Aufgaben, Pixelwerten Farbkomponenten (z.B. rot, grün, blau)
und eine Alpha-Komponente zuzuordnen. Die abgeleitete Klasse IndexColorModel unterstützt Pixelwerte, die einen Index in eine feste Farbtabelle darstellen.
• Ein Raster repräsentiert ein rechteckiges Feld von Pixeln. Die Daten werden in einem
DataBuffer verwaltet. Ein WritableRaster erlaubt das Schreiben von Pixel Eigenschaften im Raster.
• Der im Programm verwendete Konstruktor der Klasse BufferedImage nimmt eine Instanz eines ColorModels und ein WritableRaster entgegen und erzeugt daraus ein
darstellbares Image.
9.11. ZWEIDIMENSIONALE GRAPHIK
9.11.3
175
Beispiel: Santa Claus
Zum Abschluß noch ein kleiner Gruß von Santa Claus zur Weihnachtspause...
Abbildung 9.40: SantaClaus
import
import
import
import
import
import
import
java.awt.Color;
java.awt.Dimension;
java.awt.Graphics;
java.awt.Graphics2D;
javax.swing.JFrame;
javax.swing.JPanel;
javax.swing.ImageIcon;
public class SantaClaus extends JFrame {
private SantaClausThread scThread;
public SantaClaus() {
scThread = new SantaClausThread();
getContentPane().add(scThread);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void start() {
scThread.start();
}
public void stop() {
scThread.stop();
}
private class SantaClausThread extends JPanel implements Runnable {
private
private
private
private
private
private
private
final int num = 4;
ImageIcon imgs[];
Thread thread;
int index = 0;
int x = 42;
int y = 0;
int dx = 0;
176
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
public SantaClausThread() {
setBackground(Color.white);
imgs = new ImageIcon[num];
for (int i = 0; i < num; i++) {
imgs[i] = new ImageIcon("xmas" + i + ".gif");
}
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
ImageIcon i = imgs[index];
Dimension d = getSize();
Dimension c = new Dimension(i.getIconWidth(), i.getIconHeight());
g2.setBackground(getBackground());
g2.clearRect(0, 0, d.width, d.height);
if (y == d.height - c.height) {
if (x + c.width < 0 || x > d.width) {
x = (int) (Math.random() * (d.width - c.width));
y = 0;
dx = 0;
} else {
i.paintIcon(this, g2, x, y);
x += dx;
}
} else {
if (y + c.height < d.height) {
i.paintIcon(this, g2, x, y);
x += dx; y += 5;
index = (index + 1) % num;
} else {
y = d.height - c.height;
i.paintIcon(this, g2, x, y);
dx = ((x + c.width/2) > d.width/2) ? 5 : -5;
index = 0;
}
}
}
public void start() {
thread = new Thread(this);
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
}
public synchronized void stop() {
thread = null;
}
public void run() {
Thread me = Thread.currentThread();
while (thread == me) {
repaint();
try {
9.11. ZWEIDIMENSIONALE GRAPHIK
thread.sleep(80);
} catch (InterruptedException e) { break; }
}
thread = null;
}
}
public static void main(String argv[]) {
final SantaClaus window = new SantaClaus();
window.setTitle("Merry Christmas...");
window.pack();
window.setSize(new Dimension(400,300));
window.setVisible(true);
window.start();
}
}
177
178
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
Teil III
Software-Komponenten und Verteilte
Anwendungen
179
Kapitel 10
Verteilte Anwendungen
Fast alle modernen Anwendungen sind mehr oder weniger stark so konzipiert, dass Teile der Anwendungslogik, der Datenhaltung oder der Benutzungsschnittstelle auf verschiedenen vernetzten
Rechnern ablaufen können. Man erhofft sich durch die Verteilung insbesondere Vorteile hinsichtlich der Skalierbarkeit. Oftmals ist aber auch der einfache Wunsch nach Wiederverwendung von
existierenden spezialisierten Komponenten mit wohl definierten Schnittstellen ein Grund Anwendungen als verteilte Anwendungen zu realisieren.
10.1 Client/Server-Modell
Das Client/Server-Modell ist ein grundlegenden Kommunikationsmodell, bei dem die Kommunikationspartner in einem System entweder die Rolle des Clients oder des Servers einnehmen. Ein
Server verwaltet bestimmte Ressourcen während ein Client die Ressourcen nutzen möchte.
Client
Server
Access
Result
Abbildung 10.1: Client/Server-Modell
Der Kommunikationsablauf im Client/Sever-Modell besteht in der Regel aus zwei Nachrichten.
Während der Verarbeitung des Zugriffs ist im einfachsten Fall der Client blockiert.
Client/Sever-System lassen sich elegant mit der Hilfe von entfernten Proceduraufrufen (remote procedure calls, RPC) implementieren. Beim RPC wird eine lokale Funktion aufgerufen, die als StubFunktion den eigentlichen entfernten Proceduraufruf dem Programmierer weitestgehend transparent macht (bis auf die Fehlerbehandlung). Stub-Funktionen werden in der Regel automatisch erzeugt und serialisieren/deserialisieren Daten in ein zur Datenübertragung geeignetes Format (mars181
182
KAPITEL 10. VERTEILTE ANWENDUNGEN
halling). Außerdem können Stub-Funktionen das notwendige Binden an einen passenden Server
unterstützen.
Client
Client
Stub
Server
Stub
Server
Access
Send
Access
Result
Send
Result
Abbildung 10.2: RPC-Modell
Bei RPC-Systemen gibt es unterschiedliche Semantiken, was insbesondere das Verhalten bei Kommunikationsfehlern angeht:
• may-be: Es gibt keine Garantie, dass ein Aufruf auch wirklich ausgeführt wird.
• at-least-once: Der Aufruf wird mindestens einmal ausgeführt. In Fehlersituationen kann es
jedoch auch zur mehrfachen Ausführung kommen. Diese Semantik ist bei idempotenten
Diensten unproblematisch.
• at-most-once: Der Aufruf wird höchstens einmal ausgeführt.
• exactly-once: Der Aufruf wird genau einmal ausgeführt. Erfordert in der Regel ein Transaktionsmodell bzw. fehlertolerante Kommunikationsprimitive.
10.2
N-Tier Architekturen
Viele Anwendungen sind heutzutage als verteilte Anwendungen realisiert, bei denen nicht alle
Komponenten auf einem Rechensystem ausgeführt werden. Oftmals besitzen moderne Anwendungen auch Web-basierten Schnittstellen. Ein typisches Beispiel ist ein Bestellsystem mit einer online
Web-Schnittstelle.
Verteilte Anwendungen wie z.B. ein Bestellsystem bestehen in vielen Fällen aus vier verschiedenen
Elementen, die folgende Funktionen realisieren:
• Die Präsentationsschnittstelle realisiert den Dialog mit dem Benutzer und erzeugt Zugriffe
auf die Serverkomponenten.
• Die Zugriffschnittstelle realisiert die Schnittstelle, über die Präsentationsschnittstellen auf die
Anwendungslogik zugreifen.
183
10.2. N-TIER ARCHITEKTUREN
• Die Ablauflogik implementiert die eigentliche Logik der Anwendung.
• Der Datenspeicher verwaltet die von Anwendung bearbeiteten Daten, oftmals realisiert in
Form einer Datenbank.
Die Komponenten lassen sich auf unterschiedliche Art und Weise auf die Computer in einem verteilten System verteilen. Für die verschiedenen Varianten hat sich der Überbegriff N-Tier Architektur
etabliert, wobei N die Anzahl der Ebenen bezeichnet.
10.2.1
2-Tier Architektur
Bei der einfachsten Variante, die 2-Tier Architektur, verlagert man lediglich die Präsentationsschnittstelle auf einen anderen Rechner, typischerweise den lokalen Rechner des Benutzers. Beispiele für dieses Modell sind einfach Client/Server-Anwendungen im Internet (z.B. FTP, IMAP).
Die gestrichelte Linie deutet die Grenze zwischen den einzelnen Ebenen an.
presentation
access
logic
data
presentation
presentation
Abbildung 10.3: 2-Tier Architektur
10.2.2
3-Tier Architektur
Bei Anwendungen, die komplexe Datenbestände verwalten müssen, empfiehlt sich die Verwaltung
der Daten durch ein Datenbanksystem. Der Vorteil dieser Architektur liegt in der Trennung der
Datenverwaltung von der eigentlichen Logik der Anwendung.
presentation
presentation
legacy
access
logic
data
presentation
Abbildung 10.4: 3-Tier Architektur
184
KAPITEL 10. VERTEILTE ANWENDUNGEN
Mit der 3-Tier Architektur lassen sich auch bestehende alte Anwendungen (legacy applications)
oftmals leichter integrieren, indem man sie mehr oder weniger als eine Datenhaltungskomponente
betrachtet.
10.2.3
4-Tier Architektur
Bei der 4-Tier Architektur wird nun auch die Zugriffschnittstelle von der eigentlichen Anwendungslogik getrennt. Dadurch wird der Zugriff von der eigentlich Verarbeitung entkoppelt, was Vorteile
bezüglich Skalierbarkeit und Sicherheit bietet.
Typische Beispiele sind große Internet-Auftritte, bei denen die Präsentation durch einen WebBrowser erfolgt. Die Zugriffschnittstelle wird durch in der Regel mehrere Web-Server realisiert, wobei ein automatischer Lastausgleich erfolgt. Die Anwendungslogik steckt in Software-Komponenten, die auf einem Anwendungsserver ausgeführt werden. Die bearbeiteten Daten befinden sich
meist in relationalen bzw. objekt-orientierten Datenbanken.
presentation
legacy
logic
presentation
access
data
logic
presentation
data
Abbildung 10.5: 4-Tier Architektur
Kapitel 11
Java Beans
Mit Software-Komponenten versucht man wiederverwendbare Bausteine zu realisieren, die man anschließend mit speziellen Werkzeugen verknüpfen und zu neuen Anwendungen zusammenstecken
kann (Lego-Prinzip). Ziel ist es letztlich, die Entwicklungszeit durch die Wiederverwendung und
den Einsatz von Werkzeugen drastisch zu verkürzen.
Damit eine Software-Komponenten von Werkzeugen als solche erkannt und manipuliert werden
kann, müssen Informationen über diese Komponente vorhanden sein:
• Informationen über Parameter, die während der Komposition verändert werden können (properties).
• Informationen über von der Komponente realisierten Schnittstellen.
• Informationen über die Art und Weise, mit der mit einer Komponente kommuniziert werden
kann.
• Meta-Informationen zur Verwaltung und Darstellung der Komponente in Werkzeugen.
Die Java Beans sind eine Realisierung dieser Idee auf der Grundlage von Java. Im wesentlichen
unterscheidet eine Bean sich kaum von einer normalen Java Klasse, da durch die Möglichkeit der
Introspektion bereits viele notwendige Informationen zugreifbar sind. Es müssen jedoch bei der
Programmierung einer Bean einige Regeln eingehalten werden, damit die mit Introspektion nicht
ermittelbaren Informationen von Werkzeugen zugreifbar sind.
11.1 Properties
Properties sind parametrisierbare Eigenschaften einer Bean, die von einem Programm während der
Komposition manipuliert werden können.
• Properties sind private-Daten einer Bean, auf die mit public-Methoden zugegriffen
wird.
• Die Zugriffsmethoden müssen gewissen Namenskonventionen genügen (get<Property>,
set<Property>, is<Property>).
185
186
KAPITEL 11. JAVA BEANS
• Simple Properties repräsentieren einen einzelnen Wert.
• Indexed Properties repräsentieren mehrere Werte in einem Array.
• Bound Properties benachrichtigen andere Objekte, die sich als Listener angemeldet haben,
wenn sich ihr Wert ändert.
• Constrained Properties müssen Objekte, die sich als Listener angemeldet haben, vor einer
Änderung um Erlaubnis fragen.
• Die Manipulation der Properties erfolgt durch Property-Editoren. Für Standard-Datentypen
gibt es vorgefertigte Property-Editoren. Neue Property-Editoren können durch die Implementierung des Interfaces PropertyEditor implementiert werden.
• Bei komplexeren Beans kann der Entwickler auch einen Customizer realisieren, der den Anwender beim Einstellen der Properties unterstüzt. Ein Customizer implementiert das Interface
Customizer.
Das folgende Beispiel ist eine Bean mit einfachen Properties und indizierten Properties, wobei die
Properties verschiedenen Datentypen besitzen.
public class SimpleBean implements java.io.Serializable {
// simple properties except boolean:
private int number = 0;
public void setNumber(int value) {
number = value;
}
public int getNumber() {
return number;
}
// boolean properties are somewhat special:
private boolean active = false;
public void setActive(boolean value) {
active = value;
}
public boolean isActive() {
return active;
}
// indexed properties:
private float[] points;
public void setPoints(int index, float value) {
187
11.1. PROPERTIES
points[index] = value;
}
public void setPoints(float[] values) {
points = values;
}
public float getPoints(int index) {
return points[index];
}
public float[] getPoints() {
return points;
}
}
Bei gebundenen Properties werden registrierte Objekte mit einem PropertyChangeEvents
über Änderungen benachrichtigt. Objekte, die über Änderungen benachrichtig werden wollen, müssen entsprechend das Interface PropertyChangeListener implementieren. Eine zusätzliche
Klasse PropertyChangeSupport hilft bei der Verwaltung von registrierten Listenern.
«interface»
EventListener
«interface»
PropertyChangeListener
+propertyChange(e:PropertyChangeEvent): void
PropertyChangeSupport
+addPropertyChangeListener(pcl:PropertyChangeListener): void
+removePropertyChangeListener(pcl:PropertyChangeListener): void
Object
EventObject
PropertyChangeEvent
Abbildung 11.1: Java Klassen und Interfaces für gebundene Properties
import java.beans.PropertyChangeSupport;
import java.beans.PropertyChangeListener;
public class BoundBean implements java.io.Serializable {
private PropertyChangeSupport pcs;
public static final String PRICE = "price";
private int price;
public BoundBean() {
pcs = new PropertyChangeSupport(this);
}
188
KAPITEL 11. JAVA BEANS
public void addPropertyChangeListener(PropertyChangeListener pcl) {
pcs.addPropertyChangeListener(pcl);
}
public void removePropertyChangeListener(PropertyChangeListener pcl) {
pcs.removePropertyChangeListener(pcl);
}
public void setPrice(int newValue) {
if (newValue != price) {
int oldValue = price;
price = newValue;
pcs.firePropertyChange(PRICE,
new Integer(oldValue),
new Integer(newValue));
}
}
public int getPrice() {
return price;
}
}
• Die Erzeugung eines PropertyChangeEvents muß explizit programmiert werden. Dabei
sind der alte und der neue Wert sowie der Name des Properties zu übergeben.
• PropertyChangeEvents werden grundsätzlich erst nach der Änderung einer Property
erzeugt.
• Es empfiehlt sich keine PropertyChangeEvents zu generieren, wenn sich der Wert einer
Property nicht geändert hat, da es durchaus zu Schleifen kommen kann, wenn ein Listener
direkt oder indirekt wieder die set<Property>-Methode aufruft.
Der typische Ablauf ist in folgendem Sequenzdiagramm dargestellt.
:Sender
:BoundBean
:Listener
addPropertyChangeListener
setNumber
propertyChange
Abbildung 11.2: Sequenzdiagramm für Bound Properties
11.1. PROPERTIES
189
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public class BoundBeanListener implements PropertyChangeListener {
private BoundBean boundBean;
public BoundBeanListener(BoundBean bb) {
boundBean = bb;
boundBean.addPropertyChangeListener(this);
}
public void propertyChange(PropertyChangeEvent e) {
if (e.getSource() == boundBean
&& e.getPropertyName() == BoundBean.PRICE) {
System.out.println("New price: " + e.getNewValue());
}
}
}
Die Implementierung von Constrained Properties erfolgt analog zu Bound Properties. Die Listener müssen hierbei das VetoableChangeListener Interface implementieren und können die
Änderung des Datenfelds durch das Werfen einer PropertyVetoException verhindern.
import
import
import
import
import
java.beans.PropertyChangeSupport;
java.beans.PropertyChangeListener;
java.beans.VetoableChangeSupport;
java.beans.VetoableChangeListener;
java.beans.PropertyVetoException;
public class VetoableBean implements java.io.Serializable {
private PropertyChangeSupport pcs;
private VetoableChangeSupport vcs;
public static final String PRICE = "price";
private int price;
public VetoableBean() {
pcs = new PropertyChangeSupport(this);
vcs = new VetoableChangeSupport(this);
}
public void addPropertyChangeListener(PropertyChangeListener pcl) {
pcs.addPropertyChangeListener(pcl);
}
public void removePropertyChangeListener(PropertyChangeListener pcl) {
pcs.removePropertyChangeListener(pcl);
}
public void addVetoableChangeListener(VetoableChangeListener vcl) {
vcs.addVetoableChangeListener(vcl);
}
190
KAPITEL 11. JAVA BEANS
public void removeVetoableChangeListener(VetoableChangeListener vcl) {
vcs.removeVetoableChangeListener(vcl);
}
public void setPrice(int newValue) throws PropertyVetoException {
if (newValue != price) {
int oldValue = price;
vcs.fireVetoableChange(PRICE,
new Integer(oldValue),
new Integer(newValue));
price = newValue;
pcs.firePropertyChange(PRICE,
new Integer(oldValue),
new Integer(newValue));
}
}
public int getPrice() {
return price;
}
}
• In der Regel ist ein Constrained Property auch ein Bound Property, damit die Listener auch
über den positiven Ausgang der Abstimmung unterrichtet werden können.
Der typische Ablauf ist in folgendem Sequenzdiagramm dargestellt.
:Sender
:VetoableBean
:Listener
addPropertyChangeListener
setNumber
vetoableChange
propertyChange
Abbildung 11.3: Sequenzdiagramm für Vetoable Properties
small
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
11.2. EXPLIZITE BESCHREIBUNG EINER BEAN
191
import java.beans.VetoableChangeListener;
import java.beans.PropertyVetoException;
public class VetoableBeanListener
implements VetoableChangeListener, PropertyChangeListener {
private VetoableBean vetoBean;
public VetoableBeanListener(VetoableBean vb) {
vetoBean = vb;
vetoBean.addVetoableChangeListener(this);
vetoBean.addPropertyChangeListener(this);
}
public void vetoableChange(PropertyChangeEvent e)
throws PropertyVetoException {
if (e.getSource() == vetoBean
&& e.getPropertyName() == VetoableBean.PRICE) {
int oldValue = ((Integer) e.getOldValue()).intValue();
int newValue = ((Integer) e.getNewValue()).intValue();
int d = oldValue * 5 / 100;
if (newValue < oldValue - d || newValue > oldValue + d) {
throw new PropertyVetoException("change > 5 percent", e);
}
}
}
public void propertyChange(PropertyChangeEvent e) {
if (e.getSource() == vetoBean
&& e.getPropertyName() == VetoableBean.PRICE) {
System.out.println("New price: " + e.getNewValue());
}
}
}
11.2
Explizite Beschreibung einer Bean
Will man sich nicht auf die automatische Erkennung der Fähigkeiten einer Bean verlassen und stattdessen explizit die Schnittstelle einer Bean beschreiben, dann kann man eine Klasse bereitstellen,
die das BeanInfo Interface implementiert. Zur Vereinfachung gibt es eine vordefinierte Klasse
SimpleBeanInfo, die das BeanInfo Interface implementiert und von der man eine Klasse
ableiten kann.
• Die Beschreibung einer Bean und ihrer Properties, Events und Methoden erfolgt durch Descriptoren, die von der Klasse FeatureDescriptor abgeleitet sind.
• Zusätzlich kann ein Icon in unterschiedlichen Größen zur Darstellung in Werkzeugen bereitgestellt werden.
192
KAPITEL 11. JAVA BEANS
«interface»
BeanInfo
+getIcon(iconKind:int): Image
+getBeanDescriptor(): BeanDescriptor
+getPropertyDescriptor(): PropertyDescriptor[]
+getMethodDescriptor(): MethodDescriptor[]
+getEventSetDescriptor(): EventSetDescriptor[]
SimpleBeanInfo
BeanDescriptor
Object
PropertyDescriptor
FeatureDescriptor
EventSetDescriptor
MethodDescriptor
ParameterDescriptor
Abbildung 11.4: Interface BeanInfo und unterstützende Klassen
• Wird statt eines Descriptors der Wert null zurückgeliefert, so wird sich ein Werkzeug Informationen über die Bean mit Hilfe des Reflection-Mechanismums besorgen.
import
import
import
import
import
import
java.beans.PropertyDescriptor;
java.beans.EventSetDescriptor;
java.beans.SimpleBeanInfo;
java.beans.IntrospectionException;
java.beans.PropertyChangeListener;
java.beans.VetoableChangeListener;
public class VetoableBeanBeanInfo extends SimpleBeanInfo {
private static PropertyDescriptor[] properties;
private static EventSetDescriptor[] events;
static {
try {
PropertyDescriptor price
= new PropertyDescriptor("price", VetoableBean.class,
"getPrice", "setPrice");
properties = new PropertyDescriptor[] { price };
} catch (IntrospectionException e) {
e.printStackTrace();
}
try {
EventSetDescriptor pce
= new EventSetDescriptor(VetoableBean.class,
"propertyChange",
PropertyChangeListener.class,
new String[] { "propertyChanged" },
"addPropertyChangeListener",
11.3. VERPACKEN EINER BEAN
193
"removePropertyChangeListener");
EventSetDescriptor vce
= new EventSetDescriptor(VetoableBean.class,
"vetoableChange",
VetoableChangeListener.class,
new String[] { "vetoableChange" },
"addVetoableChangeListener",
"removeVetoableChangeListener");
events = new EventSetDescriptor[] { pce, vce };
} catch (IntrospectionException e) {
e.printStackTrace();
}
}
public PropertyDescriptor[] getPropertyDescriptors() {
return properties;
}
public EventSetDescriptor[] getEventSetDescriptors() {
return events;
}
public java.awt.Image getIcon(int iconKind) {
java.awt.Image img = null;
if (iconKind == ICON_COLOR_16x16 || iconKind == ICON_MONO_16x16) {
img = loadImage("price16.gif");
}
if (iconKind == ICON_COLOR_32x32 || iconKind == ICON_MONO_32x32) {
img = loadImage("price32.gif");
}
return img;
}
}
11.3
Verpacken einer Bean
Beans werden typischerweise in einem Java Archiv (.jar) verpackt in dem auch zusätzliche Hilfsklassen (z.B. BeanInfo) enhalten sein können. Eine spezielle Manifest-Datei beschreibt den Aufbau eines Archivs und wird bei der Erstellung des Archivs automatisch erzeugt. Bei Archiven, die
Beans enthalten, muß eine Vorlage (.mf) für diese Manifest-Datei geschrieben werden, mit der
weitere Meta-Informationen über die Beans in einem Archiv den Werkzeugen bekannt gemacht
werden.
Die Manifest-Datei besteht aus einer Folge von Zeilen. In jeder Zeile befindet sich genau ein
Schlüssel-Wert-Paar. Der Schlüssel wird durch einen Doppelpunkt und folgende Leerzeichen vom
Wert getrennt. Zusammengehörende Schlüssel-Wert-Paare stehen in aufeinanderfolgenen Zeilen.
Leerzeilen trennen Gruppen von Schlüssel-Wert-Paaren.
Ein einfaches Manifest für die VetoableBean sieht etwa folgendermaßen aus:
Manifest-Version: 1.0
194
KAPITEL 11. JAVA BEANS
Name: VetoableBean
Java-Bean: True
Name: VetoableBeanBeanInfo
Depends-On: price16.gif
Depends-On: price32.gif
Design-Time-Only: True
• Durch Manifest-Version wird die Version des Manifestformats beschrieben (1.0 für
Java 2).
• Die Klasse VetoableBean wird durch Java-Bean als Bean kenntlich gemacht.
• Mit Depends-On können Abhängigkeiten beschrieben werden.
• Ein Design-Time-Only zeigt an, dass ein Element im Archiv nur während des Entwurfs
benötigt wird und beim Erstellen der fertigen Anwendung weggelassen werden kann.
11.4
Beispiel
Hier folgt nun ein Beispiel für ein Bean, die einen Timer realisiert, der periodisch ein ActionEvent
generiert, das von einem TimerListener abgefangen werden kann.
Zunächst das TimerListener Interface:
/*
* TimerListener.java
*/
public interface TimerListener extends java.util.EventListener {
public void onTime(java.awt.event.ActionEvent event);
}
Implementation der Timer Bean:
/*
* Timer.java
*/
import
import
import
import
import
java.awt.event.ActionEvent;
java.beans.PropertyChangeSupport;
java.beans.PropertyChangeListener;
java.util.Vector;
java.util.Enumeration;
public class Timer extends Object implements java.io.Serializable {
public static final String PROP_DELAY = "delay";
public static final long DEFAULT_DELAY = 1000;
transient private TimerThread timerThread;
195
11.4. BEISPIEL
transient private Vector listeners;
private PropertyChangeSupport propertySupport;
private boolean running;
private long delay;
public Timer() {
delay = DEFAULT_DELAY;
propertySupport = new PropertyChangeSupport(this);
start();
}
public synchronized void start() {
if (running) return;
timerThread = new TimerThread();
running = true;
timerThread.start();
}
public synchronized void stop() {
if (! running) return;
timerThread.stop();
timerThread = null;
running = false;
}
// deprecated
public long getDelay() {
return delay;
}
public void setDelay(long value) {
if (delay == value) return;
long oldValue = delay;
delay = value;
propertySupport.firePropertyChange(PROP_DELAY,
new Long(oldValue),
new Long(delay));
}
public void addPropertyChangeListener(PropertyChangeListener pcl) {
propertySupport.addPropertyChangeListener(pcl);
}
public void removePropertyChangeListener(PropertyChangeListener pcl) {
propertySupport.removePropertyChangeListener(pcl);
}
public void addTimerListener(TimerListener l) {
if (listeners == null) listeners = new Vector();
listeners.addElement(l);
}
public void removeTimerListener(TimerListener l) {
if (listeners == null) return;
196
KAPITEL 11. JAVA BEANS
listeners.removeElement(l);
}
private void fireTimerEvent() {
if (listeners == null) return;
Vector l;
synchronized(this) {
l = (Vector)listeners.clone();
}
for (Enumeration e = l.elements(); e.hasMoreElements();) {
TimerListener tl = (TimerListener) e.nextElement();
tl.onTime(new ActionEvent(this,
ActionEvent.ACTION_PERFORMED,
"onTime"));
}
}
class TimerThread extends Thread {
public void run() {
while (true) {
try {
sleep(delay);
} catch (InterruptedException e) {}
fireTimerEvent();
}
}
}
}
Und nun noch die Implementation der Klasse TimerBeanInfo:
/*
* TimerBeanInfo.java -*/
import
import
import
import
import
import
import
java.beans.EventSetDescriptor;
java.beans.PropertyDescriptor;
java.beans.SimpleBeanInfo;
java.beans.IntrospectionException;
java.awt.Image;
java.awt.event.ActionEvent;
java.lang.reflect.Method;
public class TimerBeanInfo extends SimpleBeanInfo {
private Image icon;
private static PropertyDescriptor[] properties;
private static EventSetDescriptor[] events;
static {
try {
PropertyDescriptor delay
= new PropertyDescriptor("Delay", Timer.class,
11.4. BEISPIEL
197
"getDelay", "setDelay");
properties = new PropertyDescriptor[] { delay };
Method listenerMethod
= TimerListener.class.getMethod("onTime",
new Class[] { ActionEvent.class } );
Method addListenerMethod
= Timer.class.getMethod("addTimerListener",
new Class[] { TimerListener.class } );
Method removeListenerMethod
= Timer.class.getMethod("removeTimerListener",
new Class[] { TimerListener.class } );
EventSetDescriptor timer
= new EventSetDescriptor("timer",
TimerListener.class,
new Method[] { listenerMethod },
addListenerMethod,
removeListenerMethod);
events = new EventSetDescriptor[] { timer };
}
catch (IntrospectionException ex) {
ex.printStackTrace();
}
catch (NoSuchMethodException ex) {
ex.printStackTrace();
}
}
public TimerBeanInfo() {
icon = loadImage("timer.gif");
}
public Image getIcon(int type) {
return icon;
}
public PropertyDescriptor[] getPropertyDescriptors() {
return properties;
}
public EventSetDescriptor[] getEventSetDescriptors() {
return events;
}
}
198
KAPITEL 11. JAVA BEANS
Kapitel 12
Servlets
Das World-Wide-Web spielt eine zunehmend wichtige Rolle bei der internen Arbeitsorganisation
und der Abwicklung von Transaktionen zwischen Unternehmungen. Entsprechend müssen viele
Anwendungen heutzutage Web-basierte Benutzungsoberflächen bereitstellen.
In diesem Kapitel wird daher beschrieben, wie mit Hilfe von Java Servlets Programme in einem
Web-Server ausgeführt werden können um dynamisch Web-Seiten zu generieren und Interaktionen
zu verfolgen.
12.1 Grundlagen der Web-Technologie
Zunächst folgt eine kurze Zusammenfassung der technischen Grundlagen des World-Wide-Webs.
Eine genaue Diskussion der einzelnen relevanten Protokolle und Standards kann an dieser Stelle
nicht erfolgen. Bei Bedarf nach weiteren Informationen sei an dieser Stelle auf die angegebenen
Quellen verwiesen.
12.1.1
URIs, URLs und URNs
Globale Namensräume zur Addressierung und Benennung von Ressourcen:
• URI: Uniform Resource Identifier (Oberbegriff für URLs und URNs, RFC 2396)
• URL: Uniform Resource Locator (ortsgebundene Adresse einer Ressource)
• URN: Uniform Resource Name (persistenter Name einer Ressource)
URIs können Parameter haben, die den Zugriff auf eine Ressource näher beschreiben.
12.1.2
MIME
Die MIME-Standards (Multipurpose Internet Mail Extension) sind zur Übertragung von verschiedenen Datenformaten in Internet Mails entstanden (RFC 2045-2049). Das Grundprinzip ist die Identifikation eines Inhalts (content) durch einen Inhaltstyp (content-type), seiner Länge (content-length)
und Trennkennzeichen.
199
200
KAPITEL 12. SERVLETS
12.1.3
HTTP
Das Hypertext Transfer Protocol (HTTP) ist in der Version 1.1 in RFC 2616 spezifiziert und stellt
die folgenden HTTP Methoden bereit:
• GET: Lesen einer Ressource
• HEAD: Lesen von Meta-Informationen über einer Ressource
• POST: Erweitern einer existierenden Ressource
• PUT: Schreiben einer Ressource
• DELETE: Löschen einer Ressource
• TRACE: Testaufruf zum Lokalisieren von Fehlern
• OPTIONS: Informationen über verfügbare Optionen.
Die HTTP 1.1 Statuscodes sind strukturiert aufgebaut:
• 1xx: informational
• 2xx: successful
• 3xx: redirection
• 4xx: client error
• 5xx: server error
Zu jedem HTTP-Methodenaufruf gibt es eine Menge von Headern, die zusätzliche Informationen
über den Aufruf beinhalten können oder Informationen über die Benutzer bzw. die verwendetet
Software enthalten.
12.1.4
HTML
Die Hypertext Markup Language ist die Seitenbeschreibungssprache. Sie stammt ursprünglich von
SGML ab, einem mächtigen Standard zur Beschreibung von Dokumentstrukturen. Mittlerweile hat
man einen etwas weniger mächtigen und damit auch weniger komplexen Standard zur Beschreibung
von Dokumentstrukturen geschaffen, die Extensible Markup Language (XML). Man hat auf der
Basis von XML HTML neu definiert, was zur aktuellen Version XHTML führt (die derzeit leider
nicht von allen Browsern korrekt interpretiert wird).
12.2. GENERISCHE SERVLETS
201
12.2 Generische Servlets
Servlets sind Java Programme, die von einem Web-Server beim Zugriff auf bestimmte URLs ausgeführt werden und in der Regel dynamisch HTML-Seiten erzeugen. Die Ausführung passiert in
einer sogenannten Servlet-Engine, die entweder direkt im Web-Server integriert ist oder als externer
Prozess realisiert wird und mit dem Web-Server kommuniziert.
import
import
import
import
import
import
java.io.PrintWriter;
java.io.IOException;
javax.servlet.GenericServlet;
javax.servlet.ServletRequest;
javax.servlet.ServletResponse;
javax.servlet.ServletException;
public class HelloWorldServlet extends GenericServlet {
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
res.setContentType("text/html");
PrintWriter out = res.getWriter();
out.println("<html><body>Hello World!</body></html>");
}
}
Die Anfrage wird durch das ServletRequest Interface dargestellt.
• Die einzelnen Anfrageparameter werden über eine Enumeration zur Verfügung gestellt.
• Auf das in einer Anfrage enthaltene Dokument kann über einen ServletInputStream
zugegriffen werden, von dem man sich mit getReader() einen BufferedReader besorgen kann.
• Weitergehende Informationen über den erfolgten Zugriff kann man über weitere Methoden
des ServletRequest Objekts erhalten.
Die Antwort wird analog durch das ServletResponse Interface beschrieben.
• Im Antwortobjekt kann man den MIME-Typ der Antwort bestimmen und ggf. explizit die
Länge der Antwort (content) setzen.
• Das zur Antwort gehörende Dokument wird in einen ServletOutputStream geschrieben, von dem man sich mit getWriter() einen PrintWriter besorgen kann.
import
import
import
import
import
import
import
java.util.Enumeration;
java.io.PrintWriter;
java.io.IOException;
javax.servlet.GenericServlet;
javax.servlet.ServletRequest;
javax.servlet.ServletResponse;
javax.servlet.ServletException;
202
KAPITEL 12. SERVLETS
public class ParameterServlet extends GenericServlet {
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
res.setContentType("text/plain");
PrintWriter out = res.getWriter();
// Every ServletRequest gives us ...
out.println("Server:\t" + req.getServerName());
out.println("Port:\t" + req.getServerPort());
out.println("Client:\t" + req.getRemoteHost());
out.println("Protocol:" + req.getProtocol());
out.println("Scheme:\t" + req.getScheme());
out.println("Security:\t" + req.isSecure());
out.println("ContentType:\t" + req.getContentType());
out.println("ContentLength:\t" + req.getContentLength());
// ... plus all the request parameters:
out.println("");
Enumeration e = req.getParameterNames();
while (e.hasMoreElements()) {
String name = (String) e.nextElement();
String values[] = req.getParameterValues(name);
out.print("Parameter " + name + ":\t");
for (int i = 0; i < values.length; i++) {
out.print(values[i] + " ");
}
out.println();
}
}
}
12.3
HTTP Servlets
Da HTTP eine so große Bedeutung hat, gibt es eine entsprechende Spezialisierung der Anfrageund Antwortschnittstellen und eine Klasse HttpServlet, die Methoden für die einzelnen HTTPMethoden bereitstellt.
import
import
import
import
import
import
import
java.util.Enumeration;
java.io.PrintWriter;
java.io.IOException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
javax.servlet.ServletException;
public class HeaderServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res)
203
12.3. HTTP SERVLETS
throws ServletException, IOException {
res.setContentType("text/plain");
PrintWriter out = res.getWriter();
// Every ServletRequest gives us ...
out.println("Server:\t" + req.getServerName());
out.println("Port:\t" + req.getServerPort());
out.println("Client:\t" + req.getRemoteHost());
out.println("Protocol:" + req.getProtocol());
out.println("Scheme:\t" + req.getScheme());
out.println("Security:\t" + req.isSecure());
out.println("ContentType:\t" + req.getContentType());
out.println("ContentLength:\t" + req.getContentLength());
// ... and an HttpServletRequest tells us even more ...
out.println("");
out.println("Method:\t" + req.getMethod());
out.println("URI:\t" + req.getRequestURI());
out.println("Path:\t" + req.getServletPath());
// ... plus all the HTTP header information.
out.println("");
Enumeration e = req.getHeaderNames();
while (e.hasMoreElements()) {
String name = (String) e.nextElement();
String value = req.getHeader(name);
out.println("Header " + name + ":\t" + value);
}
}
}
UML-Darstellung der wichtigsten Klassen und Interfaces:
«interface»
«interface»
«interface»
Servlet
ServletRequest
ServletResponse
+init(config:ServletConfig): void
+destroy(): void
+service(req:ServletRequest,res:ServletResponse): void
«interface»
«interface»
«interface»
HttpServletRequest
HttpServletResponse
JspPage
HttpJspPage
Object
GenericServlet
HttpServlet
#doGet(req:HttpServletRequest,res:HttpServletResponse): void
#doPost(req:HttpServletRequest,res:HttpServletResponse): void
#doPut(req:HttpServletRequest,res:HttpServletResponse): void
#doDelete(req:HttpServletRequest,res:HttpServletResponse): void
#doOptions(req:HttpServletRequest,res:HttpServletResponse): void
#doTrace(req:HttpServletRequest,res:HttpServletResponse): void
Abbildung 12.1: Java Servlet Klassen und Interfaces
204
KAPITEL 12. SERVLETS
• Die abstrakte Klasse HttpServlet implementiert die Methode service indem die jeweils zur HTTP-Methode passende Java Methode aufgerufen wird.
• Das HttpServletResponse Interface stellt Methoden zum Senden von HTTP Fehlermeldungen (sendError()) und spezielle HTTP Konstanten bereit.
• Mit setHeader() und setStatus() können spezielle Header erzeugt werden und der
Status eines Dokuments kann gesetzt werden.
12.4
Lebenszyklus und Container
Der Lebenszyklus eines Servlets besteht aus drei Phasen:
1. Das Servlet wird in die Ausführungsumgebung geladen. Dabei ruft die Ausführungsumgebung die init() Methode auf, damit sich das Servlet initialisieren kann.
2. Das Servlet bearbeitet Anfragen.
3. Das Servlet wird aus dem Server entfernt. Dabei ruft die Ausführungsumgebung die Methode
destroy() auf.
• Achtung: Die Ausführungsumgebungen haben in der Regel mehrere Threads. Daher ist eine korrekte Synchronisation erforderlich, auch bei der Terminierung eines Servlets in der
destroy Methode.
12.5 Sessions
In der Regel ist es notwendig, dass sich Servlets zwischen einzelnen Anfragen Zustandsinformationen merken können. Mit Hilfe des HttpSession Interfaces läßt sich das relativ einfach implementieren.
import
import
import
import
import
import
import
import
java.util.Enumeration;
java.io.PrintWriter;
java.io.IOException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
javax.servlet.http.HttpSession;
javax.servlet.ServletException;
public class SessionServlet extends HttpServlet {
static final String COUNTER = "counter";
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
HttpSession session = req.getSession(true);
12.6. JAVASERVER PAGES (JSP)
205
Integer counter = (Integer) session.getAttribute(COUNTER);
if (counter == null) {
counter = new Integer(0);
}
counter = new Integer(counter.intValue() + 1);
session.setAttribute(COUNTER, counter);
res.setContentType("text/plain");
PrintWriter out = res.getWriter();
out.println("Page Counter: " + counter);
}
}
• Zu jeder Session gehört eine Menge von Attributen, wobei jedes Attribut ein Name/Wert-Paar
ist.
• Eine Session hat ein Erzeugungsdatum und eine Lebenszeit nach der die Session bei Inaktivität des Benutzers gelöscht wird.
• Die Attribute einer Session werden lokal auf dem Server gehalten. Zur Identifikation einer
Session können verschiedene Mechanismen benutzt werden (Cookies, Parameter in URLs).
Achtung: Verfahren, die die URLs modifizieren, funktionieren nur, wenn man URLs mit Hilfe
der API dynamisch erzeugt und niemals als Konstanten definiert.
• Das HttpSessionBindingListener Interface ermöglicht es anderen Objekten, sich
an ein Session-Objekt zu binden und über Änderungen im Session-Objekt mit dem Ereignis
HttpSessionBindingEvent benachrichtigt zu werden.
12.6 JavaServer Pages (JSP)
Bei der Programmierung von Servlets ist es lästig HTML Dokumente mit Hilfe von Java Methodenaufrufen zu erzeugen und die Wartung der Seiten etwa bei einfachen Layout-Änderungen erzeugt
Programmänderungen, die ihrerseits wiederum Fehler in ein ansonsten stabiles Servlet einbringen
können. Mit JavaServer Pages (JSP) verfolgt man einen anderen Ansatz, bei dem Java Quelltextfragmente in HTML Dokumente eingebettet werden. Am deutlichsten läßt sich der Unterschied an
einem kleinen Beispiel demonstrieren:
import
import
import
import
import
import
java.io.PrintWriter;
java.io.IOException;
javax.servlet.GenericServlet;
javax.servlet.ServletRequest;
javax.servlet.ServletResponse;
javax.servlet.ServletException;
public class HelloWorldServlet extends GenericServlet {
static final String DOC = "-//W3C//DTD XHTML 1.0 Strict//EN";
static final String DTD = "http://www.w3.org/TR/xhtml1/xhtml1-strict.dtd";
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
206
KAPITEL 12. SERVLETS
res.setContentType("text/html");
PrintWriter out = res.getWriter();
out.println("<!DOCTYPE html PUBLIC \"" + DOC +"\"");
out.println("\"" + DTD + "\">");
out.println("<html lang=\"en\">");
out.println(" <head>");
out.println("
<title>HelloWorldServlet</title>");
out.println(" </head>");
out.println(" <body>");
out.println("
<p>Hello World!</p>");
out.println("
<hr/>");
out.println("
<p>Generated: "
+ (new java.util.Date()).toString() + "</p>");
out.println(" </body>");
out.println("</html>");
}
}
Dieses Servlet erzeugt eine Seite, auf der das Erzeugungsdatum dynamisch eingetragen wird. Die
eigentliche Logik besteht aus genau einer Zeile Java-Quelltext. Mit Hilfe von JSP läßt sich dieselbe
Seite folgendermaßen erzeugen:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/xhtml1-strict.dtd">
<html lang="en">
<head>
<title>HelloWorldJsp</title>
</head>
<body>
<p>Hello World!</p>
<hr/>
<p>Generated: <%= (new java.util.Date()).toString() %></p>
</body>
</html>
• Offensichtlich handelt es sich bei JSPs um HTML-Dokumente, wobei in speziellen Elementen entsprechende Zeilen Java-Quelltext eingebettet sind.
• Der HTML-Teil von JSPs kann in der Regel mit normalen HTML-Werkzeugen bearbeitet
werden.
• Es ist die Aufgabe des Web-Servers, aus JSPs entsprechende Servlets zu generieren, diese zu
übersetzen, in den Server zu laden und auszuführen.
Es existieren verschiedene JSP-spezifische Konstrukte. Leider ist die originale Syntax ad-hoc und
nicht XML-konform. Mittlerweile gibt es eine Spezifikation, die auch XML-konforme Konstrukte
anbietet, die aber nicht mit der alten Notation gemischt werden dürfen. Leider unterstützen zur Zeit
noch nicht alle JSP-Implementationen die neue XML-konforme Syntax.
12.6. JAVASERVER PAGES (JSP)
12.6.1
207
Deklarationen
In Deklarationen können Variablen oder Methoden deklariert werden, die später benutzt werden
sollen. Es ist notwendig, Variablen und Methoden vor der Benutzung zu deklarieren.
Originale JSP-Notation:
<%! _declaration_; [_declaration_;]+ %>
XML-Notation:
<jsp:declaration>
_declaration_; [_declaration_;]+
</jsp:declaration>
Hierbei steht declaration für eine gültige Java Deklaration. Deklarationen können auch aus
anderen Dateien statisch eingebunden werden.
12.6.2
Ausdrücke
Ein Ausdruck ist eine Java Ausdruck, der zu einem String umgewandelt wird und anstelle des JSPAusdrucks in das HTML Dokument eingefügt wird.
Originale JSP-Notation:
<%= _expression_ %>
XML-Notation:
<jsp:expression>
_expression_
</jsp:expression>
12.6.3
Scriptlets
Ein Scriptlet beinhaltet Quelltext-Fragment bestehend aus einer beliebigen Menge von Deklarationen, Statements oder Ausdrücken.
Originale JSP-Notation:
<% _code_fragment_ %>
XML-Notation:
<jsp:scriptlet>
_code_fragment_
</jsp:scriptlet>
Scriptlets werden bei der Bearbeitung von Anfragen ausgeführt. Ein PrintWriter mit dem Namen out wird zur Verfügung gestellt, um Ausgaben an der Position des Scriptlets in das HTML
Dokument einzufügen.
208
KAPITEL 12. SERVLETS
12.6.4
Einfügungen
In eine JSP-Datei kann der Inhalt anderer JSP-Dateien eingefügt werden.
Originale JSP-Notation:
<%@ include file="_relativeURL_" %>
XML-Notation:
<jsp:directive_include file="_relativeURL_"/>
Das Einbinden der Datei relativeURL in die aktuelle JSP Datei erfolgt zum Zeitpunkt der
Übersetzung (static include). Die eingefügte Datei kann selbst wieder JSP-Konstrukte enthalten.
12.6.5
Weiterleitungen
Eine JSP-Datei kann die Bearbeitung der Anfrage an andere JSP-Dateien oder statische HTMLDokumente weiterleiten.
XML-Notation:
<jsp:forward page="_relativeURL_" />
Die relative URL relativeURL kann auf dynamische oder statische Dokumente verweisen. Bei
Verweisen auf dynamische Dokumente können Parameter in der Form von Attribut/Wert-Paaren
weitergereicht werden.
12.6.6 Seiten-Direktiven
Seiten-Direktiven setzen Attribute, die für die gesamten JSP-Seite gelten.
Originale JSP-Notation:
<%@ page _directive_="_value_" %>
XML-Notation:
<jsp:directive_page _page_directive_list_ />
Die wichtigsten Seiten-Direktiven sind:
• import=" package.class "
Diese Direktive importiert Klassen oder Pakete und kann mehrmals in einer JSP-Seite auftauchen. Die Pakete java.lang.*, javax.servlet.*, javax.servlet.jsp.* und
javax.servlet.http.* werden automatisch importiert.
12.7. JAVASERVER PAGES UND BEANS
209
• session=" bool "
Der boolesche Wert zeigt an, ob für die JSP-Seite eine Session benötigt wird oder nicht.
Wenn der Wert true ist, dann ist die Session über das Objekt mit dem Namen session
zugreifbar.
• info=" text "
Ein erläuternder statischer Text wird im erzeugten Servlet abgelegt und ist kann mit Hilfe von
Servlet.getServletInfo() abgefragt werden.
• contentType=" mimetype "
Bestimmt den erzeugten MIME-Typ, standardmäßig text/html.
• isThreadSafe=" bool "
Bei einer sicheren JSP-Seite können mehrere Threads gleichzeitig nebenläufige Zugriffe auf
die Seite abarbeiten. Setzt man diese Direktive auf false, so wird der Web-Server die einzelnen Threads serialisieren.
• errorPage=" relativeURL "
Ein Verweis auf eine andere Seite, die die Behandlung von Fehlern übernimmt.
12.7
JavaServer Pages und Beans
Die Einbettung von Java-Quelltext in HTML-Dokumente schafft leider immer noch keine befriedigende Trennung der Darstellung (HTML) von der zugrundeliegenden Verarbeitungslogik (Java).
Man kann allerdings durch die Verwendung von Beans die Trennung deutlich verbessern. Es gibt
entsprechende JSP-Konstrukte, die insbesondere die Verarbeitung von Daten aus Web-Formularen
vereinfachen helfen.
12.7.1
Bean-Benutzung
Durch die Benutzung von Beans läßt sich der Umfang von Java-Quelltext in HTML-Dokumenten
deutlich reduzieren. Außerdem läßt sich die Übergabe von Parametern aus HTML-Fomularen and
Java-Programme vereinfachen, da Parameter automatisch an Properties von Beans zugewiesen werden können.
XML-Notation:
<jsp:useBean id="_bean_" scope="_scope_" class="_package.class_" />
• id=" bean "
Definiert den Namen einer Variablen, über die auf die Instanz der Bean zugegriffen werden
kann.
• scope=" scope "
Definiert, wo aus die Instanz der Bean zugegriffen werden kann. Der Parameter scope
kann folgende vordefinierte Werte annehmen:
– page: Gültigkeit in der JSP-Seite, die auch das <jsp:useBean/> Konstrukt enthält.
210
KAPITEL 12. SERVLETS
– request: Gültigkeit für beliebige JSP-Seiten bis zur Beendigung der aktuellen Anfrage.
– session: Gültigkeit für alle JSP-Seiten, die dieselbe Session bearbeiten.
– application: Gültigkeit für alle JSP-Seiten einer ganze Applikation (was immer
eine Applikation genau ist).
• class=" package.class "
Spezifiziert die Klasse, die die Bean implementiert. Die Klasse muß einen public Konstruktur
ohne Argumente bereitstellen.
12.7.2 Properties
Auf die nicht indizierten Properties einer Bean kann über spezielle JSP-Konstrukte zugegriffen
werden.
XML-Notation:
<jsp:getProperty
<jsp:setProperty
<jsp:setProperty
<jsp:setProperty
name="_bean_"
name="_bean_"
name="_bean_"
name="_bean_"
property="_name_" />
property="_prop_" value="_val_" />
property="_prop_" param="_par_" />
property="*" />
Mit getProperty wird ein Wert einer Bean Property ausgelesen.
Die erste Form von setProperty weist den Wert val der Property prop der Bean bean
zu. Die zweite Form weist den Wert des Anfrageparameters par zu während die dritte Form alle
Parameter der Anfrage entsprechenden Properties zuweist. Bei der Zuweisung werden die Strings
automatisch in passende Datentypen konvertiert.
Achtung: Die Zuweisung der Properties in der dritten Form von setProperty erfolgt in einer
nicht weiter spezifizierten Reihenfolge.
12.8
Beispiel: Raten von Nummern
public class GuessNumberBean implements java.io.Serializable {
private
private
private
private
private
int number;
int guess;
int count;
String hint;
boolean correct;
//
//
//
//
//
number to guess
current guess
number of guesses so far
hint for next guess
correct guess?
public GuessNumberBean() {
number = Math.abs(new java.util.Random().nextInt() % 100) + 1;
correct = false;
guess = -1;
count = 0;
hint = "";
}
12.8. BEISPIEL: RATEN VON NUMMERN
public void setGuess(String value) {
count++;
try {
guess = Integer.parseInt(value);
} catch (NumberFormatException e) {
guess = -1;
hint = "a number next time";
return;
}
correct = (guess == number);
if (! correct) {
hint = (guess < number) ? "higher" : "lower";
}
}
public String getGuess() {
return "" + guess;
}
public boolean isCorrect() {
return correct;
}
public String getHint() {
return hint;
}
public int getCount() {
return count;
}
}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/xhtml1-strict.dtd">
<%@ page import="GuessNumberBean" %>
<jsp:useBean id="gnb" class="GuessNumberBean" scope="session"/>
<jsp:setProperty name="gnb" property="*"/>
<html lang="en">
<head>
<title>Guess Numbers</title>
</head>
<body bgcolor="white">
<% if (gnb.isCorrect()) { %>
<p>
Congratulations! You found the correct number
<%= gnb.getGuess() %> after just
<%= gnb.getCount() %> tries.
</p>
<p>
<a href="GuessNumber.jsp">Try again</a>?
</p>
211
212
KAPITEL 12. SERVLETS
<% } else if (gnb.getCount() == 0) { %>
<p>Welcome to the Guess Number game.</p>
<p>I am thinking of a number between 1 and 100.</p>
<form method="get">
Your guess: <input type="text" name="guess">
<input type="submit" value="submit">
</form>
<% } else { %>
<p>Your guess <%= gnb.getGuess() %> is good, but not quite
correct. Try <%= gnb.getHint() %>.</p>
<p>You have made <%= gnb.getCount() %> guesses so far.</p>
<form method="get">
Your guess: <input type="text" name="guess">
<input type="submit" value="submit">
</form>
<% } %>
</body>
</html>
12.9
Tomcat Referenzimplementation
Im Rahmen des Jakarta-Projekts ist die Tomcat Referenzimplementation entstanden.
• Tomcat kann sowohl eigenständig betrieben werden oder in Kombination mit anderen WebServern wie z.B. Apache.
• Komplexe Web-basierte Applikationen können in sogenannte Web Application Resource
(WAR) Dateien gepackt werden und damit relativ einfach verteilt und installiert werden.
Kapitel 13
CORBA
Die Common Object Request Broker Architecture (CORBA) [15, 19] wird seit 1989 durch die
Object Management Group (OMG) entwickelt und standardisiert. Hinter der OMG verbirgt sich ein
Zusammenschluß von Herstellern, die durch die Schaffung einer gemeinsamen objekt-orientierten
Kommunikations-Middleware die Interoperabilität von Produkten erreichen wollen.
13.1
CORBA Grundlagen
Die Object Management Architecture (OMA) bildet die Grundlage von CORBA.
Common Facilities
Dist. Doc.
Inf. Mgmt.
Application Objects
Sys. Mgmt.
...
Object Request Broker
...
Naming
Events
Trader
Persistence
Security
Common Object Services (COS)
Abbildung 13.1: Object Management Architecture (OMA)
Sie setzt sich aus vier Komponenten zusammen:
• Der Object Request Broker (ORB) bildet den Kern des Architekturmodells. Der ORB ermöglicht es einem Objekt, Anfragen an andere verteilte Objekte zu stellen bzw. Anfragen von
anderen verteilten Objekten zu empfangen. Der ORB sorgt dabei für Transparenz bezüglich
der tatsächlichen Implementation der Objekte und sichert damit die Interoperabilität.
213
214
KAPITEL 13. CORBA
• Die Object Services stellen eine Menge von grundlegenden Diensten bereit, die zur Implementation und Benutzung von CORBA Objekten nützlich sind. Die in den Object Services
bereitgestellten Dienste sind allgemein sinnvoll und unabhängig von Anwendungsgebieten.
• Die Common Facilities sind eine Sammlung von Diensten, die für eine Menge von Applikationen in einem Problembereich nützlich sind. Im Gegensatz zu Object Services sind Common Facilities nicht so grundlegend und auf bestimmte Problembereiche zugeschnitten.
• Die Application Objects sind die Objekte, die letztlich von Programmierern als Teil von Produkten oder aber im Rahmen von studentischen Arbeiten bzw. Praktika implementiert werden. Die Bedeutung dieser Application Objects wird (natürlich) von der OMG nicht definiert.
13.2 Object Request Broker (ORBs)
Ein Object Request Broker (ORB) bildet die Infrastruktur, mit der Dienste von einem Objekt in
Anspruch genommen werden können. Der Zugriff auf diese Objekte ist in allen Fällen transparent
und unabhängig davon ob die Objekte über mehrere Rechner verteilt sind oder lokal in einem einzigen Prozeß existieren. Der ORB übernimmt die Lokalisierung eines Objekts, die Übertragung und
Anpassung eines Aufrufs an das Zielobjekt sowie die Übertragung der Ergebnisse oder Ausnahmen
an den Aufrufer.
13.3 Object Services
Die Object Services stellen Hilfsdienste bereit, ohne die verteilte Applikationen nicht entwickelt
werden können. Die Object Services gehören zur Grundfunktionalität einer CORBA Implementation und müssen in allen CORBA-konformen Systemen vorhanden sein. Die im folgenden kurz
beschriebenen CORBA Services sind lediglich eine kleine Auswahl der standardisierten Object
Services:
• Der Naming Service ist ein Dienst, mit dem Objekten Namen zugeordnet werden können.
Jeder Name existiert in einem Kontext, der selbst wiederum einen Namen haben kann. Der
Namensraum ist also hierarchisch strukturiert.
• Der Event Service erlaubt es, Nachrichten über eingetretene Ereignisse asynchron einem oder
mehreren Empfängern zuzustellen. Entsprechend wird zwischen Erzeugern und Verbrauchern
von Nachrichten unterschieden. Die Nachrichten selbst werden durch sogenannte Kanäle zugestellt, wobei ein Nachrichten-Verbraucher nicht notwendigerweise aktiv sein muß, während
eine Nachricht in einem Kanal eingebracht wird.
• Der Life Cycle Service stellt Dienste bereit, mit denen der Lebenslauf eines Objekts kontrolliert werden kann. Der Life Cycle Service umfaßt insbesondere Dienste zum Erzeugen,
Löschen, Kopieren oder Verschieben von Objekten.
13.4
Common Facilities
Neben den Object Services, die grundlegende Basisfunktionen bereitstellen, gibt es weitere standardisierte Dienste, die in speziellen Anwendungsbereichen sinnvoll sind.
215
13.5. KOMPONENTEN EINES ORBS
13.5 Komponenten eines ORBs
Das erklärte Ziel ist die möglichst transparente Kommunikation zwischen verteilten Objekten, die in
verschiedenen Sprachen implementiert sein können und auf verschiedenen Betriebssystemen ablaufen. Die Unabhängigkeit von den verwendeten Programmiersprachen wird durch eine Beschreibung
der Objekt-Schnittstellen in einer neutralen Sprachen, der CORBA Interface Definition Language,
erreicht. Neben dieser IDL gibt es weitere CORBA-Komponenten.
Client
Interface
Object Implementation
Dynamic
IDL
ORB
Invocation
Stubs
Interface
Static IDL
Dynamic
Skeleton
Skeleton
Object
Adapter
Repository
Implementation
Repository
ORB Core
Abbildung 13.2: Komponenten eines Object Request Brokers (ORB)
• Das Dynamic Invocation Interface erlaubt die Konstruktion von Aufrufen zur Laufzeit.
• Die IDL Stubs werden mit Hilfe eines Compilers aus einer IDL-Definition erzeugt und realisieren statische Stub-Prozeduren auf der Client-Seite.
• Das ORB Interface stellt generelle Infrastruktur zur Verfügung.
• Die Static IDL Skeleton werden ebenfalls mit Hilfe eines Compilers aus einer IDL-Definition
erzeugt und realisieren statische Stub-Prozeduren auf der Server-Seite.
• Das Dynamic Skeleton Interface erlaubt die Konstruktion von Objektimplementationen zur
Laufzeit.
• Der Objektadapter übernimmt das Erzeugen, Starten, Beenden und Löschen von Objektimplementationen.
• Informationen über die bekannten IDL-Schnittstellen werden im Interface Repository verwaltet.
• Informationen über die bekannten Implementationen werden im Implementation Repository
verwaltet.
13.6
Interface Definition Language (IDL)
Die Interface Definition Language (IDL) ist eine formale Sprache, mit der die von außen sichbare
Schnittstelle eines Objekts definiert wird. Eine IDL-Schnittstelle muß alle Informationen enthalten, die zur Benutzung des durch ein Objekt realisierten Dienstes notwendig ist. Die IDL ist eine
abstrakte deklarative Sprache, die keine konkrete Datendarstellung impliziert oder eine bestimmte
216
KAPITEL 13. CORBA
Implementation eines Objekts festlegt. IDL-Definitionen werden mit Hilfe von speziellen Übersetzern auf übliche Programmiersprachen abgebildet. So existieren beispielsweise standardisierte
Sprachabbildungen für C, C++, Smalltalk oder Java. (Dabei ist zu beachten, daß für einige Sprachen
Einschränkungen existieren. Beispielsweise verfügt Java über keine vorzeichenlosen Zahlentypen,
die aber in der IDL existieren. Die Spezifikation überläßt es in solchen Fällen dem Programmierer,
die sich daraus ergebenden Probleme korrekt zu behandeln.)
IDL-Definitionen befinden sich in sogenannten Modulen. Ein Modul definiert einen Namensraum.
Alle Bezeichner in einem Modul müssen eindeutig sein. Ein Modul enthält neben Definitionen von
Datentypen, Konstanten und Exceptions im wesentlichen Definitionen von sogenannten Interfaces.
Ein Interface definiert eine CORBA Objektklasse, die durch die Menge der Attribute und Operationen beschrieben wird. Ein Interface kann von einem anderen Interface abgeleitet werden, wobei die
Attribute und Operationen an die abgeleitete Klasse vererbt werden.
Achtung: Bezeichner, die sich nur in der Gross-/Kleinschreibung unterscheiden, sind in der IDL
identisch. Die Definitionen
typedef long Foo;
const Foo foo = 1;
kollidieren daher im Namensraum und stellen einen Fehler dar.
13.6.1
Typsystem
object reference
basic types
constructed types
short
long
long long
unsigned short
unsigned long
unsigned long long
float
double
long double
fixed
char
wchar
string
wstring
boolean
octet
enum
any
struct
sequence
union
array
Abbildung 13.3: Datentypen der Interface Definition Language (IDL)
• Die Datentypen short, long und long long sind ganze vorzeichenbehaftete Zahlen (16,
32 bzw. 64 Bit).
13.6. INTERFACE DEFINITION LANGUAGE (IDL)
217
• Die Datentypen unsigned short, unsigned long und unsigned long long sine ganze vorzeichenlose Zahlen (16, 32 bzw. 64 Bit).
• Die Datentypen float, double und long double entsprechen den IEEE 754-1985 einfachen, doppelten und den erweiterten doppelten Gleitpunktzahlen.
• Der Datentyp char ist ein 8-Bit Zeichen während der Datentyp wchar ein Zeichen in mehreren Bytes halten kann.
• Analog ist der Datentyp string eine Zeichenkette bestehend aus 8-Bit Zeichen während
ein wstring aus Zeichen bestehen kann, die in mehreren Bytes gehalten kann.
• Der Datentyp enum definiert eine Aufzählung.
• Der Datentyp octet repräsentiert ein einzelnes Byte, das während einer Übertragung niemals verändert wird.
• Der Datentyp any kann den Wert eines beliebigen anderen Datentyps enthalten, wobei die
Wert mit einem sogenannten TypeCode assoziiert ist.
• Datentypen können zu einer Struktur struct zusammengefaßt werden. Die Mitglieder der
Struktur (member) können selbst wieder zusammengesetzte Datentypen sein.
• Der Datentyp union stellt eine Vereinigung dar, wobei das Unterscheidungsmerkmal benannt werden muß (discriminated union).
• Eine sequence ist eine Sequenz von einem Datentyp und entspricht einem Feld oder Array.
13.6.2
Konstanten
Konstanten können mit Hilfe von Ausdrücken definiert werden, die sehr ähnlich zu Ausdrücken in
Sprachen wie Java oder C/C++ sind. Konstanten sind nur für die Basis-Datentypen vorgesehen.
<const_dcl> ::= "const" <const_type> <identifier> "=" <const_exp>
13.6.3
Ausnahmen
Die Definition von Ausnahmen (exceptions) ähnelt der Definition von Strukturen. Die Mitglieder
(member) einer Exception beschreiben eine Ausnahmesituation genauer.
<except_dcl> ::= "exception" <identifier> "{" <member>* "}"
13.6.4
Interfaces
Ein Interface definiert die Schnittstelle für ein CORBA-Objekt und legt die Signaturen der Operationen und die Attribute des Objekts fest.
218
KAPITEL 13. CORBA
<interface_dcl>
::= <interface_header> "{" <interface_body> "}"
<interface_header> ::= [ "abstract" | "local" ] "interface" <identifier>
[ <interface_inheritance_spec> ]
<interface_inheritance_spec>
::= ":" <interface_name> { "," <interface_name> }*
<interface_name>
::= <scoped_name>
<interface_body>
<export>
::= <export>*
::=
<type_dcl> ";"
| <const_dcl> ";"
| <except_dcl> ";"
| <attr_dcl> ";"
| <op_dcl> ";"
• In einem Interface können auch Datentypen, Konstante und Ausnahmen definiert werden, die
dann nur in dem Sichtbarkeitsbereich (scope) des Interfaces gültig sind.
• Ein Interface kann von mehreren anderen Interfaces abgeleitet werden (Mehrfachvererbung).
Operationen
In der CORBA-IDL werden lediglich die signaturen von Operationen beschrieben. Es gibt keine
Mechanismen in der IDL, um die Semantik einer Operation zu programmieren“.
”
<op_dcl> ::= [<op_attribute>] <op_type_spec> <identifier>
<parameter_dcls> [<raises_expr>] [<context_expr>]
<op_attribute> ::= "oneway"
<op_type_spec> ::= <param_type_spec> | "void"
<parameter_decls>
<param_decl>
<param_attribute>
<param_type_spec>
::=
::=
::=
::=
"(" <param_decl> { "," <param_decl> }* ")"
<param_attribute> <param_type_spec> <simple_declarator>
"in" | "out" | "inout"
<base_type_spec>
<raises_expr> ::= "raises" "(" <scoped_name> { "," <scoped_name> }* ")"
<context_expr> ::= "context" "(" <string_literal> { "," <string_literal> }* ")"
• Die Parameter-Attribute zeigen on, dass ein Parameter vom Client zum Server übertragen
wird (in), ein Parameter vom Server zum Client übertragen wird (out), oder ein Parameter
in beide Richtungen übertragen wird (inout).
• Die Exceptions, die beim Aufruf einer Operation auftreten können, werden durch die optionale raises-Klausel angegeben.
• Kontextinformation des Client können der Objektimplementation zur Verfügung gestellt, um
z.B. das Leistungsverhalten einer Objektimplementation zu beeinflussen.
13.7. OBJEKT-REFERENZEN
219
Attribute
Ein Interface kann Attribute haben. Die Definition eines Attributs ist äquivalent zur Definition von
zwei Zugriffsfunktionen zum Lesen und Schreiben.
<attr_dcl> ::= ["readonly"] "attribute" <param_type_spec>
<simple_declarator> { "," <simple_declarator> }*
13.6.5
Module
Ein Modul erzeugt einen Namensraum für eine Menge von Definitionen. Module können ineinander
geschachtelt werden, wodurch ein hierarchischer Namensraum entsteht.
<module> ::= "module" <identifier> "{" <definition>+ "}"
13.7 Objekt-Referenzen
Damit ein Client ein CORBA-Objekt aufrufen kann, muß der Client zunächst eine Referenz auf das
Objekt (object reference) besitzen. Das genaue Format der Objekt-Referenzen ist nicht festgelegt
und zwei verschiedene Implementationen können unterschiedliche Formate benutzen. Eine ausgegebene Objekt-Referenz ist auch nur für die Lebenszeit des Clients gültig, der sie bekommen hat.
Es gibt eine spezielle ausgezeichnete Objekt-Referenz, die kein Objekt referenziert.
• Eine stringified object reference ist eine ASCII-Darstellung einer Objekt-Referenz.
13.8 Java Language Mapping
Die Abbildung der CORBA-IDL auf die Implementationssprache Java ist in [14] definiert. Die
Abbildung ist in vielen Teilen relativ einfach, da Java der IDL sehr nahe kommt. Allerdings gibt es
in einige Details auch durchaus Überraschungen.
• IDL-Module werden auf Java-Packages abgebildet.
• Die Basis-Datentypen werden entsprechend Tabelle 13.1 auf Java-Datentypen abgebildet.
Achtung: Bei vorzeichenlosen Zahlen können plötzlich negative Werte in Java auftreten.
• Strukturen und Vereinigungen werden auf Java Klassen abgebildet.
• Eine Sequenz wird auf ein Array in Java abgebildet.
• Aufzählungen (enum) werden auf Java Klassen abgebildet, die entsprechende Konstanten
definieren und Methoden zur Umwandlung bereitstellen.
• Für jedes Interface werden zwei Java Interfaces erzeugt. Das erste Interface modelliert die
Operationen und das zweite davon abgeleitete Interface den Rest.
• IDL-Exceptions werden auf Java Klassen abgebildet, die indirekt von java.lang.Exception
abgeleitet sind.
220
KAPITEL 13. CORBA
IDL Datentyp
boolean
char
wchar
octet
string
wstring
short
unsigned short
long
unsigned long
long long
unsigned long long
float
double
fixed
Java Datentyp
boolean
char
char
byte
java.lang.String
java.lang.String
short
short
int
int
long
long
float
double
java.math.BigDecimal
Tabelle 13.1: Abbildung der Basis-Datentypen
• Für definierte Datentypen werden sogenannte Holder Classes erzeugt, die zur Realisierung
von out und inout Parametern benötigt werden.
• Zur Einschränkung von Objekt-Referenzen auf konkrete Java Objekte werden sogenannte
Helper Classes erzeugt.
Das folgende Beispiel definiert ein IDL-Interface Hello mit der Operation sayHello() in dem
Modul HelloModule.
module HelloModule {
interface Hello {
string sayHello();
};
};
Die Übersetzung des IDL Moduls nach Java liefert die in Abbildung 13.4 dargestellten Klassen.
Der Quellcode für einen einfachen Client:
import
import
import
import
import
import
import
HelloModule.Hello;
HelloModule.HelloHelper;
org.omg.CORBA.ORB;
org.omg.CORBA.UserException;
org.omg.CosNaming.NameComponent;
org.omg.CosNaming.NamingContext;
org.omg.CosNaming.NamingContextHelper;
public class HelloClient
{
221
13.8. JAVA LANGUAGE MAPPING
«interface»
HelloOperations
«interface»
Object
HelloHelper
«interface»
Hello
«interface»
IDLEntity
_HelloStub
ObjectImpl
_HelloImplBase
Abbildung 13.4: Klassendiagramm für die von Hello.idl erzeugten Java Klassen
public static void main(String args[])
{
try {
org.omg.CORBA.Object obj;
NamingContext ncxt;
ORB orb = ORB.init(args, null);
obj = orb.resolve_initial_references("NameService");
ncxt = NamingContextHelper.narrow(obj);
NameComponent nc = new NameComponent("Hello", "");
NameComponent path[] = { nc };
Hello helloRef = HelloHelper.narrow(ncxt.resolve(path));
String hello = helloRef.sayHello();
System.out.println(hello);
} catch (UserException e) {
e.printStackTrace();
System.exit(1);
}
}
}
Der Quellcode für einen einfachen Server:
import
import
import
import
import
import
HelloModule._HelloImplBase;
org.omg.CORBA.ORB;
org.omg.CORBA.UserException;
org.omg.CosNaming.NameComponent;
org.omg.CosNaming.NamingContext;
org.omg.CosNaming.NamingContextHelper;
public class HelloServer
{
public static void main(String args[])
throws java.lang.InterruptedException
{
222
KAPITEL 13. CORBA
try {
org.omg.CORBA.Object obj;
ORB orb = ORB.init(args, null);
HelloServant hello = new HelloServant();
orb.connect(hello);
obj = orb.resolve_initial_references("NameService");
NamingContext ncxt = NamingContextHelper.narrow(obj);
NameComponent nc = new NameComponent("Hello", "");
NameComponent path[] = { nc };
ncxt.rebind(path, hello);
// Wait for something not to happen to keep the ORB alive.
java.lang.Object sync = new java.lang.Object();
synchronized(sync){
sync.wait();
}
} catch (UserException e) {
e.printStackTrace();
System.exit(1);
}
}
}
class HelloServant extends _HelloImplBase
{
public String sayHello() {
return "Hello World.";
}
}
13.9
Naming Service
Der CORBA Naming Service [] ist ein grundlegender Common Object Service zur Zuordnung und
Verwaltung von Namen von Objekten. Der Namensraum ist hierarchisch strukturiert und besteht
aus internen Knoten (naming contexts) und Blättern (name bindings).
• Die Zuordnung eines Namens zu einem CORBA-Objekt wird durch ein Binding dargestellt.
• Ein NamingContext Objekt verwaltet Bindings und andere NameContexts.
• Der Name eines NamingContexts bzw. eines Bindings wird als NameComponent bezeichnet
und besteht aus zwei Zeichenfolgen: einem String zur Identifikation und einem String, der
die Art des Bindings näher beschreibt.
• Eine Sequenz von NameComponents beschreibt einen Pfad im Namensraum.
13.9. NAMING SERVICE
223
• Mit Hilfe eines BindingIterators kann man über die Elemente in einem NamingContext iterieren. Achtung: Der Iterator existiert auf dem Name Server und sollte tunlichst nach Gebrauch
gelöscht werden. Name Server dürfen auch selbständig Iteratoren löschen, woraus ein Client
sinnvoll reagieren sollte.
• Pfade können als stringified names dargestellt werden, wobei NameComponents durch ’/’
voneinander getrennt werden und die Identifikation und die Art eines Bindings durch einen
Punkt getrennt werden (z.B. maumau/player1.mau).
Der CORBA Naming Service ist in folgendem IDL-Modul spezifiziert:
#ifndef _COS_NAMING_IDL_
#define _COS_NAMING_IDL_
#pragma prefix "omg.org"
module CosNaming
{
typedef string Istring;
struct NameComponent {
Istring id;
Istring kind;
};
typedef sequence <NameComponent> Name;
enum BindingType { nobject, ncontext };
struct Binding {
Name
binding_name;
BindingType binding_type;
};
// Note: In struct Binding, binding_name is incorrectly defined
// as a Name instead of a NameComponent. This definition is
// unchanged for compatibility reasons.
typedef sequence <Binding> BindingList;
interface BindingIterator;
interface NamingContext {
enum NotFoundReason { missing_node, not_context, not_object };
exception NotFound {
NotFoundReason why;
Name rest_of_name;
};
exception CannotProceed {
NamingContext cxt;
Name rest_of_name;
};
224
KAPITEL 13. CORBA
exception InvalidName{};
exception AlreadyBound{};
exception NotEmpty{};
void bind(in Name n, in Object obj)
raises(NotFound, CannotProceed,
InvalidName, AlreadyBound);
void rebind(in Name n, in Object obj)
raises(NotFound, CannotProceed, InvalidName);
void bind_context(in Name n, in NamingContext nc)
raises(NotFound, CannotProceed,
InvalidName, AlreadyBound);
void rebind_context(in Name n, in NamingContext nc)
raises(NotFound, CannotProceed, InvalidName);
Object resolve(in Name n)
raises(NotFound, CannotProceed, InvalidName);
void unbind(in Name n)
raises(NotFound, CannotProceed, InvalidName);
NamingContext new_context();
NamingContext bind_new_context(in Name n)
raises(NotFound, AlreadyBound,
CannotProceed, InvalidName);
void destroy()
raises(NotEmpty);
void list(in unsigned long how_many,
out BindingList bl,
out BindingIterator bi);
};
interface BindingIterator {
boolean next_one(out Binding b);
boolean next_n(in unsigned long how_many, out BindingList bl);
void destroy();
};
};
#endif /* ifndef _COS_NAMING_IDL_ */
13.10
Mau Mau
In diesem Abschnitt soll ein verteiles Mau-Mau-Spiel realisiert werden. Die IDL-Schnittstelle sieht
wie folgt aus:
/*
225
13.10. MAU MAU
* This file defines the CORBA IDL interface for playing the famous
* MauMau card game. Everyone is invited to write his/her own player.
*
* (c) 1998 TU Braunschweig
(Juergen Schoenwaelder, Ralph Wittmann)
* (c) 2001 Uni Osnabrueck
(Juergen Schoenwaelder)
*/
module MauMau {
enum Color { square, heart, spade, club };
enum Value { seven, eight, nine, ten, jack, queen, king, ace };
struct Card {
Color color;
Value value;
};
const unsigned short number_colors = 4;
const unsigned short number_values = 8;
const unsigned short number_cards = 32;
/*
* The following definitions define the interface for a CORBA MauMau
* player object. The Player interface defines the following operations:
*
* - The start() operation is called to initialize the player object
*
for a new game.
*
* - The take() operation is called to send cards to a player object
*
(this can happen when the game starts or during the game).
*
* - The play() operation is called whenever the player has to play
*
a card. The result of the play() operation is either the value
*
‘serve’ (this is the normal case) or ‘skip’ (the player could
*
not play a card).
*
* - The info() operation is called after each turn in order to inform
*
all players about what has happened.
*
* - The bye() operation is called to end a game.
*/
enum Status { serve, skip };
interface Player {
void start(in unsigned short players,
in unsigned short ident);
// # players in the game
// # assigned to this player
void take(in Card card);
// card you have to take
Status play(in Card stack,
in unsigned short age,
out Card card,
// card on the stack
// age of card on the stack
// card played (if any)
226
KAPITEL 13. CORBA
inout Color color);
void info(in
in
in
in
in
unsigned short player,
Card stack,
unsigned short age,
Color color,
unsigned short taken);
oneway void bye(in string s);
// new color (if any)
//
//
//
//
//
identity of the player
card on the stack
age of card on the stack
new color (if any)
# cards taken or 0
// reason phrase
};
};
Die Implementation des Spielverwalters und der Spieler erfolgt nach folgenden Klassendiagramm:
«interface»
PlayerOperations
+start()
+take()
+play(): Status
+info()
+bye()
«interface»
_PlayerImplBase
Player
Status
PlayerServer 1
n
PlayerServant
-pile: Vector
Server Implementation
1
Color
1
Value
Card
+color: Color n
+value: Value n
Game
+players: Player[]
+deck: OwnedCard[]
+Game(NamingContext)
+start()
+end()
+run()
OwnedCard
-owner: short
-card: Card
Client Implementation
Abbildung 13.5: Klassendiagramm für das Mau-Mau-Spiel
Die Interaktionen zwischen Spielern und dem Spielverwalter, die durch die IDL-Schnittstelle festgelegt sind, sind in folgendem Sequenzdiagramm dargestellt:
227
13.10. MAU MAU
aGame
start()
aPlayer1
aPlayer2
aPlayer3
start()
start()
start()
* take(Card)
* take(Card)
* take(Card)
run()
play()
info()
info()
info()
play()
end()
bye()
bye()
bye()
Abbildung 13.6: Sequenzdiagramm für das Mau-Mau-Spiel
Die Implementation der Klasse PlayerServant:
import
import
import
import
import
import
import
import
import
MauMau._PlayerImplBase;
MauMau.Card;
MauMau.CardHolder;
MauMau.Color;
MauMau.ColorHolder;
MauMau.Value;
MauMau.Status;
java.util.Vector;
java.util.Enumeration;
class PlayerServant extends _PlayerImplBase
{
private short ident;
private short players;
228
KAPITEL 13. CORBA
private Vector pile;
public PlayerServant()
{
ident = 0;
players = 0;
pile = null;
}
/**
* A new game is about to start. Initialize the pile of
* cards.
*/
public void start(short players, short ident)
{
this.players = players;
this.ident = ident;
pile = new java.util.Vector();
}
/**
* Take a card.
*/
public void take(Card card)
{
pile.addElement(card);
}
/**
* Play a card making sure we honor the rules of the game.
* Everything else is stupid - playing the first suitable card we
* find without any sophisticated strategies.
*/
public Status play(Card stack, short age,
CardHolder ch, ColorHolder wh)
{
Card c;
if (stack.value == Value.seven && age == 0) {
c = find(Value.seven);
if (c != null) {
return serve(c, ch, wh);
}
return skip(stack, ch, wh);
}
if (stack.value == Value.eight && age == 0) {
c = find(Value.eight);
if (c != null) {
return serve(c, ch, wh);
}
13.10. MAU MAU
229
return skip(stack, ch, wh);
}
if (stack.value == Value.jack) {
for (Enumeration e = pile.elements(); e.hasMoreElements();) {
c = (Card) e.nextElement();
if ((c.color == wh.value && c.value != Value.jack)
|| (c.value == Value.jack && age > 0)) {
return serve(c, ch, wh);
}
}
return skip(stack, ch, wh);
}
for (Enumeration e = pile.elements(); e.hasMoreElements();) {
c = (Card) e.nextElement();
if (c.value == Value.jack) continue;
if (c.value == stack.value || c.color == stack.color) {
return serve(c, ch, wh);
}
}
c = find(Value.jack);
if (c != null) {
return serve(c, ch, wh);
}
return skip(stack, ch, wh);
}
/**
* Information how the game proceeds. We choose to ignore it.
*/
public void info(short player, Card stack, short age,
Color color, short taken)
{
// This is a dumb player who ignores info about the game.
}
/**
* The game is over. Drop the pile of cards.
*/
public void bye(String s)
{
pile = null;
}
/**
* Helper method to skip. Note that we still have to initialize
* the holder classes.
*/
230
KAPITEL 13. CORBA
private Status skip(Card c, CardHolder ch, ColorHolder wh)
{
ch.value = c;
wh.value = c.color;
return Status.skip;
}
/**
* Helper method to serve a card by removing it from the pile
* of cards we have and updating the holder classes.
*/
private Status serve(Card c, CardHolder ch, ColorHolder wh)
{
pile.removeElement(c);
ch.value = c;
wh.value = c.color;
return Status.serve;
}
/**
* Helper method to find a card in our pile with a given value.
*/
private Card find(Value value)
{
for (Enumeration e = pile.elements(); e.hasMoreElements();) {
Card c = (Card) e.nextElement();
if (c.value == value) {
return c;
}
}
return null;
}
}
Die Implementation der Klasse PlayerServer:
import
import
import
import
import
import
import
import
import
org.omg.CORBA.ORB;
org.omg.CORBA.UserException;
org.omg.CosNaming.NameComponent;
org.omg.CosNaming.NamingContext;
org.omg.CosNaming.NamingContextHelper;
org.omg.CosNaming.NamingContextPackage.NotFound;
org.omg.CosNaming.NamingContextPackage.CannotProceed;
org.omg.CosNaming.NamingContextPackage.InvalidName;
org.omg.CosNaming.NamingContextPackage.AlreadyBound;
public class PlayerServer
{
static final int numPlayers = 3;
static final String context = "maumau";
static NamingContext addContext(NamingContext ncxt, String id, String kind)
13.10. MAU MAU
231
throws NotFound, CannotProceed, InvalidName, AlreadyBound
{
NameComponent nc = new NameComponent(id, kind);
NameComponent path[] = { nc };
return ncxt.bind_new_context(path);
}
static void addBinding(NamingContext ncxt, String id,
String kind, org.omg.CORBA.Object obj)
throws NotFound, CannotProceed, InvalidName
{
NameComponent nc = new NameComponent(id, kind);
NameComponent path[] = { nc };
ncxt.rebind(path, obj);
}
public static void main(String args[])
throws java.lang.InterruptedException
{
try {
org.omg.CORBA.Object obj;
NamingContext ncxt;
ORB orb = ORB.init(args, null);
obj = orb.resolve_initial_references("NameService");
ncxt = NamingContextHelper.narrow(obj);
PlayerServant players[] = new PlayerServant[numPlayers];
try {
ncxt = addContext(ncxt, context, "");
} catch (Exception AlreadyBound) {};
for (int i = 0; i < numPlayers; i++) {
players[i] = new PlayerServant();
orb.connect(players[i]);
addBinding(ncxt, "simple player " + i, "", players[i]);
}
java.lang.Object sync = new java.lang.Object();
synchronized(sync){
sync.wait();
}
} catch (UserException e) {
e.printStackTrace();
System.exit(1);
}
}
}
Die Implementation der Klasse Game:
import
import
import
import
MauMau.Player;
MauMau.PlayerHelper;
MauMau.Card;
MauMau.CardHolder;
232
KAPITEL 13. CORBA
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
MauMau.Color;
MauMau.ColorHolder;
MauMau.Value;
MauMau.Status;
OwnedCard;
org.omg.CORBA.ORB;
org.omg.CORBA.UserException;
org.omg.CosNaming.NameComponent;
org.omg.CosNaming.NamingContext;
org.omg.CosNaming.NamingContextHelper;
org.omg.CosNaming.BindingHolder;
org.omg.CosNaming.BindingListHolder;
org.omg.CosNaming.BindingIteratorHolder;
java.util.Vector;
java.util.Enumeration;
public class Game
{
static final String context = "maumau";
/**
* Exception which is thrown if a player tries to play
* an illegal card.
*/
public class IllegalCardException extends Exception
{
public IllegalCardException()
{
super();
}
public IllegalCardException(String s)
{
super(s);
}
}
private
private
private
private
private
private
OwnedCard deck[];
Card stack;
Color color;
short age;
boolean verbose;
Player players[];
//
//
//
//
//
//
Owned cards in the game.
Card currently on the stack.
Color requested.
Age of the card on the deck.
Generate verbose output if true.
Players in a game.
private static final Color colors[] = { Color.square, Color.heart,
Color.spade, Color.club };
private static final Value values[] = { Value.seven, Value.eight,
Value.nine, Value.ten,
Value.jack, Value.queen,
Value.king, Value.ace };
static final short TALON = -1;
static final short PILE = -2;
// card is in the talon
// card is on the stack
233
13.10. MAU MAU
static final short INITIAL = 3;
// initial number of cards per player
/**
* Initialize the game by obtaining the players from the naming
* service and initializing the deck of cards.
*/
public Game(NamingContext ncxt)
{
initPlayers(ncxt);
initDeck();
verbose = true;
}
/**
* Initialize the deck by creating a new set of cards and
* assigning them to the talon. Pick a random card and put it on
* the stack.
*/
private void initDeck() {
short l;
short i = 0;
deck = new OwnedCard[colors.length * values.length];
for (int c = 0; c < colors.length; c++) {
for (int v = 0; v < values.length; v++) {
Card card = new Card(colors[c], values[v]);
deck[i++] = new OwnedCard(card, TALON);
}
}
l = next();
stack = deck[l].card;
deck[l].owner = PILE;
}
/**
* Initialize the players by iterating through the naming context
* and resolving the names to players.
*/
private void initPlayers(NamingContext ncxt)
{
short i;
java.util.Vector v = new java.util.Vector();
boolean ok;
BindingHolder bh = new BindingHolder();
BindingListHolder blh = new BindingListHolder();
BindingIteratorHolder bih = new BindingIteratorHolder();
ncxt.list(0, blh, bih);
234
KAPITEL 13. CORBA
for (ok = bih.value.next_one(bh); ok; ok = bih.value.next_one(bh)) {
try {
org.omg.CORBA.Object obj = ncxt.resolve(bh.value.binding_name);
Player p = PlayerHelper.narrow(obj);
v.add(p);
} catch (UserException e) {
e.printStackTrace();
System.exit(1);
}
}
bih.value.destroy();
players = new Player[v.size()];
v.toArray(players);
}
/**
* Count the number of cards owned by a given player.
*/
private int count(short player)
{
int cnt = 0;
for (int i = 0; i < deck.length; i++) {
if (deck[i].owner == player) {
cnt++;
}
}
return cnt;
}
/**
* Move all cards in the pile except the card on top of the stack
* back to the talon. Note: We do not really shuffle the cards
* here - instead we pick a random card when we need one.
*/
public void shuffle(Card stack)
{
for (int i = 0; i < deck.length; i++) {
if (deck[i].owner == PILE
&& deck[i].card != stack) {
deck[i].owner = TALON;
}
}
}
/**
* Pick the next random card from the talon. Simply pick random
* cards unitl we have found one which is in the talon.
*/
public short next()
13.10. MAU MAU
235
{
if (count(TALON) == 0) {
shuffle(stack);
if (count(TALON) == 0) {
return -1;
// throw an exception here?
}
}
while (true) {
short k = (short) (Math.random()
* (colors.length * values.length));
if (deck[k].owner == TALON) {
return k;
}
}
}
/**
* Show which card is owned by whom.
*/
public void show(short player)
{
if (verbose) {
System.out.print(player + ":");
for (int i = 0; i < deck.length; i++) {
if (deck[i].owner == player) {
System.out.print(" " + deck[i].card);
}
}
System.out.println();
}
}
/**
* Give a number of cards to a player.
*/
public short give(short player, short count)
{
for (short l = 0; l < count; l++) {
short k = next();
if (k < 0 || k >= deck.length) {
return l;
}
deck[k].owner = player;
players[player].take(deck[k].card);
if (verbose) {
System.out.println(player + ": receives card " + deck[k].card);
}
}
return count;
}
236
KAPITEL 13. CORBA
/**
* Start the game by calling the start operation for each player
* and dispensing the initial number of cards to each player.
*/
public void start()
{
for (short i = 0; i < players.length; i++) {
players[i].start((short) players.length, i);
}
for (short i = 0; i < players.length; i++) {
give(i, INITIAL);
}
}
/**
* End the game by calling the bye operation for each player.
*/
public void end(String s)
{
if (verbose) {
System.out.println(s);
}
for (short i = 0; i < players.length; i++) {
players[i].bye(s);
}
}
/**
* Send about information to all players.
*/
public void info(short player, short taken)
{
for (short i = 0; i < players.length; i++) {
players[i].info(player, stack, age, color, taken);
}
}
/**
* Check whether a given response is valid or whether someone is
* cheating by playing e.g. with stolen cards.
*/
private void check(short player, Card card) throws IllegalCardException
{
if (stack.value == Value.jack) {
if (age == 0) {
if (card.value == Value.jack) {
throw new IllegalCardException(
player + ": not allowed to put a jack on a jack");
}
13.10. MAU MAU
237
} else {
if (card.value != Value.jack && card.color != color) {
throw new IllegalCardException(
player + ": must serve with the requested color");
}
}
} else {
if (card.color != stack.color
&& card.value != stack.value && card.value != Value.jack) {
throw new IllegalCardException(
player + ": must serve color or value");
}
if ((stack.value == Value.seven
|| stack.value == Value.eight)
&& age == 0 && card.value != stack.value) {
throw new IllegalCardException(
player + ": must serve the value");
}
}
for (int k = 0; k < deck.length; k++) {
Card c = deck[k].card;
if (c.value == card.value && c.color == card.color) {
if (deck[k].owner != player) {
throw new IllegalCardException(
player + ": must play with his own cards");
}
deck[k].owner = PILE;
stack = card;
return;
}
}
throw new IllegalCardException(player + ": must play known card");
}
/**
* Run the game by asking each player in turn to play a card
* until one player has no cards left.
*/
public short run() throws IllegalCardException
{
short i;
short penalty = 0;
short taken = 0;
age = 0;
color = stack.color;
for (i = 0; true; i = (short) ((i+1) % players.length)) {
if (verbose) {
System.out.println();
238
KAPITEL 13. CORBA
System.out.print("S: " + stack);
if (stack.value == Value.jack) {
System.out.print(" (" + color + ")");
}
System.out.println();
show(i);
}
CardHolder ch = new CardHolder();
ColorHolder wh = new ColorHolder(color);
Status s = players[i].play(stack, age, ch, wh);
if (s == Status.skip) {
if (stack.value == Value.eight && age == 0) {
if (verbose) {
System.out.println(i + ": must pause");
}
} else if (stack.value == Value.seven && age == 0) {
if (verbose) {
System.out.println(i + ": must take "
+ penalty + " cards");
}
taken = give(i, penalty);
} else {
if (verbose) {
System.out.println(i + ": skips and must take 1 card");
}
taken = give(i, (short) 1);
}
penalty = 0;
age++;
}
if (s == Status.serve) {
if (ch.value.value == Value.jack) {
check(i, ch.value);
color = wh.value;
age = 0;
if (verbose) {
System.out.println(i + ": serves with " +
+ " and wishes color "
}
} else {
if (ch.value.value == Value.seven) penalty +=
check(i, ch.value);
age = 0;
if (verbose) {
System.out.println(i + ": serves with " +
}
}
}
info(i, taken);
stack
+ color);
2;
stack);
13.10. MAU MAU
if (count(i) == 0) {
return i;
}
}
}
/**
* Connect to the name service and run the game.
*/
public static void main(String args[])
{
NamingContext ncxt = null;
Game game = null;
short winner;
try {
org.omg.CORBA.Object obj;
ORB orb = ORB.init(args, null);
obj = orb.resolve_initial_references("NameService");
ncxt = NamingContextHelper.narrow(obj);
NameComponent nc = new NameComponent(context, "");
NameComponent path[] = {nc};
obj = ncxt.resolve(path);
ncxt = NamingContextHelper.narrow(obj);
} catch (UserException e) {
e.printStackTrace();
System.exit(1);
}
try {
game = new Game(ncxt);
game.start();
winner = game.run();
game.end("player " + winner + " wins the game");
} catch (IllegalCardException e) {
game.end(e.getMessage());
}
}
}
Die Implementation der Hilfsklasse OwnedCard:
import MauMau.Card;
import MauMau.Color;
import MauMau.Value;
public class OwnedCard
{
public Card card;
public short owner;
239
240
KAPITEL 13. CORBA
public OwnedCard(Card card, short owner) {
this.card = card;
this.owner = owner;
}
}
13.11
JDK’s Java IDL
Aus der JDK Dokumentation:
NOTE: Although it is true in theory that ORBs written in different languages should be
able to talk to each other, we haven’t tested the interoperability of the Java ORB with
other vendor’s ORBs.
Kapitel 14
Java Name and Directory Interface
(JNDI)
Namensdienste zur Zuordnung und Auflösung von Namen zu Objekten sind grundlegend für jedes
verteilte System. Es haben sich mittlerweile viele Namensdienste etabliert (z.B. DNS, LDAP, NIS,
CORBA Naming Service), die alle ähnliche Dienste anbieten.
Das Java Name and Directory Interface (JNDI) [20] definiert eine universelle Programmierschnittstelle, mit der auf beliebige Namens- und Verzeichnisdienste zugegriffen werden kann (Virtualisierung).
Java Application
JNDI API
JNDI Naming Manager
JNDI SPI
CORBA
Naming
RMI
LDAP
NDS
NIS
Abbildung 14.1: JNDI Architektur
Durch die Benutzung von der JNDI API sind Anwendungen nicht an spezifische Namens- und
Verzeichnisdienste gebunden. Außerdem ist es relativ einfach mit Hilfe des JNDI Service Provider Interface (SPI) möglich, neue Namens- und Verzeichnisdienste einzuführen, ohne bestehende
Anwendungen ändern zu müssen.
14.1 JNDI-Terminologie
Zunächst ist es wichtig, die JNDI-Terminologie zu verstehen:
241
242
KAPITEL 14. JAVA NAME AND DIRECTORY INTERFACE (JNDI)
• Ein atomarer Name (atomic name) ist ein nicht zerlegbarer Teil eines zusammengesetzten
Namens.
• Ein zusammengesetzter Name (compound name) ist eine Folge von atomaren Namen.
• Die Zuordnung eines Namens zu einem Objekt wird als Bindung binding bezeichnet.
• Ein Kontext (context) ist ein Objekt, das eine Menge von Namensbindungen mit atomaren
Namen verwaltet.
• Atomare Namen in einem gegebenen Kontext können an andere Teil-Kontexte (subcontext)
gebunden werden.
• Ein Namensystem (naming system) ist eine Menge verbundener Kontext desselben Typs mit
denselben Operationen.
• Ein Namensraum (name space) ist die Menge aller Namen in einem Namensystem.
• Ein gemischter Name composite name ist ein Name, der aus zusammengesetzten Namen
mehrerer Namensysteme besteht.
• Alle Namen sind relativ zu einem Kontext. Es gibt entsprechend keine absoluten Namen.
Eine URL wie beispielsweise http://www.inf.uos.de/schoenw/index.html ist ein
gutes Beispiel für einen gemischten Namen composite name der drei Namensysteme benutzt. Der
erste Teil http ist ein atomarer Name aus dem Namensraum der URL Schemata Identifikatoren.
Der zweite Teil www.inf.uos.de ist ein zusammengesetzter Name aus dem DNS Namensraum. Dieser zusammengesetzte Name wird von rechts nach links interpretiert. Der dritte Teil
schoenw/index.html ist ein zusammengesetzter Name in einem Namensraum eines Dateisystems der von links nach rechts interpretiert wird.
Noch etwas mehr JNDI-Terminologie:
• Ein Verzeichnisobjekt (directory object) ist ein Objekttyp mit einer Menge assoziierter Attribute, der Informationen in einem verteilten System repräsentiert.
• Ein Attribut (attribute) eines Verzeichnisobjekts besteht aus einem Identifier und einer Menge
von Werten.
• Ein Verzeichnisobjekt stellt Methoden zur Erzeugen und Löschen von Attributen und zum
Verändern von Attributewerten bereit.
Offenbar wird ein Verzeichnisdienst als eine Erweiterung eines Namensdienstes modelliert. Zusätzlich unterstützt JNDI die Benachrichtung von Anwendungen über Veränderungen durch spezielle
Ereignisse und speziell LDAP Version 3 als gebräuchlichen Verzeichnisdienst. Entsprechend besteht die JNDI API aus folgenden vier Paketen:
1. javax.naming
Grundlegende Klassen und Schnittstellen zum Zugriff auf Namensdienste.
2. javax.naming.directory
Erweiterung der grundlegenden Klassen und Schnittstellen zum Zugriff auf Verzeichnisdienste.
243
14.2. ZUGRIFF AUF NAMENSDIENSTE
3. javax.naming.event
Klassen und Schnittstellen zur Benachrichtigung über Änderungen in Namens- und Verzeichnisdiensten.
4. javax.naming.ldap
Spezifische Erweiterungen für LDAP Version 3 Verzeichnisse.
Das Paket javax.naming.spi definiert die Schnittstelle, mit der neue Namens- und Verzeichnisdienste integriert werden können.
14.2 Zugriff auf Namensdienste
Die folgende Abbildung zeigt die wesentlichen Klassen und Interfaces zum Zugriff auf Namensdienste.
«interface»
«interface»
«interface»
Enumeration
Serializable
Cloneable
«interface»
Context
«interface»
«interface»
NamingEnumeration
Name
+close(): void
+createSubcontext(name:Name): Context
+destroySubcontext(name:Name): void
+bind(name:Name,obj:Object): void
+rebind(name:Name): void
+lookup(name:Name): Object
+rename(old:Name,new:Name): void
+unbind(name:Name): void
+list(name:Name): NamingEnumeration
+listBindings(name:Name): NamingEnumeration
+hasMore(): boolean
+next(): Object
+close(): void
CompoundName
«interface»
NameParser
Object
+parse(name:String): Name
CompositeName
InitialContext
NameClassPair
Binding
Abbildung 14.2: JNDI Klassen und Interfaces
• Alle Methoden basieren auf einem Kontext-Objekt. Operationen, die einen Namen als Parameter benutzen, lösen zuerst den Namen zu einem Kontext-Objekt auf.
• Zu den Methoden die einen Name Parameter haben gibt es in der Regel eine entsprechende Methode mit einem String Parameter, wobei die Zeichenkette dann zunächst in einen
Name Parameter konvertiert wird.
• Die Klasse InitialContext erlaubt es, einen initialen Kontext für ein Namensystem zu
bekommen.
244
KAPITEL 14. JAVA NAME AND DIRECTORY INTERFACE (JNDI)
Das folgende Beispiel demonstriert, wie mit Hilfe eines JNDI Service Providers für das lokale
Dateisystem Dateinamen aufgelöst werden können.
import
import
import
import
import
java.io.File;
java.util.Hashtable;
javax.naming.Context;
javax.naming.InitialContext;
javax.naming.NamingException;
public class Lookup
{
public static void main(String[] args)
{
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
env.put(Context.PROVIDER_URL, "file:/");
if (args.length > 0) {
try {
Context ctx = new InitialContext(env);
for (int i = 0; i < args.length; i++) {
File f = (File) ctx.lookup(args[i]);
System.out.println(f);
}
ctx.close();
} catch (NamingException e) {
System.err.println("Lookup failed: " + e);
}
}
}
}
• Der Service Provider wird über eine Hashtabelle ausgewählt und parametrisiert.
• Der initiale Kontext muß explizit durch den Aufruf der Methode close freigegeben werden,
damit eventuell gebundene Ressourcen freigegeben werden.
Das folgende Beispiel demonstriert, wie man durch die in einem Kontext bekannten Namen iterieren
kann.
import
import
import
import
import
import
import
java.util.Hashtable;
javax.naming.Context;
javax.naming.InitialContext;
javax.naming.NamingException;
javax.naming.NamingEnumeration;
javax.naming.NameClassPair;
javax.naming.Binding;
public class List
{
14.3. ZUGRIFF AUF VERZEICHNISDIENSTE
245
public static void main(String[] args)
{
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
env.put(Context.PROVIDER_URL, "file:/");
try {
Context ctx = new InitialContext(env);
// List only the names...
NamingEnumeration list = ctx.list("bin");
while (list.hasMore()) {
NameClassPair nc = (NameClassPair)list.next();
System.out.println(nc);
}
// List the names together with the actual bindings...
NamingEnumeration bindings = ctx.listBindings("bin");
while (bindings.hasMore()) {
Binding bd = (Binding)bindings.next();
System.out.println(bd.getName() + ": " + bd.getObject());
}
ctx.close();
} catch (NamingException e) {
System.err.println("List failed: " + e);
}
}
}
• Die Methode list eines Kontexts liefert einen Enumerator für alle im Kontext bekannten
Namen.
• Die Methode listBindings eines Kontexts liefert alle Bindungen inklusive der gebundenen Objekte.
• Der Aufwand der beiden Methoden kann abhängig vom Service Provider deutlich variieren.
Das Erzeugen von neuen Bindungen oder Kontexten erfolgt mit den entsprechenden Methoden des
Context Interface.
14.3 Zugriff auf Verzeichnisdienste
Ein Verzeichnis ist in JNDI eine Erweiterung eines Namensdienstes, wobei zu jedem Namen eine
Menge von Attributen zugeordnet werden können. Mit Hilfe von Suchprimitiven lassen sich dann
aus dem Verzeichnis bestimmte Einträge ermitteln.
Die folgende Abbildung zeigt die wesentlichen Klassen und Interfaces zum Zugriff auf Verzeichnisse.
Lesen von Attributen:
246
KAPITEL 14. JAVA NAME AND DIRECTORY INTERFACE (JNDI)
«interface»
Context
«interface»
DirContext
+getAttributes(name:Name): Attributes
+modifyAttributes(name:Name,op:int,attrs:Attributes): void
+search(name:Name,matchingAttributes:Attributes): NamingEnumeration
+search(name:Name,filterExpr:String,filterArgs:Obejct[],cons:SearchControls): NamingEnumeration
«interface»
Attribute
+add(value:Object): boolean
+set(index:int,value:Object): Object
+remove(index:int): Object
+remove(value:Object): boolean
+contains(value:Object): boolean
+clear(): void
+size(): int
«interface»
Attributes
+get(attrID:String): Attribute
+getAll(): NamingEnumeration
+size(): int
+put(attr:Attribute): Attribute
+remove(attrID:String): Attribute
Object
BasicAttribute
BasicAttributes
InitialDirContext
Abbildung 14.3: JNDI Klassen und Interfaces für Verzeichnisdienste
import
import
import
import
import
import
java.util.Hashtable;
javax.naming.Context;
javax.naming.NamingException;
javax.naming.directory.Attributes;
javax.naming.directory.DirContext;
javax.naming.directory.InitialDirContext;
public class LookupAttributes
{
public static void main(String[] args)
{
Hashtable env = new Hashtable(11);
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=uos");
try {
DirContext ctx = new InitialDirContext(env);
String[] attrIDs = { "sn", "telephonenumber",
"golfhandicap", "mail" };
Attributes answer =
ctx.getAttributes("cn=Ted Geisel, ou=People", attrIDs);
14.3. ZUGRIFF AUF VERZEICHNISDIENSTE
247
// GetattrsAll.printAttrs(answer);
ctx.close();
} catch (NamingException e) {
System.err.println("Lookup failed: " + e);
System.exit(1);
}
}
}
Filter bei Suchanfragen sind in RFC 2254 [10] definiert, z.B.:
(&(sn=Geisel)(mail=*))
Mit Hilfe der SearchControls kann die Ergebnismenge und die Bearbeitung der Suchanfrage
parametrisiert werden.
248
KAPITEL 14. JAVA NAME AND DIRECTORY INTERFACE (JNDI)
Kapitel 15
Enterprise Java Beans
Mit den Enterprise Java Beans (EJBs) hat Sun eine Java-spezifische Komponentenarchitektur definiert, die die Komposition von verteilten Anwendungen aus Standard-Komponenten unterstützt.
EJBs erlauben einen verteilten Entwicklungsprozess, bei dem zwischen den Herstellern von Komponenten, den Herstellern von Ablaufumgebungen, den Erstellern von spezifischen Anwendungen
und den Betreibern von Anwendungen unterschieden wird.
EJBs sind am ehesten mit der CORBA-Technologie vergleichbar, da viele der CORBA-Dienste
(z.B. Transaktionsunterstützung) auch in der EJB-Umgebung existieren, wenn auch in einer Javaspezifischen Form.
Enterprise Java Beans sind nicht mit den Java Beans zu verwechseln. EJBs sind Komponenten
die typischerweise Teile einer Anwendungslogik repräsentieren. Es geht bei EJBs weniger um die
Manipulation von komplexen Objekten einer graphischen Benutzungsoberfläche.
15.1 Eigenschaften von EJBs
Zunächst die wichtigsten Eigenschaften von EJBs:
• Nur ein einziges EJB-Objekt stellt die Schnittstelle für eine EJB nach außen zur Verfügung
(Facade Design Pattern).
• EJBs benötigen einen EJB-Container der EJBs beherbergt, Aufrufe entgegennimmt und von
EJBs ausführen läßt.
• EJB-Container stellen zusätzliche Dienste bereit (z.B. Transaktionen, Sicherheit).
• EJB-Container können sofern erforderlich EBJs ein- und auslagern. Dazu muß jede EJB das
Interface Serializable implementieren.
EJBs können in Session Beans und Entity Beans eingeteilt werden.
Session Beans
• Session Beans implementieren die Ablauflogik einer Anwendung.
249
250
KAPITEL 15. ENTERPRISE JAVA BEANS
• Die Lebenszeit einer Session Bean erstreckt sich typischerweise über eine Sitzung, die mehrere Client/Server-Interaktionen umfaßt. (Das Session-Konzept ähnelt den Sessions der Servlets.)
• Eine aktive Session Bean ist immer einem Client zugeordnet - Beans bearbeiten niemals
Anfragen mehrerer Clients nebenläufig.
• Inaktive Session Beans sind entsprechend keinem Client zugeordnet und können bei Bedarf
zugeordnet werden.
• Zustandslose Session Beans besitzen keinen internen Zustand. Sie können in Pools verwaltet
werden und dann sehr schnell einem Client zugeordnet werden.
• Zustandsbehaftete Session Beans müssen in der Regel serialisiert werden um Sie einem anderen Client zuzuordnen.
Entity Beans
• Entity Beans werden zur Modellierung der Daten einer Anwendung verwendet.
• Entity Beans realisieren in der Regel eine objekt-orientierte Sicht auf die zugrundeliegende
Datenbank.
• Entity Beans sind in der Regel persistent. Die Verwaltung der Persistenz kann entweder von
der Bean selbst vorgenommen werden (Bean-Managed Persistent Entity Bean) oder aber von
dem Container (Container-Managed Persistent Entity Bean).
15.2 Eigenschaften eines Containers
Ein Container stellt die Infrastruktur für verteilte Anwendungen bereit. Die von einem Container
angebotenen Funktionen sind sehr ähnlich zu den CORBA-Services. Die Standardeigenschaften
eines Containers sind:
• Ressourcen- und Lebenszyklusmanagement
Zuständig für die Verwaltung der Betriebsmittel und das Erzeugen, neuer Instanzen bzw. die
Entfernung von inaktiven Instanzen aus dem Speicher.
• Zustandsverwaltung
Verwaltung von Zuständen für die Dauer einer logischen Kommunikationsbeziehung.
• Transaktionsmanagement
Unterstützung für die Abwicklung von Transaktionen zwischen mehreren beteiligten EJBs.
• Sicherheit
Definition von Sicherheitsklassen für EJBs, die die spezifischen Rechte bei der Ausführung
einer EJB festlegen.
• Datenpersistenz
Serialisierung und Speicherung des Zustands einer EJB um ihn zu einem späteren Zeitpunkt
wieder rekonstruieren zu können.
15.3. AUFBAU EINER EJB
251
• Entfernter Zugriff und Ortstransparenz
Netzwerkweiter Zugriff auf die Funktionalität von EJBs wobei Benutzer keine genauen Informationen über den Ort der laufenden EBJs benötigen.
• Helferwerkzeuge
Werkzeuge zur automatischen Erzeugung von Quelltexten.
Weitere optionale Eigenschaften sind:
• Lastbalancierung
Austausch von Lastinformationen zwischen Containern mit dem Ziel, die Last auszugleichen.
• Dynamischer Austausch von Komponenten zur Laufzeit
Austausch von EJBs zwischen Containern zur Laufzeit um z.B. Systeme für Wartungsarbeiten evakuieren zu können.
• Verteilte Transaktionen
Unterstützung von Transaktionen über Container- und Servergrenzen hinweg.
• Integrierte XML-Unterstützung
Erleichterung der Datenrepräsentation mit Hilfe der XML-Standards.
• Integration von CORBA
Erweiterungen zur einfacheren Integration von CORBA und EJB Komponenten.
15.3
Aufbau einer EJB
Die wesentlichen Komponenten einer EJB sind:
• Enterprise Java Bean Implementation
• EJB-Objekt samt Remote Interface
• Home-Objekt samt Home Interface
• Verwaltungsinformationen (Deployment Descriptors)
• EJB Jar-Datei
Prinzipieller Ablauf beim Zugriff auf eine EJB (Abbildung 15.1):
• Zunächst erfolgt eine Lokalisierung des Home-Objekts der EJB über einen Namensdienst
(siehe JNDI).
• Vom Home-Objekt kann eine Referenz auf eine Instanz eines EJB-Objekts angefordert werden.
• Das Home-Objekt erzeugt eine passende Instanz und schickt die Referenz an den Client.
• Der Client kann über das EJB-Objekt auf die eigentliche Implementation der EJB zugreifen.
Die Schnittstellen sind wie folgt in Java definiert (Abbildung 15.2):
252
KAPITEL 15. ENTERPRISE JAVA BEANS
EJB Container
aClient
4 Call EJBObject
aEJBObject
5. Call EJB
aEJBInternalObjekt
3. EJBObject Reference
2. Create EJBObject
1. EJBObject?
aHomeObject
Abbildung 15.1: Zusammenspiel zwischen Home-Objekt und EJB-Objekt
253
15.3. AUFBAU EINER EJB
«interface»
Serializable
«interface»
EnterpriseBean
«interface»
EntityBean
«interface»
SessionBean
+setSesionContext(ctx:SessionContext): void
+ejbRemove(): void
+ejbActivate(): void
+ejbPassivate(): void
+setEntityContext(ctx:EntityContext): void
+unsetEntityContext(): void
+ejbRemove(): void
+ejbActivate(): void
+ejbPassivate(): void
+ejbLoad(): void
+ejbStore(): void
«interface»
Remote
«interface»
EJBObject
+getEJBHome(): EJBHome
+getPrimaryKey(): Object
+remove(): void
+getHandle(): Handle
+isIdentical(obj:EJBObject): boolean
«interface»
EJBHome
+remove(handle:Handle): void
+remove(primaryKey:Object): void
+getEJBMetaData(): EJBMetaData
+getHomeHandle(): HomeHandle
Abbildung 15.2: Enterprise Java Beans Interfaces
254
KAPITEL 15. ENTERPRISE JAVA BEANS
Fragen zur Selbstkontrolle
1. Einführung
(a) Nennen Sie Eigenschaften von Software und erläutern Sie inwiefern sich Software von
anderen technischen Produkten unterscheidet.
(b) Definieren Sie den Begriff Software-Engineering (Software-Technik).
(c) Was versteht man unter der Softwarekrise und was sind die typischen Problemquellen?
(d) Was sind fehlertolerante Systeme? Was ist der Unterschied zwischen einem Fehler und
einem Ausfall?
(e) Erläutern Sie den Begriff Software-Qualität. Welche Qualitätsmerkmale kennen Sie?
2. Prozessmodelle
(a) Wozu dient das Capability Maturity Model (CMM)?
(b) Erläutern Sie den wesentlichen Unterschied zwischen dem Wasserfallmodell und dem
V-Modell.
(c) Beschreiben Sie den Unterschied zwischen einem horizontalen und einem vertikalen
Prototyp und zwischen einem Demontrationsprototyp und einem Pilotsystem.
(d) Nennen Sie die wesentlichen Techniken des extremen Programmierens.
(e) Welches Prozessmodell würden Sie bei der Entwicklung einer Software zur Verwaltung
von Bibliotheksbeständen inklusive einer Web-basierten Ausleihfunktion einsetzen?
3. Unified Modeling Language
(a) Nennen Sie mindestens 5 Diagrammarten der Unified Modeling Language.
(b) Wie werden in Klassendiagramme Sichtbarkeiten und Gültigkeitsbereiche notiert?
(c) Was versteht man unter Generalisierung, Spezialisierung, Aggregation und Komposition? Welche Darstellungsformen werden benutzt?
(d) Was versteht man unter einer rekursiven Assoziation? Geben Sie ein Beispiel für eine
rekursive Assoziation an.
(e) Erläutern Sie den Begriff der Multiplizität.
(f) Was sind Assoziationsklassen?
(g) Entwerfen Sie ein Klassendiagramm und ein passendes Objektdiagramm für eine sehr
einfache Bibliothek.
(h) Entwerfen Sie ein Sequenzdiagramm für den Vorgang Auto tanken“.
”
255
256
KAPITEL 15. ENTERPRISE JAVA BEANS
(i) Was ist ein Kollaborationsdiagramm? In welchem Verhältnis steht es zu einem Sequenzdiagramm?
(j) Beschreiben Sie in einem Aktivitätsdiagramm die Zubereitung einer Pizza. Welche Aktivitäten können nebenläufig ausgeführt werden? Wozu kann man Swimlanes“ benut”
zen?
(k) Erklären Sie die Begriffe Komponenten- und Verteilungsdiagramm.
4. Entwurfsmuster
(a) Was versteht man unter einem Entwurfsmuster und in welcher Form werden Entwurfsmuster beschrieben?
(b) Beschreiben Sie das Entwurfsmuster Composite.
(c) Was versteht man unter einem Observer? Skizzieren Sie das Prinzip in Form eines UML
Diagramms. Kennen Sie Realisierungen des Observer Entwurfsmusters?
(d) Welche Bedeutung hat das Strategy Entwurfsmuster? Wann kann man es sinnvoll einsetzen?
(e) Was versteht man unter einer Chain of Responsibility?
(f) Erläutern Sie das Model-View-Controller Entwurfsmuster.
5. Qualitätssicherung
(a) Nennen Sie typische Klassen von Softwarefehlern.
(b) Was ist eine Programminspektion?
(c) Erklären Sie den Unterschied zwischen Black Box und White Box Testverfahren.
(d) Was versteht man unter funktionalen Testverfahren? Wozu dienen die Äquivalenzklassen?
(e) Erklären Sie anhand eines kleinen Beispiels den Begriff des Kontrollflußgraphen.
(f) Im Zusammenhang mit Kontrollfluß-orientierten Verfahren spielen die Begriffe Anweisungsüberdeckung, Zweigüberdeckung, Bedingungsüberdeckung und Pfadüberdeckung
eine wichtige Rolle. Erklären Sie die Begriffe sowie das Verhältnis zueinander.
(g) Wozu dienen Regressionstests?
6. Werkzeuge
(a) Welche Werkzeuge zur Programmentwicklung kennen Sie?
(b) Was versteht man unter Versionsmanagement?
(c) Im Zusammenhang mit den Systemen RCS und CVS spricht man von Revisionen, Varianten und Konfigurationen. Erläutern Sie diese Begriffe an einem geeigneten Beispiel.
(d) Nennen Sie die wesentlichen Unterschiede zwischen RCS und CVS.
(e) Wozu dienen Testwerkzeuge? Nennen Sie Beispiele.
(f) Was versteht man unter einer integrierten Entwicklungsumgebung? Welche Vorteile besitzen integrierten Entwicklungsumgebungen?
7. Ergonomische Aspekte
(a) Was versteht man unter einem Benutzermodell?
15.3. AUFBAU EINER EJB
257
(b) Was sind modale Dialoge und wann sollte man modale Dialoge verwenden?
(c) Erklären Sie die Begriffe Primärdialog und Sekundärdialog.
(d) Was ist der Unterschied zwischen einer funktionsorientierten und einer objektorientierten Bedienung?
(e) Erklären Sie die Begriffe Aufgabenangemessenheit, Selbstbeschreibungsfähigkeit, Steuerbarkeit, Erwarungskonformität, Fehlertoleranz, Individualisierbarkeit und Lernförderlichkeit.
(f) Welche Regeln sollte man bei der Gestaltung von Dialogen beachten?
8. Java Abstract Windowing Toolkit (AWT)
(a) Was versteht man unter ereignisorientierter Programmierung?
(b) Erläutern Sie an einem kleinen Beispiel, wie ein Java Programm auf den Eintritt eines
Ereignisses reagieren kann.
(c) Was versteht man unter containment und durch welche Klassen wird dieses Konzept
realisiert?
9. Java Foundation Classes (Swing)
(a) Wodurch unterscheiden sich Top-Level-Container von anderen Containern in der Swing
Klassenbibliothek?
(b) Welche Aufgaben erledigt ein Layout Manager? Was ist der Zusammenhang zwischen
minimumLayoutSize, preferredLayoutSize und maximumLayoutSize?
(c) Erläutern Sie den Algorithmus hinter dem Border Layout Manager.
(d) Vergleichen Sie den Box Layout Algorithmus mit dem Flow Layout Algorithmus.
(e) Der Grid Bag Layout Algorithmus benutzt Informationen aus GridBadConstraints Objekten um das Layout zu berechnen. Nennen Sie einige der Parameter und beschreiben
Sie deren Wirkung. Was versteht man in diesem Zusammenhang under padding?
(f) Erläutern Sie an einem Beispiel wie ein einfaches Menü implementiert wird.
(g) Was versteht man im Zusammenhang von Menüs unter Actions? Welchen Vorteil bietet
die Benutzung von Actions?
(h) Mit welchen Java-Elementen lassen sich Ausschnitte einer Komponente darstellen, die
mit einem Rollbalken manipuliert werden können?
(i) Nach welchem Prinzip erfolgt die Darstellung von Dokumenten in Java? Wie werden
Änderungen an einem Dokument propagiert?
(j) Welche Standard-Dialoge und -Komponenten sind Ihnen bekannt und wofür kann man
sie benutzen?
(k) Bei der Darstellung von Graphiken wird ein User Space Koordinatensystem und ein
Device Space Koordinatensystem benutzt. Erklären Sie den Unterschied.
(l) Was versteht man unter einem rendering context?
10. Verteilte Anwendungen
(a) Erläutern Sie den Begriff Client/Server-Modell und nennen Sie einige Beispiele.
(b) Was versteht man unter einem Remote Procedure Call (RPC)? Wozu dienen die StubProzeduren?
258
KAPITEL 15. ENTERPRISE JAVA BEANS
(c) Nennen Sie Ihnen bekannte RPC-Semantiken und erklären Sie die Unterschiede.
(d) Was versteht man unter N-Tier Architekturen? Geben Sie jeweils ein Beispiel für eine
2-Tier, eine 3-Tier und eine 4-Tier Architektur an.
11. Java Beans
(a) Was versteht man unter einer Software-Komponente? Wie verwendet man SoftwareKomponenten effektiv?
(b) Erklären Sie an einem kleinen Beispiel was Properties von Java Beans sind. Was versteht man unter einer Zugriffsmethode?
(c) Erläutern Sie die Begriffe Simple Property, Indexed Property, Bound Property und
Constrained Property.
(d) Wie werden Constrained Properties implementiert? Erklären Sie den Ablauf beim Verändern
von Constrained Properties.
(e) Welche Aufgabe hat eine BeanInfo Klasse?
12. Servlets
(a) Erklären Sie die Begriffe Uniform Resource Locator (URL), Uniform Resource Name
(URN) und Uniform Resource Identifier (URI).
(b) Nennen Sie die wichtigsten Methoden des HTTP Protokolls und erläutern Sie, wie die
Statuscodes aufgebaut sind.
(c) Erläutern Sie den Begriff Servlet. Was ist der Unterschied zwischen einem generischen
Servlet und einem HTTP Servlet?
(d) Was versteht man im Zusammenhang mit Servlets unter einer Session? Nennen Sie
mindestens zwei Methoden mit denen Sessions bei mehreren HTTP-Methodenaufrufen
identifiziert werden können.
(e) Wie können Zustandsinformationen an eine Session gebunden werden und wielange
existieren Sessions im Web-Server?
(f) Was sind JavaServer Pages? Welche Vor- und Nachteile haben JavaServer Pages gegenüber einfachen Java Servlets?
(g) Durch den Einsatz von Beans läßt sich die Trennung der Anwendungslogik von der
Darstellung der Information in HTML weiter verbessern. Welche wichtige Rolle spielen
in diesem Zusammenhang die Properties einer Bean?
13. CORBA
(a) Skizzieren sie die Object Management Architecture (OMA). Was ist der Unterschied
zwischen Common Object Services und Common Facilities?
(b) Aus welchen Komponenten besteht ein Object Request Broker (ORB) und welche Funktionen haben sie?
(c) Welche primäre Funktion erfüllt die Interface Definition Language (IDL)?
(d) Was versteht man unter einer discriminated union?
(e) Welche Bedeutung haben in, out und inout Parameter in IDL-Operationen?
(f) Erläutern Sie den Begriff Objekt-Referenz. Wozu dienen Objekt-Referenzen?
15.3. AUFBAU EINER EJB
259
(g) Welche Aufgabe hat der Naming Service? Wie ist der Namensraum oganisiert?
(h) Skizzieren Sie ein Java Programm zum Durchlaufen eines kompletten Namensraums.
(i) Welche Aufgabe haben Holder- und Helper-Klassen, die bei der IDL-Übersetzung nach
Java erzeugt werden?
14. Java Name and Directory Interface
(a) Erklären Sie den Unterschied zwischen einem Namensdienst und einem Verzeichnisdienst.
(b) Nennen Sie Beispiele für zusammengesetzte Namen (compound names) und gemischte
Namen (composite names).
(c) Was ist ein JNDI Kontextobjekt (Context) und welche Methoden stellt es zur Verfügung?
15. Enterprise Java Beans
(a) Welche Aufgabe erfüllt ein EJB-Objekt und auf welchem Design Pattern basiert es?
(b) Erläutern Sie an einer Skizze, wie ein Client eine EJB-Instanz lokalisiert und mit ihr
kommuniziert.
(c) Erklären Sie den Unterschied zwischen Entity und Session Beans.
(d) Nennen Sie mindestens fünf Aufgaben, die durch einen Container unterstützt werden.
260
KAPITEL 15. ENTERPRISE JAVA BEANS
Literaturverzeichnis
[1] H. Balzert. Lehrbuch der Software-Technik. Spektrum, Akademischer Verlag, 2 edition, 2000.
[2] K. Beck. Extreme Programming Explained: Embrace Change. Addison-Wesley, 1999.
[3] G. Booch, J. Rumbaugh, and I. Jacobsen. The Unified Modeling Language User Guide. Addison Wesley, 1998.
[4] F. P. Brooks. The Mythical Man-Month. Addison Wesley, 1975.
[5] T. DeMarco. Der Termin. Hanser Verlag, 1998.
[6] K. Beck E. Gamma. Junit a cook’s tour. Technical report, 1998.
[7] K. Beck E. Gamma. Junittest infected: Programmers love writing tests. Technical report,
1998.
[8] E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design Patterns: Elements of Reusable
Object-Oriented Software. Addison Wesley, 1995.
[9] Michael Herczeg. Software-Ergonomie: Grundlagen der Mensch-Computer-Kommunikation.
Addison Wesley, 1994.
[10] T. Howes. The String Representation of LDAP Search Filters. RFC 2254, Netscape Communications Corp., December 1997.
[11] B.P. Miller, L. Fredriksen, and B. So. An empirical study of the reliability of UNIX utilities.
Communications of the ACM, 33(12):32–44, 1990.
[12] Object Management Group. Unified Modeling Language Specification Version 1.3. Formal
Specification, Object Management Group, March 2000.
[13] B. Oestereich. Objektorientierte Softwareentwicklung: Analyse und Design mit der Unified
Modeling Language. Oldenbourg, 4 edition, 1998.
[14] OMG. IDL to Java Language Mapping Specification. Technical Report Revision 1.1, Object
Management Group, June 2001.
[15] OMG. The Common Object Request Broker: Architecture and Specification. Technical Report
Revision 2.6, Object Management Group, December 2001.
[16] R. Reißing. Extremes Programmieren. Informatik Spektrum, 23(2):118–121, 2000.
[17] W. Schneider. Ergonomische Anforderungen für Bürotätigkeiten mit Bildschirmgeräten –
Grundsätze der Dialoggestaltung (Kommentar zu DIN EN ISO 9241-10). Beuth Verlag, 1998.
261
262
LITERATURVERZEICHNIS
[18] B. Shneiderman. Designing the User Interface: Strategies for Effective Human-Computer
Interaction. Addison Wesley, 3 edition, 1997.
[19] J. Siegel. CORBA Fundamentals and Programming. Wiley & Sons, 1996.
[20] Sun. Java Naming and Directory Interface Application Programming Interface (JNDI API).
Technical Report 1.2, Sun Microsystems, July 1999.
[21] K. Walrath and M. Campione. The JFC Swing Tutorial: A Guide to Constructing GUIs.
Addison Wesley, 3 edition, 1999.
[22] A. Zeller and J. Krinke. Programmierwerkzeuge. dpunkt, 2000.
Herunterladen