Informatik C - Vorlesungen

Werbung
Informatik C
Vorlesungsskript WS 2002/2003
Jürgen Schönwälder
Version vom 30. Januar 2003
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, dass 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 und im WS 2002/2003 betreut und viel konstruktive Kritik an diesem Skript geübt hat.
Bedanken möchte ich mich an dieser Stelle auch bei Astrid Heinze für das gelegentliche Korrekturlesen einzelner Kapitel während der Überarbeitung des Skripts im WS 2002/2003.
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
29
30
32
33
33
35
37
39
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 . . . . . . .
4.8 Anti Pattern: Creeping Featuritis . .
4.9 Anti Pattern: Design By Committee
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
40
41
43
44
45
46
.
.
.
.
.
.
.
.
.
51
54
55
58
61
65
66
67
68
68
Qualitätssicherung
5.1 Typische Softwarefehler . . . . . . . . . . .
5.2 Programminspektionen . . . . . . . . . . . .
5.3 Testverfahren . . . . . . . . . . . . . . . . .
5.3.1 Funktionale Testverfahren . . . . . .
5.3.2 Kontrollfluss-orientierte Testverfahren
5.3.3 Mutationstesten . . . . . . . . . . . .
5.3.4 Regressionstesten . . . . . . . . . . .
5.4 Softwaremetriken . . . . . . . . . . . . . . .
5.5 Qualitätssicherung mit ISO 9000 . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
69
69
69
70
71
73
75
76
76
78
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 . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
79
79
79
80
80
80
82
82
83
84
84
87
.
.
.
.
.
.
.
.
.
.
.
II Graphische Benutzungsoberflächen
89
7
91
91
92
92
Ergonomische Aspekte
7.1 Dialogarten und Dialogmodi . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7.2 Grundsätze zur Dialoggestaltung . . . . . . . . . . . . . . . . . . . . . . . . . . .
7.2.1 Aufgabenangemessenheit . . . . . . . . . . . . . . . . . . . . . . . . . .
INHALTSVERZEICHNIS
.
.
.
.
.
.
.
92
93
93
93
94
94
94
8
Java Abstract Windowing Toolkit (AWT)
8.1 AWT Komponenten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.2 Ereignisorientiere Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . .
97
97
98
9
Java Foundation Classes (Swing)
9.1 Einführende Beispiele . . . . . . . . . . . . . . .
9.1.1 Hello World . . . . . . . . . . . . . . . .
9.1.2 Celsius Converter . . . . . . . . . . . . .
9.1.3 Look and Feel . . . . . . . . . . . . . .
9.1.4 Root Panes . . . . . . . . . . . . . . . .
9.1.5 Threads . . . . . . . . . . . . . . . . . .
9.2 Layout Manager . . . . . . . . . . . . . . . . . .
9.2.1 Border Layout . . . . . . . . . . . . . .
9.2.2 Box Layout . . . . . . . . . . . . . . . .
9.2.3 Card Layout . . . . . . . . . . . . . . .
9.2.4 Flow Layout . . . . . . . . . . . . . . .
9.2.5 Grid Layout . . . . . . . . . . . . . . . .
9.2.6 Grid Bag Layout . . . . . . . . . . . . .
9.2.7 Absolute Positionierung . . . . . . . . .
9.2.8 Implementation eines Layout-Manager .
9.3 Ränder . . . . . . . . . . . . . . . . . . . . . . .
9.3.1 Einfache Ränder . . . . . . . . . . . . .
9.3.2 Ränder mit Titeln . . . . . . . . . . . . .
9.3.3 Zusammengesetzte Ränder . . . . . . . .
9.4 Menüs . . . . . . . . . . . . . . . . . . . . . . .
9.4.1 Toolbars . . . . . . . . . . . . . . . . . .
9.4.2 Actions . . . . . . . . . . . . . . . . . .
9.5 Scrolling . . . . . . . . . . . . . . . . . . . . . .
9.6 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 . .
7.3
7.2.2 Selbstbeschreibungsfähigkeit . . . . .
7.2.3 Steuerbarkeit . . . . . . . . . . . . .
7.2.4 Erwartungskonformität . . . . . . . .
7.2.5 Fehlertoleranz . . . . . . . . . . . .
7.2.6 Individualisierbarkeit . . . . . . . . .
7.2.7 Lernförderlichkeit . . . . . . . . . .
Acht goldene Regeln für die Dialoggestaltung
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
105
106
106
107
111
114
114
115
115
116
119
122
123
124
126
127
132
132
134
136
138
141
143
147
149
151
151
152
153
156
163
167
170
174
176
182
INHALTSVERZEICHNIS
9.11.3 Beispiel: Santa Claus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
III
Software-Komponenten und Verteilte Anwendungen
195
10 Java Beans
10.1 Properties . . . . . . . . . . . . .
10.2 Explizite Beschreibung einer Bean
10.3 Verpacken einer Bean . . . . . . .
10.4 Beispiel . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
197
197
204
206
207
11 Servlets
11.1 Grundlagen der Web-Technologie
11.1.1 URIs, URLs und URNs .
11.1.2 MIME . . . . . . . . . . .
11.1.3 HTTP . . . . . . . . . . .
11.1.4 HTML . . . . . . . . . .
11.2 Generische Servlets . . . . . . . .
11.3 HTTP Servlets . . . . . . . . . .
11.4 Lebenszyklus und Container . . .
11.5 Sessions . . . . . . . . . . . . . .
11.6 JavaServer Pages (JSP) . . . . . .
11.6.1 Deklarationen . . . . . . .
11.6.2 Ausdrücke . . . . . . . .
11.6.3 Scriptlets . . . . . . . . .
11.6.4 Einfügungen . . . . . . .
11.6.5 Weiterleitungen . . . . . .
11.6.6 Seiten-Direktiven . . . . .
11.7 JavaServer Pages und Beans . . .
11.7.1 Bean-Benutzung . . . . .
11.7.2 Properties . . . . . . . . .
11.8 Beispiel: Raten von Nummern . .
11.9 Tomcat Referenzimplementation .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
211
211
211
211
211
212
212
214
216
216
217
219
219
219
220
220
220
221
221
222
222
224
12 Extensible Markup Language (XML)
12.1 Struktur von XML Dokumenten .
12.2 Document Type Definitions (DTD)
12.3 XML Namensräume . . . . . . .
12.4 XML Schema (XSD) . . . . . . .
12.5 XML Paths (XPATH) . . . . . . .
12.6 XML Stylesheet Language (XSL)
12.7 Document Object Model (DOM) .
12.8 Java DOM API . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
225
225
227
228
228
231
231
232
232
13 Verteilte Anwendungen
13.1 Client/Server-Modell . . .
13.2 N-Tier Architekturen . . .
13.2.1 2-Tier Architektur
13.2.2 3-Tier Architektur
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
233
233
234
235
235
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
INHALTSVERZEICHNIS
13.2.3 4-Tier Architektur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
14 CORBA
14.1 CORBA Grundlagen . . . . . . . .
14.2 Object Request Broker (ORBs) . . .
14.3 Object Services . . . . . . . . . . .
14.4 Common Facilities . . . . . . . . .
14.5 Komponenten eines ORBs . . . . .
14.6 Interface Definition Language (IDL)
14.6.1 Typsystem . . . . . . . . .
14.6.2 Konstanten . . . . . . . . .
14.6.3 Ausnahmen . . . . . . . . .
14.6.4 Interfaces . . . . . . . . . .
14.6.5 Module . . . . . . . . . . .
14.7 Objekt-Referenzen . . . . . . . . .
14.8 Java Language Mapping . . . . . .
14.9 Naming Service . . . . . . . . . . .
14.10Mau Mau . . . . . . . . . . . . . .
14.11Schlußbemerkungen . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
15 Remote Method Invocation (RMI)
237
237
238
238
238
238
239
240
241
241
241
242
242
243
246
248
264
265
16 Java Name and Directory Interface (JNDI)
269
16.1 JNDI-Terminologie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
16.2 Zugriff auf Namensdienste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
16.3 Zugriff auf Verzeichnisdienste . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273
17 Enterprise Java Beans
277
17.1 Eigenschaften von EJBs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
17.2 Eigenschaften eines Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
17.3 Aufbau einer EJB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
18 Java Database Connectivity (JDBC)
18.1 Relationale Datenbanken . . . . . . .
18.2 Structured Query Language . . . . . .
18.2.1 Data Definition Language . .
18.2.2 Data Manipulation Language .
18.2.3 Data Control Language . . . .
18.3 SQL Zugriffe aus Java mit JDBC . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
281
281
282
282
283
284
285
Teil I
Software Engineering
1
Kapitel 1
Einführung
Eigenschaften von Software [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 Teile) 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ßprozess unterliegt. Berücksichtigt man jedoch, dass 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 Messgeräte. Die Qualität von Software ist schwer definierbar und quantifizierbar.
Definition des 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
[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 befasst. (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, dass die vorhandenen Vorgehensweisen nicht erfolgreich auf die neuen größeren Projekte
übertragen werden konnten. Viele Softwareprojekte scheiterten und es wurde der Begriff der Softwarekrise geprägt.
Die aktuelle Lage ist nach Untersuchungen von Laprie (1999) immer noch nicht rosig:
• Gut ein Drittel der Software-Projekte wird zur Zufriedenheit des Kunden und des Anbieters
durchgeführt.
• Bei einem Drittel der Software-Projekte wird ein Produkt oder eine Dienstleistung abgeliefert, die in wesentlichen Teilen den Anforderungen des Kunden nicht entspricht.
• Knapp ein Drittel der Software-Projekte enden ohne Auslieferung der Leistung (also der
Software) 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 ca. 10-15 Jahren. In der Regel muss 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 mussten
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, dass 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 erlaubt Leerzeichen in Bezeichnern).
• 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 Prozesssteuerungsanlage (materielle Schäden und ggf. ökologische und gesundheitliche Schä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 Ausfallzeit in 20 Jahren bei fortlaufendem Betrieb.
(Das entspricht wenige als 3 Minuten Ausfallzeit 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 AT&T Backbone-Netzes 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 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 menschlichem
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 Einflussnahme lässt 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.
8
KAPITEL 1. EINFÜHRUNG
• Es wird allgemein angenommen, dass eine Absenkung der Schadenswahrscheinlichkeit unter
das allgemeine Lebensrisiko (10−7 ) nicht sinnvoll ist.
−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 Entwicklungsprozess, 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:
1.4. SOFTWARE-QUALITÄT
9
• Funktionalität: Vorhandensein von Funktionen mit festgelegten Eigenschaften. Diese Funktionen erfüllen die definierten Anforderungen.
• 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 Missbrauch 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 [14]. 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).
10
KAPITEL 1. EINFÜHRUNG
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.
• 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 und Programmen 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 umfasst die Begriffe Informationssicherheit (Security) und
Funktionssicherheit (Safety):
– Informationssicherheit ist der Schutz der Vertraulichkeit und die Integrität von Daten
und Vorgängen.
– Funktionssicherheit ist die Eigenschaft eines Systems, dass die realisierte Funktion der
Komponenten mit der Spezifikation übereinstimmt und das System keine funktional
unzulässigen Zustände annimmt.
• Ergonomie: Software-Ergonomie befasst sich mit der Gestaltung interaktiver Programmsysteme und entwickelt Kriterien und Methoden, mit denen Programmsysteme den menschlichen Bedürfnissen entgegenkommend gestaltet werden können.
– Angemessene Schnittstellen für verschiedene Anwenderklassen.
– Oberflächenelemente sollten in jedem Fenster einer Anwendung einheitlich angeordnet
und beschriftet sein.
1.4. SOFTWARE-QUALITÄT
11
– Vernünftige Reaktionen auf fehlerhaftes Benutzerverhalten (brauchbare Fehlermeldungen, undo/redo-Funktionen).
• 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 angepasst werden.
– Ziel ist ein Baukastensystem, bei dem Software aus Standardkomponenten zusammengesetzt wird.
– Beispiele für erfolgreiche Wiederverwendung: Programmbibliotheken, Fenstersysteme,
Datenbanken
– Wiederverwendung wird hauptsächlich durch Abstraktion erreicht und erfordert einen
Entwicklungsprozess auf hohem Abstraktionsniveau.
Literaturhinweise
Von H. Balzert stammt ein gutes und sehr umfassendes Lehrbuch zum Thema Software-Engineering
[1]. Unbedingt lesenswert sind auch die Bücher von F. Brooks [5] und T. DeMarco [7]. 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 [7] 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 eine Softwareentwicklung nach festen Regeln nachvollziehbar und systematisch durchführen
zu können, braucht man so genannte 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) an der Carnegie Mellon University hat ein 5-StufenModell (capability maturity model, CMM) entwickelt, mit dem sich die tatsächlich erreichte Qualität des Entwicklungsprozesses beschreiben lässt:
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) ist informell vereinheitlicht.
• Einsatz von Werkzeugen 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 ist formalisiert, standardisiert und überprüft.
• Klar definierte Prozessmodelle sind vorhanden.
• Einsatz von Werkzeugen für Aufgaben des Prozessmanagements (Sammlung von Kenngrößen).
• Einsatz von Werkzeugen, 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, dass sich die Qualität der
Entwicklungsprozesse 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 gegebenenfalls 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 vom Entwickler durch fehlende Dokumentation.
- Tests und Verbesserungen sind 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 früher grundlegender Ansatz zur Strukturierung des Entwicklungsprozesses. Es taucht in der Literatur in vielen verschiedenen Variationen auf. Der Name drückt aus,
dass man sich wie bei einem mehrstufigen Wasserfall von der Planungsphase zur Wartungsphase
bewegt.
Planungsphase
Durchf"uhrbarkeitsstudie
Definitionsphase
Pflichtenheft
Entwurfsphase
Produktentwurf
Quelltext, Dokumentation,
Implementierungs−
Objektprogramm, Testprotokoll
phase
Installationsphase
Abnahmeprotokoll
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.
• Orientiert sich stark an der Top-Down-Vorgehensweise.
• Möglichkeit zur Rückkehr zu früheren Phasen falls grundlegende Fehler oder Inkonsistenzen
entdeckt werden.
Bewertung:
+ Leicht verständliches Prozessmodell.
+ Trennung des Was“ vom Wie”.
”
”
2.2. WASSERFALLMODELL
17
- 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
In der Definitionsphase werden die Anforderungen an die zu entwickelnde Software festgelegt.
• Ermitteln der detaillierten Anforderungen.
• Beschreibung und Analyse der Anforderungen.
• Modellierung, Simulation, Animation.
Das Ergebnis ist eine verbale Beschreibung der Anforderungen in Form eines Pflichtenhefts.
- Das Pflichtenheft erklärt, was das Produkt mit welchen Qualitätsmerkmalen leisten soll (nicht
wie).
- Es sollte verständlich und eindeutig sein (für Kunde und Entwickler).
- Es sollte konsistent und vollständig sein.
18
KAPITEL 2. PROZESSMODELLE
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:
• Zunächst erfolgt eine Auslieferung an ausgewählte Kunden, die Erfahrungen und Fehler
zurückmelden. (Beta-Test)
• Erst nach dieser Probezeit erfolgt die 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 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 der 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 so genannte Prototypen (Vorführmodelle) für das
zu entwickelnde System mit reduzierter Funktionalität oder Qualität entwickelt.
• Ein Demonstrationsprototyp dient der Auftragsakquisation und wird meist mit Generatoren
und Skript-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, der nicht nur zur experimentellen Erprobung oder zur Veranschaulichung dient, sondern selbst der Kern des Produkts ist. Ein Pilotsystem hilft, die
organisatorische Integration des Produkts vorzubereiten, indem es dem Benutzer einen Vorgeschmack auf das System gibt.
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. In weiteren Teilprojekten werden später weitere Komponenten hinzukommen.
• Es ist notwendig, bei der Spezifikation und dem Entwurf von Kernkomponenten bereits Erweiterungen zu berücksichtigen.
• Die Architektur muss für Erweiterungen offen gehalten werden.
• Es ist jeweils zu prüfen, ob die 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 Prozessmodells
4. Auswertung und Vorbereitung des nächsten Umlaufs
Bewertung:
+ Risikominimierung in allen Phasen und bei allen Teilprojekten.
+ Jede Komponente kann nach dem geeignetsten Prozessmodell entwickelt werden.
+ Regelmäßige Bewertung und Korrektur der Ergebnisse.
+ Unterstützt und fördert 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, Kommunikationsprotokolle)
- 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, 21]. 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
• XP benutzt einen hochgradig iterativen 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.
”
• Die Entwickler schätzen den Aufwand zur Realisierung für jede Story.
• Der Kunde entscheidet, welche Story in der nächsten Iteration implementiert wird.
• 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.
• Die Entwickler schreiben Tests für ihre Klassen (unit tests).
• Der Kunde entwickelt Testfälle für eine Story (functional tests).
• 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 und typischerweise dem Alltag entnommen.
• Sie ersetzt den Architekturentwurf und erleichtert die Kommunikation.
• Beispiele für Metaphern sind die Desktop-Metapher für graphische Oberflächen (der
Bildschirm entspricht einer Schreibtischoberfläche) oder die Lagerhaus-Metapher für
die Verwaltung von Daten in einem System.
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 Quelltextes).
• Erreicht wird eine Verbesserung der Verständlichkeit und Änderbarkeit des Quelltextes.
• Durch die umfangreiche Testsammlung kann mit hoher Wahrscheinlichkeit festgestellt
werden, ob sich bei einer Refaktorisierung Fehler eingeschlichen haben.
• Der Quelltext selbst soll schließlich in hohem Maße selbsterklärend sein und anderweitige Dokumentation ersetzen.
2.7. EXTREMES PROGRAMMIEREN (XP)
23
7. Programmieren in Paaren
• Die Programmierung erfolgt immer in Paaren vor einem Rechner.
• Während einer der Entwickler programmiert, prüft der Partner den Quelltext auf logische Fehler und ob der neue Quelltext zur Systemmetapher passt 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 Programmieren 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äßig (mindestens einmal am
Tag) in das aktuelle zentrale Archiv der Quelltexte integriert.
• Dabei müssen nach der Integration 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 Kundenvertreter 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 Einfluss 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 relativ vage.
24
KAPITEL 2. PROZESSMODELLE
Kapitel 3
Unified Modeling Language (UML)
Die Unified Modeling Language [3, 18, 26] 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 Diagrammarten 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 und Aussagekraft zu erhöhen.
Die UML gibt es in verschiedenen Versionen. Zum Zeitpunkt der Erstellung dieses Skripts war
die Version 1.3 [16] die aktuelle Version. Bei der Überarbeitung sind einige Anpassungen auf die
Version 1.4 [17] vorgenommen worden.
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, Ope25
26
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
rationen und der Semantik für eine Menge von Objekten. Alle Objekte einer Klasse entsprechen
dieser Definition.
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 Vorlesung
Bemerkungen:
• 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 (wie z.B. Vorlesung)
und sind oftmals Hauptwörter. Der Klassenname kann auch einen Pfad enthalten, der ein
Paket (package) identifiziert.
• Namen von Attributen und Operationen beginnen typischerweise mit einem Kleinbuchstaben
(titel, toHtml). Namen von Attributen sind in der Regel Hauptwörter, 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 Datentyp des Rückgabewerts definiert werden kann. Ebenso können optional die Parameter der Operation angegeben werden, wobei wiederum jeder Parameter durch einen Namen identifiziert wird und
optional einen Datentyp und einen Standardwert (default) besitzt.
• Zur Identifikation von relevanten Klassen wird oftmals die Hauptwortidentifikation (noun
identification technique) benutzt, bei der aus einer Anforderungsspezifikation alle relevanten
Hautpwörter extrahiert werden. Klassen beschreiben insbesondere oft
–
–
–
–
greifbare Dinge oder Stücke aus der Realität (z.B. Vorlesung, Personen, Räume),
Rollen (z.B. Dozent, Student),
Ereignisse (z.B. Ankunft, Weggang, Anfrage) oder
Interaktionen (z.B. Treffen).
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: UML-Klasse Vorlesung mit Sichtbarkeitsangaben
Bemerkungen:
• Zugriffsrestriktionen werden durch Symbole vor dem Namen eines Attributs oder einer Operation angezeigt.
• Das Symbol + steht für public und bedeutet, dass das Attribut bzw. die Operation für alle
sichtbar und benutzbar ist.
• Das Symbol # steht für protected und bedeutet, dass das Attribut bzw. die Operation für die
Klasse und alle abgeleiteten Klassen sichtbar und benutzbar ist.
• Das Symbol − steht für private und bedeutet, dass das Attribut bzw. die Operation nur für
die Klasse sichtbar und benutzbar ist.
• Das Symbol steht für package und bedeutet, dass das Attribut bzw. die Operation nur innerhalb des Pakets 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: UML-Klasse Vorlesung mit Gültigkeitsbereichen
Bemerkungen:
• 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 ebenfalls 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 allgemeineren und einem speziellen Element, wobei das spezielle Element weitere Eigenschaften hinzufügt und sich kompatibel zum Allgemeinen verhält. Die Vererbung ist ein Programmiersprachenkonzept, mit dem sich Generalisierungen / Spezialisierungen ausdrücken lassen.
Veranstaltung
Generalisierungen /
Spezialisierungen
Vorlesung
#dozent: Person
-skript: URL
+getDozent(): Person
+getSkript(): URL
-titel: String
-semester: String
+getTitle(): String
+getSemester(): String
Uebung
Seminar
Praktikum
#leiter: Person
-aufgaben: URL
Abbildung 3.5: Generalisierung der Vorlesung in eine Veranstaltung
Bemerkungen:
29
3.2. KLASSENDIAGRAMME
• Generalisierungen / Spezialisierungen werden in UML durch Pfeile dargestellt, wobei die
Pfeilspitze zur allgemeineren Klasse zeigt. Die Pfeilspitze ist leer und nicht gefüllt.
• Ein Objekt einer spezialisierten Klasse kann ein Objekt einer allgemeineren Klasse in jedem
Kontext ersetzen, in dem ein Mitglied der allgemeineren Klasse erwartet wird, aber nicht
anders herum.
• Eine natürlichsprachliche Prüfung ist die Frage, ob ein Exemplar u einer Spezialisierung U
ein Exemplar g einer Generalisierung G ist, d.h. ob u ein g ist (is-a Beziehung).
• Die Implementierung von Generalisierungen / Spezialisierungen kann in einer objekt-orientierten
Sprache durch Vererbung erfolgen. Vererbung ist eine Implementierungsbeziehung. Gelegentlich ist aber auch die Realisierung mit anderen Mitteln (Komposition) sinnvoll, um beispielsweise eine zu starke Kopplung zwischen den Klassen zu vermeiden.
3.2.5
Abstraktheit
Operationen oder auch ganze Klassen können abstrakt (abstract) sein. Eine abstrakte Operation ist
zwar innerhalb einer Klasse definiert, muss 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 als eine abstrakte Klasse
Bemerkungen:
• Abstrakte Operationen oder Klassen werden durch die kursive Schreibweise der Namen angedeutet.
3.2.6
Aggregation / Komposition
Eine Aggregation (aggregation) ist ein Beziehung, deren beteiligte Klassen eine Ganzes-TeileHierarchie darstellen. Eine Komposition (composition) ist eine strengere Form der Aggregation,
bei der die Teile vom Ganzen existenzabhängig sind.
30
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
Veranstaltung
-titel: String
-semester: String
+getTitle(): String
+getSemester(): String
Vorlesung
Uebung
#dozent: Person
-skript: URL
+getDozent(): Person
+getSkript(): URL
Aggregation
Seminar
Lehrangebot
Praktikum
#leiter: Person
-aufgaben: URL
Abbildung 3.7: Lehrangebot besteht aus Veranstaltungen
Bemerkungen:
• 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.
• Ein typisches Beispiel für eine Komposition ist ein Spielfeld, das aus einer Menge von Feldern besteht.
3.2.7
Assoziationen
Assoziationen (associations) beschreiben allgemein Beziehungen zwischen Klassen. Generalisierung / Spezialisierung und Aggregation / Komposition sind spezielle Assoziationen. Entsprechend
kann man diese speziellen Assoziationen auch jeweils durch allgemeine Assoziationen ausdrücken.
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: Personen halten Vorlesungen und arbeiten für andere Personen
Bemerkungen:
31
3.2. KLASSENDIAGRAMME
• 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 wie vielen 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.
Veranstaltung
-titel: String
-semester: String
+getTitle(): String
+getSemester(): String
Person
1
+name: String
haelt
Vorlesung
Uhrzeit
+zeit: String
0..* #dozent: Person
-skript: URL
+getDozent(): Person
+getSkript(): URL
Assoziationsklasse
Abbildung 3.9: Personen halten Vorlesungen zu bestimmten Uhrzeiten
Bemerkungen:
• Assoziationsklassen werden durch gestrichelte Linien mit der Assoziation verbunden.
Gelegentlich sind an einer Assoziation mehr als zwei Klassen beteiligt. Man spricht dann von so
genannten mehrgliedrigen Assoziationen.
32
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
Raum
+nummer: String
Veranstaltung
-titel: String
-semester: String
+getTitle(): String
+getSemester(): String
Person
+name: String
Vorlesung
#dozent: Person
-skript: URL
+getDozent(): Person
+getSkript(): URL
mehrgliedrige Assoziation
Abbildung 3.10: Personen halten Vorlesungen in einem bestimmten Raum
Bemerkungen:
• Mehrgliedrige Assoziationen werden durch Linien dargestellt, die durch eine zentrale Route
miteinander verbunden sind.
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
+getName(): String
Veranstaltung
-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.11: Mitarbeiter realisieren die Schnittstelle Person
Bemerkungen:
• Eine Schnittstellenklasse wird durch den Stereotyp interface gekennzeichnet.
• Die Realisierung einer Schnittstellenklasse wird durch einen gestrichelten Pfeil (analog zur
Generalisierung / Spezialisierung) angezeigt.
33
3.2. KLASSENDIAGRAMME
Schnittstellen können alternativ auch kompakter ohne weitere Details durch einen einfachen Kreis
dargestellt werden.
Veranstaltung
Person
Realisierung
Schnittstelle
chef
1
Mitarbeiter
-titel: String
-semester: String
+getTitle(): String
+getSemester(): String
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
Bemerkungen:
• Eine Linie, die einen solchen Kreis mit einem Modellelement verbindet, signalisiert, dass das
Modell die Schnittstelle realisiert.
3.2.9
Abhängigkeiten
Eine Abhängigkeit (dependency) ist eine Beziehung zwischen zwei Modellelementen, die anzeigt,
dass 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 einen 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 benutzenden
Klassen erfordern.
3.2.10
Objekte
Objekte sind konkrete Ausprägungen (Instanzen) von Klassen. Sie stellen einen Schnappschuss
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.
34
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
c01: Vorlesung
titel = "Informatik C"
semester = "WS 2001/2002"
skript = "http://www.vorlesungen.uos.de/informatik/c01/"
js: Mitarbeiter
name = "Juergen Schoenwaelder"
sec01: Vorlesung
haelt
haelt
haelt
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).
• 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 dargestellt werden.
Für jedes Attribut existiert dann eine Zeile in der Form Attributename = Wert.
• Beziehungen zwischen Objekten werden durch einfache Linien dargestellt.
Das Diagramm von Abbildung 3.13 kann entsprechend auch viel kompakter dargestellt werden,
wenn die Details nicht wesentlich sind (Abbildung 3.14).
c01: Vorlesung
js: Mitarbeiter
haelt
haelt
haelt
sec01: Vorlesung
bs01: Vorlesung
Abbildung 3.14: Kompakte Darstellung eines Objektdiagramms
35
3.2. KLASSENDIAGRAMME
Es ist auch möglich, so genannte Multiobjekte (Abbildung 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.
Vorlesungsverwaltung
Vorlesung
Mitarbeiter
Uebung
Seminar
Praktikum
Abbildung 3.16: Paketdiagramm
Bemerkungen:
• 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 des Pakets auf der Registerlasche.
• Jedes Element gehört zu genau einem Paket.
• Pakete können andere Pakete enthalten (Hierarchie).
• 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.
• Zwischen Paketen können Abhängigkeiten existieren, die oftmals durch das Importieren von
Paketelementen entstehen.
36
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
• Pakete können auch Generalisierungen/Spezialisierungen von anderen Paketen sein.
Abhaengigkeit
GUI
Generalisierung/
Spezialisierung
Browser
Mac GUI
Windows GUI
Unix GUI
Abbildung 3.17: Paketdiagramm mit Abhängigkeiten und Generalisierung/Spezialisierung
37
3.3. SEQUENZDIAGRAMME
3.3 Sequenzdiagramme
Ein Sequenzdiagramm (sequence diagram) 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.
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
Bemerkungen:
• Die Zeitachse verläuft von oben nach unten.
• Objekte werden wie in Objektdiagrammen durch Rechtecke dargestellt.
• Ausgehend vom Objekt zeigt eine gestrichelte vertikale Lebenslinie die Dauer der Existenz
des Objekts an.
• Das Ende der Existenz eines Objekts wird durch ein Kreuz am Ende der Lebenslinie hervorgehoben.
• Breite vertikale Balken signalisieren welche Objekte gerade aktiv sind und deuten so den
Kontrollfluss 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
Bemerkungen:
• Asynchrone Nachrichten, die keine Antwort erzwingen, werden durch offene Pfeile dargestellt.
• Eine Nachricht, die einem Operationsaufruf entspricht, wird durch einen gefüllten Pfeil dargestellt.
• Der Abschluss 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.
39
3.4. KOLLABORATIONSDIAGRAMME
3.4 Kollaborationsdiagramme
Ein Kollaborationsdiagramm (collaboration diagram) zeigt 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.
s: Caller
1.1: setDialTone()
1: liftReceiver
2: *dialDigit(d)
2.1.3: connect()
2.1.5: disconnect()
: Switch
2.1: <<create>>
2.2: <<destroy>>
c: Conversation
2.1.2: connect()
2.1.7: disconnect()
2.1.1: ring()
2.1.4: connect()
2.1.6: disconnect()
2.1.1.1: liftReceiver
2.1.4.1: hangUpReceiver
r: Caller
Abbildung 3.20: Kollaborationsdiagramm für eine Telefonverbindung
Bemerkungen:
• Der zeitliche Zusammenhang der Nachrichten wird durch die hierarchische Nummerierung
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
Zustandsdiagramme (statechart diagrams) veranschaulichen einfache Zustandsautomaten. Dabei
stehen die Zustände im Vordergrund der Betrachtung. Die Transitionsmöglichkeiten zwischen den
Zuständen werden durch gerichtete Pfeile angedeutet.
Das folgende Beispiel zeigt einen Zustandsautomaten für eine einfache Klimasteuerungsanlage.
powerOff
powerOn
idle
tooCold
tooHot
atTemp
atTemp
cooling
tooHot
heating
tooCold
Abbildung 3.21: Zustandsdiagramm einer Klimaregelungsanlage
Bemerkungen:
• Der Startzustand wird durch einen ausgefüllten Kreis dargestellt.
• Die Zielzustände werden durch einen umringten gefüllten Kreis dargestellt.
• An den Transitionspfeilen können die Namen der auslösenden Ereignisse (events) angebracht
werden.
• Zustände können selbst wiederum interne Zustandsdiagramme enthalten.
41
3.6. AKTIVITÄTSDIAGRAMME
3.6 Aktivitätsdiagramme
Aktivitätsdiagramme (activity diagrams) beschreiben den Kontrollfluss in einem System. Im Gegensatz zu Sequenzdiagrammen, die den Nachrichtenaustausch zwischen Objekten darstellen, beschreiben Aktivitätsdiagramme den dynamischen Ablauf von Aktivitäten. Eine Aktivität ist eine
fortlaufende nicht-atomare Ausführung in einer Zustandsmaschine.
Das folgende Aktivitätsdiagramm zeigt den Ablauf der Aktivitäten beim Kaffeekochen:
Durst auf Kaffee
Filter einsetzen
Wasser einfuellen
Kaffee einfuellen
Tassen suchen
Kaffee kochen
Kaffee eingiessen
Kaffee trinken
Abbildung 3.22: Kaffeepause als Aktivitätsdiagramm
Bemerkungen:
• Der Startzustand wird durch einen ausgefüllten Kreis dargestellt.
• Die Zielzustände werden durch einen umringten gefüllten Kreis dargestellt.
• Eine Aktivität wird durch ein abgerundetes Rechteck dargestellt.
42
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
• An breiten horizontalen Linien können Kontrollflüsse aufgeteilt und wieder zusammengeführt
werden.
Durch die Einführung von Schwimmlinien (swim lanes) können Zuständigkeiten ausgedrückt werden:
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
43
3.7. ANWENDUNGSFALLDIAGRAMME
3.7 Anwendungsfalldiagramme
Ein Anwendungsfall (use case) beschreibt eine Menge von Aktivitäten eines Systems aus der Sicht
seiner Akteure (actor).
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
FirmenKunde
Abbildung 3.25: Anwendungsfall Kreditkartensystem
Bemerkungen:
Haendler
Bank
44
KAPITEL 3. UNIFIED MODELING LANGUAGE (UML)
• 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.
• 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
Bemerkungen:
• Eine Komponente wird als Rechteck dargestellt, das am linken 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, das ü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
Bemerkungen:
• Ein Knoten wird durch einen Quader dargestellt.
• Knoten besitzen Namen, die typischerweise kurze Hauptworte 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.
47
3.10. BEISPIEL: FAHRSTUHL
• Die Steuerungslogik muss nach dem Erreichen einer Ziel-Etage die Tür öffnen und wieder
schließen.
• 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 (Abbildung 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 (Abbildung 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 Abbildung 3.30 mit
der Multiplizität 1:1 drückt aus, dass 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, dass 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 veranlasst die Beleuchtung des Knopfes und instruiert den Fahrstuhl sich in die entsprechende Etage zu
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
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.
aPassenger
anElevatorButton
aController
anElevator
aDoor
pressed
update
illuminate
move
cancel
open
close
Abbildung 3.31: Sequenzdiagramm für eine Fahrstuhlsteuerung
Analog zum Sequenzdiagramm lässt sich ein Kollaborationsdiagramm angeben (Abbildung 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) [10] 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
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).
Zur Dokumentation von Entwurfsmustern wird eine Schablone vorgeschlagen, die aus folgenden
Elementen besteht [10]:
• Name des Entwurfsmusters:
Der Name und die Klassifikation des Entwurfsmusters. Ein guter Name ist wichtig, da er das
Vokabular der Entwickler prägt.
51
52
KAPITEL 4. ENTWURFSMUSTER
• Aufgabe:
Kurze Beschreibung, was ein Entwurfsmuster leistet und welche 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.
• 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. 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.
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
um die Abhängigkeiten zwischen Objekten und Softwarekomponenten zu kontrollieren. Wichtige
Maßnahmen sind nach [10]:
• Programmiere zu einem Interface und nicht zu einer Implementation.
• Bevorzuge die Komposition von Objekten gegenüber der 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 im Vergleich zu Design Pattern 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 muss.
• Wenn eine Klasse den abgeleiteten 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 verschiedene 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 Schnittstelle 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 Iterators.
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, dass 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, dass verschiedene Algorithmen leicht realisiert werden
können und den Nachteil, dass 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, dass Ä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 Iterator Schnittstelle 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, dass 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 nur über die Component-Schnittstelle.
Leaf-Objekte implementieren eine Operation in der Regel direkt. Composite-Objekte implementieren Funktionen in der Regel dadurch, dass die Operation an alle enthaltenen Objekte weitergeleitet
wird. Dabei können vor und nach dem Weiterleiten noch zusätzliche Berechnungen stattfinden.
Konsequenzen
• Das Pattern 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
• xxx
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 Subject benachrichtigt die Observer wenn eine Zustandsänderung sattfindet.
• Wenn ein konkreter Observer benachrichtigt wird, dann kann er vom Subject 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 Subjects 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, dass ein Observer von mehreren Subjects abhängig sein kann. In
solchen Fällen muss 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, dass 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, dass 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, dass 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
Oftmals ist es notwendig, eine Aufgabe in verschiedenen Arten zu lösen. Typische Beispiele sind
das Speichern von Daten in unterschiedlichen Dateiformaten, die Komprimierung von Daten mit
unterschiedlichen Komprimierungsverfahren oder die Darstellung von Daten in unterschiedlichen
Diagrammarten.
Anwendbarkeit
• Wenn viele verwandte Klassen 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 lose 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
Oftmals ist es sinnvoll, die Ausführung einer Aktion einem von mehreren möglichen Realisierungen
zu überlassen. Beispielsweise kann beim Öffnen einer Datei der Dateiname einer Reihe von Programmen präsentiert werden, bis man ein Programm gefunden hat, das den Dateinamen akzeptiert
und öffnen kann.
Anwendbarkeit
• Wenn es mehrere potentielle Bearbeiter gibt, die einen gegebenen Auftrag potentiell ausführen
können und es unklar ist, welcher Bearbeiter der geeignete ist.
• Wenn die Menge der Bearbeiter dynamisch verändert werden können soll.
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 übernimmt 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
4.8 Anti Pattern: Creeping Featuritis
• Problem: You want to satisfy your customers. You want to do the best possible job you can
with your product, and thus win fame and fortune for yourself.
• Forces: Programmers have big egos. Customers don’t always know what they want. Programmers often miss the distinction between what’s needed and what’s neat”.
• Anti-Solution: You get all of the programmers and designers together in a big room (aka
DesignByCommittee) and everybody adds into the product what they want. This process
feeds off itself and everyone starts adding new features as the process continues.
• Discussion: CreepingFeaturitis can begin, or continue at any stage in the software development process. It’s most common in the Analysis stage, where it results in either an unending
Analysis stage (see AnalysisParalysis) or in an unrealistically ambitious specification. In the
design phase CreepingFeaturitis is characterized by adding more bells and whistles than were
called for in the Analysis, or by trying to abstract everything before anything is ever made
concrete. In the coding stage, it is characterized by coding that never ends as programmers
continue to add öne more feature”.
4.9
Anti Pattern: Design By Committee
• Problem: Given a political environment in which no one person has enough clout to present
a design for a system and get it approved, how do you get a design done?
• Forces: Often, a problem can be clearly identified for which no existing solution fits well. For
instance, the DOD figured out in the mid-late ’70s that the existing programming languages
that were being used for big military projects just didn’t cut it. FORTRAN, JOVIAL and
COBOL were not going to allow programmers to write the programs necessary to build things
like SDI – they didn’t provide large-scale programming support, encapsulation, or a host of
other things that language designers had decided that were needed. However, no one had the
force of personality or knowledge to drive through a single, consistent solution. There was no
Alan Kay or Grace Hopper to provide a vision. So they:
• Solution: Put together a big committee to solve the problem. Let them battle it out amongst
themselves and finally take whatever comes out the end.
• Discussion: The problem with DesignByCommittee is that everyone on the committee has
their own vision of the final product. They each fight to get their $0.02 added in to the final
version. Since there is no unifying vision, what results is a mish-mash of features in which
everybody gets their share put in. By the way, this story relates how Ada came about.
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 Prozess und
sollte daher auch so organisiert werden, dass nicht die Entwickler selbst für die Qualitätssicherung
zuständig sind. Außerdem ist darauf zu achten, dass bei der Qualitätssicherung das Produkt im
Vordergrund steht und nicht etwa die Qualität der Entwickler.
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.
• Kontrollflussfehler: 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 zu wenig durchlaufen wird.
• Initialisierungsfehler: Falsche oder fehlende Initialisierungen.
• Datenflussfehler: 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, bei dem der Quelltext eines Programms durch
andere Personen 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 muss 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 formalen
Inspektionsprotokolle erzeugt werden. Eine Optimierung des Entwicklungsprozesses ist allerdings
durch die fehlende Erfassung von Daten 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 ist 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.
5.3. TESTVERFAHREN
*
*/
71
A sentence must be terminated by a dot.
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.
• Funktionale Testverfahren setzen eine vollständige und widerspruchsfreie Spezifikation voraus und sie 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.
72
KAPITEL 5. QUALITÄTSSICHERUNG
Betrachten wir die Konstruktion von Äquivalenzklassen für die Methode countVowels(). Als
Spezifikation liegt lediglich der Kommentar vor, aus dem sich aber schon drei Äquivalenzklassen
ableiten lassen:
- 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() Vokalen unterscheiden können muss, 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.TestCase;
import junit.framework.Assert;
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()
5.3. TESTVERFAHREN
73
{
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, dass die Spezifikation von countVowels() unvollständig ist.
5.3.2
Kontrollfluss-orientierte Testverfahren
Kontrollflussorientierte Testverfahren betrachten bestimmte Strukturelemente eines Programms wie
z.B. Anweisungen, Zweige, Bedingungen oder Pfade und fordern von den Testfällen, dass der Kontrollfluss jedes dieser Elemente mindestens einmal erreicht (Überdeckungstest).
Programmdarstellung als Kontrollflussgraph:
• Knoten stellen Anweisungen oder Bedingungen einer Kontrollstruktur dar.
• Kanten stellen den möglichen Kontrollfluss zwischen zwei Anweisungen dar.
• Ein Pfad ist eine Kombination von Kanten, die vom Startknoten zum Endknoten führt.
Die Methode countVowels() besitzt den in Abbildung 5.1 dargestellten Kontrollflussgraphen.
Der wesentliche Wert kontrollfluss-orientierter Verfahren liegt darin, dass die Qualität der Testfallsammlung beurteilt werden kann. Kontrollfluss-orientierte Testverfahren werden eher selten zur
Konstruktion von Testfällen benutzt.
Anweisungsüberdeckung
• Jeder Knoten des Kontrollflussgraphen muss einmal ausgeführt werden.
• Nicht sehr leistungsfähiges Minimalkriterium.
• Unentdeckte Fehler in nicht ausgeführten Zweigen.
Zweigüberdeckung
• Jede Kante des Kontrollflussgraphen muss einmal durchlaufen werden.
• Realistisches Minimalkriterium, das die Anweisungsüberdeckung einschließt.
• Unentdeckte Fehler bei Kombinationen und Wiederholungen von Schleifen.
74
KAPITEL 5. QUALITÄTSSICHERUNG
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: Kontrollflussgraph für countVowels()
Bedingungsüberdeckung
• Jede Bedingung muss mindestens einmal den Wert false und mindestens einmal den Wert
true annehmen.
• Bei atomarer Bedingungsüberdeckung muss jede einfache Bedingung den Wert true oder
false annehmen. Die atomare Bedingungsüberdeckung umfasst nicht die Anweisungsüberdeckung.
• Bei der minimalen Mehrfachbedingungsüberdeckung muss 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 Kompromiss und impliziert die Zweigüberdeckung bei
einer Anweisungsüberdeckung.
Pfadüberdeckung
• Jeder Pfad des Kontrollflussgraphen muss einmal durchlaufen werden.
• Die Pfadüberdeckung ist ein theoretisches Kriterium, da in den meisten praktischen Fällen
durch Schleifen unendlich viele Pfade möglich sind. Die Pfadüberdeckung dient als Vergleichsmaßstab für andere Überdeckungstests.
• Selbst eine Pfadüberdeckung findet nicht alle Fehler.
5.3. TESTVERFAHREN
5.3.3
75
Mutationstesten
Beim Mutationstesten werden kleine Änderungen (Mutationen) im Quelltext vorgenommen und es
wird anschließend überprüft, wie viele der so erzeugten Mutanten von den Tests erkannt werden.
Auch hier geht es wiederum um die Frage, welche Qualität eine Testfallsammlung besitzt.
• 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, dass sich komplexe Fehler aus
”
einfachen zusammensetzen.
Man unterscheidet folgende Mutationsoperatoren:
• Berechnungsfehler:
– Ändern von arithmetischen Operationen
– Löschen von arithmetischen (Teil-) Ausdrücken
– Ändern von Konstanten
• Schnittstellenfehler:
– Vertauschen / Ändern von Parametern
– Aufruf anderer Methoden
• Kontrollflussfehler:
– 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
• Datenflussfehler:
– Vertauschen von Variablen in einem Sichtbarkeitsbereich
– Änderungen in der Indexberechnung
Wird eine Mutation nicht von den Testfällen erkannt, so ist zu überprüfen, inwieweit die Mutation
nicht die Korrektheit des Programms beeinflusst hat. (Manche Mutationen wie z.B. die Vertauschung von Anweisungen können durchaus zu einem weiterhin korrekten Programm führen.)
76
KAPITEL 5. QUALITÄTSSICHERUNG
5.3.4
Regressionstesten
Beim Regressionstesten geht es darum, nach Änderungen am Programmtext sicherzustellen, dass
die bisher bestandenen Tests auch von der neuen, veränderten Programmversion 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, dass tendenziell die Qualität der Software im Laufe der Zeit ebenfalls
besser werden sollte.
5.4 Softwaremetriken
Mit Hilfe von speziellen Metriken kann man versuchen, die Eigenschaften einer Software durch die
Analyse des Quelltextes zu ermitteln. Durch den Vergleich der gemessenen Metriken mit Werten
aus anderen Projekten und Standards einer ganzen Organisation kann man versuchen, die Qualitätseigenschaften der Software und des Entwicklungsprozesses zu messen.
Das grundlegende Problem hierbei ist die Tatsache, dass die eigentlich interessanten Größen wie
z.B. Wartbarkeit, Zuverlässigkeit, Portierbarkeit und Benutzerfreundlichkeit nicht messbar sind.
Stattdessen werden andere leichter berechenbare Metriken als Indikatoren für diese Größen benutzt, was natürlich immer reichlich Spielraum zur Interpretation lässt. Man kann Metriken in zwei
Klassen einteilen:
• Statische Metriken werden durch Messungen der Artefakte (Entwurfsdiagramme, Quelltexte,
Dokumentation) ermittelt.
• Dynamische Metriken werden durch Messungen bei der Ausführung von Programmen ermittelt.
Gebräuchliche statische Softwaremetriken:
• Fan-in / Fan-out: Die Anzahl der Funktionen, die eine gegebene Funktion aufrufen (fan-in)
bzw. die Anzahl der Funktionen, die von einer gegebenen Funktion aufgerufen werden (fanout). Ein hoher Fan-in deutet auf eine enge Verknüpfung der betrachteten Funktion hin und
entsprechend können Änderungen an der Funktion tiefgreifende Auswirkungen haben. Ein
hoher Fan-out deutet darauf hin, dass die betrachtete Funktion eine komplexe Steuerlogik
besitzt.
• Größe des Quellcodes: Generell sind in einem großen Programm mehr Fehler zu erwarten
als in einem kleinen Programm. Obwohl die Größe des Quellcodes eine sehr einfache und
unscharfe Metrik ist, wird sie in der Praxis gerne eingesetzt.
• Länge der Bezeichner: Bei dieser Metrik wird die durchschnittliche Länge von Bezeichnern
ermittelt. Ein Programm mit sehr kurzen Bezeichnern ist in der Regel schwer verständlich.
Dasselbe gilt allerdings auch für Programme mit zu langen Bezeichnern.
• Tiefe der Verschachtelungen und Verzweigungen: Tief verschachtelte bedingte Anweisungen
sind in der Regel schwer verständlich.
77
5.4. SOFTWAREMETRIKEN
• Fog-Index: Ein Maß für die durchschnittliche Länge von Wörtern und Sätzen in Dokumenten.
Ein Dokument mit sehr langen Sätzen kann schwerer zu verstehen sein.
• Zyklomatische Komplexität: Die zyklomatische Komplexität wurde von McCabe eingeführt
und ist auf der Basis eines Kontrollflussgraphen definiert. Die zyklomatische Zahl V (G) eines
Kontrollflussgraphen G mit e Kanten, n Knoten und p Verbindungen zu anderen Komponenten ist gegeben durch:
V (G) = e − n + 2p
Im Normalfall ist p = 1. Für den Kontrollflussgraphen aus Abbildung 5.1 ergibt sich mit
e = 9, n = 8 und p = 1 die zyklomatische Komplexität V (G) = 9 − 8 + 2 · 1 = 3.
Die zyklomatische Komplexität ist relativ einfach zu berechnen und besser als die Anzahl der
Quelltextzeilen. Allerdings werden viele Programmmerkmale stark vereinfacht (z.B. Komplexität von Bedingungen) und es gibt entsprechend viele Vorschläge für Verbesserungen der
zyklomatischen Komplexität.
• Halstead Metriken: Grundlage der Halstead-Metriken sind die in einem Programm benutzten unterschiedlichen Operanden und Operatoren. Ein Operator in diesem Zusammenhang ist
jedes Symbol oder Schlüsselwort, das eine Aktion kennzeichnet. Operanden sind alle Symbole, die Daten darstellen (z.B. Variable, Konstante, Sprungmarken). Bestimmt werden die
folgenden Basisgrößen:
η1
η2
N1
N2
:
:
:
:
Anzahl der unterschiedlichen Operatoren
Anzahl der unterschiedlichen Operanden
Gesamtzahl der verwendeten Operatoren
Gesamtzahl der verwendeten Operanden
Die Größe des Vokabulars ist gegeben durch η = η1 + η2 und die Länge der Implementierung
durch N = N1 + N2 . Ein Maß für die Schwierigkeit ein Programm zu schreiben oder zu
verstehen ist die Metik D, die wie folgt definiert ist:
D=
η1 · N2
2η2
Nach Halstead beschreibt D den Aufwand zum Schreiben von Programmen, den Aufwand
bei Reviews und das Verstehen bei Wartungsvorgängen. Insgesamt sind Halstead-Metriken
einfach zu berechnen und ein brauchbares Maß für Komplexität. Allerdings sind die Klassifikationsregeln für Operanden und Operatoren nicht unbedingt eindeutig und die HalsteadMetriken berücksichtigen nur lexikalische Komplexität und ignorieren Namensräume in moderneren Programmiersprachen.
• Tiefe und Breite des Vererbungsgraphen: Gemessen wird die Struktur des Vererbungsgraphen.
Sehr tiefe Vererbungsgraphen sind in der Regel schwer zu überschauen und zu warten.
• Gewichtete Anzahl der Methoden pro Klasse: Bestimmung der Anzahl Methoden pro Klasse,
wobei die Methoden gewichtet werden. Dabei kann die Komplexität einer Methode in die
Gewichtung einfließen.
• Anzahl überschreibender Methoden: Bestimmung, wie oft eine Methode überschrieben wird.
Wird eine Methode sehr häufig überschrieben, so kann es sein, dass die realisierte Semantik
unpassend gewählt wurde.
78
KAPITEL 5. QUALITÄTSSICHERUNG
Allgemein ist bei der Verwendung von Metriken zu berücksichtigen, dass Programmierer bei Kenntnis der verwendeten Metriken den Quelltext so organisieren können, dass ein beabsichtigter Effekt
eintritt.
Ein etwas anderer Ansatz geht davon aus, dass Entwickler sehr wohl relativ schnell schlechten
Quelltext erkennen können, auch wenn Sie oftmals nicht spontan erklären können, warum der
Quelltext schlecht ist (bad smelling code). Einige typische Indikatoren für schlecht riechenden“
”
Quelltext:
• Duplizierter Quelltext
• Zu lange und komplexe Methoden
• Klassen mit sehr vielen Instanzvariablen
• Klassen mit zu wenigen Instanzvariablen
• Klassen mit zu viel Quelltext
• Klassen mit zu wenig Quelltext
• Viele sehr ähnliche Unterklassen
• Leere catch-Anweisungen
• Mehrfachbenutzung von Variablen
• ...
Die graphische Darstellung mehrerer Metriken erfolgt oftmals unter Verwendung von Kiviat-Graphen.
5.5 Qualitätssicherung mit ISO 9000
Das ISO 9000 Normenwerk legt für das Auftraggeber-Lieferantenverhältnis einen allgemeinen, organisatorischen Rahmen zur Qualitätssicherung fest.
Literaturhinweise
Informationen zur Qualitätssicherung findet man bei Balzert [1] und Sommerville [25]. Ein mittlerweile klassisches Buch zum Thema Softwaretests stammt von Myers [15].
Kapitel 6
Werkzeuge
Zur effizienten 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 [29].
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, dass 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 einfache 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;
79
80
KAPITEL 6. WERKZEUGE
15
=>
16
17
18
19
main[1] next
i = 0;
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)
Etwas 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.
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.
6.2. PROGRAMMKONSTRUKTION
Abbildung 6.1: Data Display Debugger
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
$(JAVAC) $(JFLAGS) $<
all: $(CLASSES)
test: $(CLASSES)
81
82
KAPITEL 6. WERKZEUGE
$(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, dass mehrere Entwickler gleichzeitig 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
• 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.
6.3. VERSIONSMANAGEMENT
83
• 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, dass 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.
• Mehrere Entwickler können sich lokale 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
checkout
commit
add
remove
update
diff
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.
84
KAPITEL 6. WERKZEUGE
6.4 Testwerkzeuge
Testwerkzeuge haben die Aufgabe, die automatische Durchführung von Komponententests oder Regressionstests zu unterstützen.
6.4.1
JUnit
Hinter JUnit [9, 8] 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.
• Die Klasse TestSuite beschreibt eine Menge von Tests, wobei die Elemente einer TestSuite
wiederum TestSuites oder TestCases sein können.
• 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.
”
6.4. TESTWERKZEUGE
85
Beispiel 1 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. Es gelten die folgenden
Beziehungen:
9
f = c + 32
5
5
c = (f − 32)
9
Eine einfache Implementation der Klasse Temperature in Java könnte folgendermaßen aussehen:
// temperature/Temperature.java
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 Testfällen zur Klasse Temperature in JUnit:
// temperature/TemperatureTest.java
import
import
import
import
junit.framework.Test;
junit.framework.TestCase;
junit.framework.TestSuite;
junit.framework.Assert;
public class TemperatureTest extends TestCase
{
private Temperature zeroCelsius;
86
KAPITEL 6. WERKZEUGE
private Temperature zeroFahrenheit;
private Temperature hundredCelsius;
private Temperature 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(), precision);
Assert.assertEquals(0, zeroFahrenheit.getFahrenheit(), precision);
}
6.5. INTEGRIERTE ENTWICKLUNGSUMGEBUNGEN
87
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
88
KAPITEL 6. WERKZEUGE
Teil II
Graphische Benutzungsoberflächen
89
Kapitel 7
Ergonomische Aspekte
Ziel der Software-Ergonomie ist die Entwicklung und Evaluierung gebrauchstauglicher Software-Produkte,
die Benutzer zur Erreichung ihrer Arbeitsergebnisse befähigen und dabei ihre Belange im jeweiligen Nutzungskontext beachten [1, 11]. 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 Softwaresystemen und Benutzern erhält man, wenn Softwaresysteme explizite Benutzermodelle anlegen und die Interaktion mit dem Benutzer entsprechend dieser Modelle steuern.
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)
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).
91
92
KAPITEL 7. ERGONOMISCHE ASPEKTE
• 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 empfiehlt sich, 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. Kommandosprachen).
• 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 [22] 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 angepasst. 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.2
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 muss 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 angepasst.
• 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. GRUNDSÄTZE ZUR DIALOGGESTALTUNG
7.2.3
93
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 angepasst 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 (Widerruf).
• 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).
• 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 angepasst, 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 minimalem Korrekturaufwand seitens des Benutzers erreicht werden kann.
• Benutzereingaben dürfen niemals 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.
94
KAPITEL 7. ERGONOMISCHE ASPEKTE
• 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 muss.
7.2.6
Individualisierbarkeit
Ein Dialog ist individualisierbar, wenn das Dialogsystem Anpassungen an die Erfordernisse der Arbeitsaufgabe sowie an die individuellen Fähigkeiten und Vorlieben des Benutzers zulässt.
• 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.
• Wahl unterschiedlicher Informations-Darstellungsformen.
• Möglichkeit, eigenes Vokabular zu benutzen, um eigene Bezeichnungen für Objekte und Arbeitsabläufe festzulegen.
• Möglichkeiten, 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 zugrunde liegenden 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 noch mal
zusammen [23]:
1. Versuche Konsistenz zu erreichen.
Aus ähnlichen Situationen sollten ähnliche Aktionsfolgen resultieren. In Prompts, Menüs und Hilfeinformationen sollten identische Begriffe verwendet werden.
7.3. ACHT GOLDENE REGELN FÜR DIE DIALOGGESTALTUNG
95
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.
5. Biete einfache Fehlerbehandlung.
Es sollte grundsätzlich nicht möglich sein, schwerwiegende Fehler zu begehen. Falls ein Fehler 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.
96
KAPITEL 7. ERGONOMISCHE ASPEKTE
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:
97
98
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 zugrunde
”
liegende API nicht immer einfach und unproblematisch.
8.2 Ereignisorientiere Programmierung
• Ein klassisches“ Programm liest Eingaben, die es durch eine sequentielle (nicht-nebenläufige) Abar”
beitung 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 muss, hat im Wesentlichen folgenden
logischen Aufbau:
while (true) {
99
8.2. EREIGNISORIENTIERE PROGRAMMIERUNG
Object
EventObject
AWTEvent
ActionEvent
ContainerEvent
AdjustmentEvent
ComponentEvent
FocusEvent
KeyEvent
InputEvent
ItemEvent
PaintEvent
TextEvent
WindowEvent
MouseEvent
Abbildung 8.3: AWT Ereignisse
EventObject e = waitForNextEvent();
processEvent(e);
}
• Das Programm sollte in 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“ Funktio”
nen 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 ein”
friert“.
Ereignisse werden in Java durch spezielle Klassen dargestellt (Abbildung 8.3). Ein kleines Beispiel-Programm...
// events1/ActionExample.java
import java.awt.Frame;
import java.awt.Button;
public class ActionExample extends Frame
{
public ActionExample()
{
Button button = new Button("press me");
add(button);
}
public static void main(String args[])
{
100
KAPITEL 8. JAVA ABSTRACT WINDOWING TOOLKIT (AWT)
Frame f = new ActionExample();
f.pack();
f.show();
}
}
Bemerkungen:
• 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 so genannte EventListener Interfaces (Abbildung 8.4) implementieren, können auf passende Ereignisse reagieren.
«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
Damit lässt sich das Beispielprogramm vervollständigen:
// events2/ActionExample.java
import java.awt.Frame;
8.2. EREIGNISORIENTIERE PROGRAMMIERUNG
101
import java.awt.Button;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ActionExample extends Frame
{
public ActionExample()
{
Button button = new Button("press me");
add(button);
button.addActionListener(new ButtonActionListener());
}
class ButtonActionListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
System.out.println("button pressed");
}
}
public static void main(String args[])
{
Frame f = new ActionExample();
f.pack();
f.show();
}
}
Bemerkungen:
• 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?
// events3/ActionExample.java
import
import
import
import
import
import
java.awt.Frame;
java.awt.Button;
java.awt.event.ActionEvent;
java.awt.event.ActionListener;
java.awt.event.MouseEvent;
java.awt.event.MouseListener;
public class ActionExample extends Frame
{
102
KAPITEL 8. JAVA ABSTRACT WINDOWING TOOLKIT (AWT)
public ActionExample()
{
Button button = new Button("press me");
add(button);
button.addActionListener(new ButtonListener());
button.addMouseListener(new ButtonMouseListener());
}
class ButtonListener implements ActionListener
{
public void actionPerformed(ActionEvent e) {
System.out.println("button pressed");
}
}
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) {}
}
public static void main(String args[])
{
Frame f = new ActionExample();
f.pack();
f.show();
}
}
Bemerkungen:
• 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)
– 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. Mit Hilfe von Adapter-Klassen kann die Implementierung
”
verkürzt werden, da man keine leeren“ Methoden realisieren muss:
”
8.2. EREIGNISORIENTIERE PROGRAMMIERUNG
// events4/ActionExample.java
import
import
import
import
import
import
java.awt.Frame;
java.awt.Button;
java.awt.event.ActionEvent;
java.awt.event.ActionListener;
java.awt.event.MouseEvent;
java.awt.event.MouseAdapter;
public class ActionExample extends Frame
{
public ActionExample()
{
Button button = new Button("press me");
add(button);
button.addActionListener(new ButtonListener());
button.addMouseListener(new ButtonMouseAdapter());
}
class ButtonListener implements ActionListener
{
public void actionPerformed(ActionEvent e) {
System.out.println("button pressed");
}
}
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");
}
}
public static void main(String args[])
{
Frame f = new ActionExample();
f.pack();
f.show();
}
}
Mit Hilfe von anonymen Klassen lässt sich das Programm noch etwas kompakter schreiben:
// events5/ActionExample.java
import
import
import
import
java.awt.Frame;
java.awt.Button;
java.awt.event.ActionEvent;
java.awt.event.ActionListener;
103
104
KAPITEL 8. JAVA ABSTRACT WINDOWING TOOLKIT (AWT)
import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;
public class ActionExample extends Frame
{
public ActionExample()
{
Button button = new Button("press me");
add(button);
button.addActionListener(new ButtonListener());
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");
}
});
}
class ButtonListener implements ActionListener
{
public void actionPerformed(ActionEvent e) {
System.out.println("button pressed");
}
}
public static void main(String args[])
{
Frame f = new ActionExample();
f.pack();
f.show();
}
}
Ob sich durch diese kompaktere Darstellung die Lesbarkeit der Quelltextes verbessert hat, kann allerdings
diskutiert werden.
Kapitel 9
Java Foundation Classes (Swing)
Hinter dem Namen Swing [28] verbirgt sich eine Sammlung von Java-Klassen zur Implementation von graphischen Benutzungsoberflächen, die Teil der Java Foundation Classes (JFC) ist. Die Swing-Klassen, deren
Namen (fast) alle mit einem großen J beginnen, ersetzen die vorher benutzten AWT-Komponenten. Die
Swing-Klassen selbst basieren jedoch auf Teilen der grundlegenden AWT-Infrastruktur, weshalb auch in Zukunft AWT-Komponenten vorhanden sein werden. 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.
105
106
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
// hello1/HelloWorldSwing.java -import javax.swing.JFrame;
import javax.swing.JLabel;
public class HelloWorldSwing
{
public static void main(String[] args)
{
final 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);
}
}
Bemerkungen:
• Erzeugt ein top-level Fenster (JFrame) mit dem Titel HelloWorldSwing“.
”
• Erzeugt ein JLabel-Objekt mit dem Inhalt Hello World“ und fügt dieses Objekt in die ContentPane
”
des JFrame-Objekts ein.
• Beim Löschen des Fensters soll das Programm beendet werden.
• Zum Schluss werden die Elemente im Frame angeordnet und sichtbar gemacht.
Eine abgewandelte Version des Hello World“-Programms mit expliziter Ereignisverarbeitung zur Behand”
lung von WindowEvents.
// hello2/HelloWorldSwing.java -import
import
import
import
java.awt.event.WindowEvent;
java.awt.event.WindowAdapter;
javax.swing.JFrame;
javax.swing.JLabel;
9.1. EINFÜHRENDE BEISPIELE
107
public class HelloWorldSwing
{
public static void main(String[] args)
{
final JFrame frame = new JFrame("HelloWorldSwing");
final JLabel label = new JLabel("<html><i>Hello</i> " +
"<b>World</b>!</html>");
frame.getContentPane().add(label);
frame.pack();
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}
Bemerkungen:
• Explizite programmierte 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.
• Achtung: Labels können auch HTML enthalten!?!
9.1.2
Celsius Converter
Der CelsiusConverter ist ein einfaches Programm, das Werte in Grad Celsius entgegen nimmt und den
entsprechenden Wert in Grad Fahrenheit darstellt.
Abbildung 9.3: CelsiusConverter
// celsius1/CelsiusConverter.java -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;
108
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
public class CelsiusConverter
{
JFrame frame;
JPanel panel;
JLabel celLabel, fahLabel, resLabel;
JButton convButton, exitButton;
JTextField inputTextField;
public CelsiusConverter()
{
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();
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 (NumberFormatException e) {
resLabel.setText("illegal input");
return;
}
resLabel.setText((int) f + " F");
}
}
9.1. EINFÜHRENDE BEISPIELE
109
class ExitListener implements ActionListener
{
public void actionPerformed(ActionEvent event) {
System.exit(0);
}
}
public static void main(String[] args)
{
CelsiusConverter converter = new CelsiusConverter();
}
}
Bemerkungen:
• Ein JPanel kann andere Komponenten aufnehmen.
• Die Anordnung der Komponenten im JPanel wird durch einen sogenannten 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.
Tauscht man im CelsiusConverter die Klasse ConvertListener durch folgende Implementation
aus, so wird im Fehlerfall ein modaler Dialog angezeigt.
// celsius2/CelsiusConverter.java -import
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.JOptionPane;
javax.swing.SwingConstants;
public class CelsiusConverter
{
JFrame frame;
JPanel panel;
JLabel celLabel, fahLabel, resLabel;
JButton convButton, exitButton;
JTextField inputTextField;
public CelsiusConverter()
{
110
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();
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 (NumberFormatException e) {
JOptionPane.showMessageDialog(frame,
"illegal input \"" + inputTextField.getText() +"\"",
"Illegal Input", JOptionPane.ERROR_MESSAGE);
resLabel.setText("");
return;
}
resLabel.setText((int) f + " F");
}
}
class ExitListener implements ActionListener
{
public void actionPerformed(ActionEvent event) {
System.exit(0);
}
}
9.1. EINFÜHRENDE BEISPIELE
111
public static void main(String[] args)
{
CelsiusConverter converter = new CelsiusConverter();
}
}
Bemerkungen:
• 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.3
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
Abbildung 9.5: LookAndFeelMotif
// looknfeel/LookAndFeel.java
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 =
112
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
"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);
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();
9.1. EINFÜHRENDE BEISPIELE
113
}
catch (Exception exc) {
JRadioButton button = (JRadioButton) e.getSource();
button.setEnabled(false);
updateState();
System.err.println("LookAndFeel: " + exc.getMessage());
}
}
}
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);}
});
frame.getContentPane().add("Center", panel);
frame.pack();
frame.setVisible(true);
panel.updateState();
}
}
Bemerkungen:
• Die Klasse LookAndFeel ist eine Spezialisierung des JPanel.
• Neben einem JButton werden drei JRadioButton erzeugt, die in einer ButtonGroup zusammengefasst 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 Methode updateState() selektiert den JRadioButton, der dem aktuellen Look and Feel
entspricht.
114
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
• Die Methode main() erzeugt einen JFrame und eine LookAndFeel Instanz, die in die ContentPane
des JFrame eingetragen wird.
9.1.4
Root Panes
Abbildung 9.6: Swing Root Panes
Bemerkungen:
• Jede top-level Komponente stellt ein sogenanntes RootPane zur Verfügung, das selbst aus mehreren
Panes besteht.
• Das GlassPane ist normalerweise vollkommen transparent. Es dient lediglich dazu, Ereignisse für
das gesamte RootPane abzufangen.
• 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 platzieren.
9.1.5
Threads
Obwohl Java mehrere nebenläufige Threads unterstützt, gibt es in Swing deutliche Einschränkungen, da die
Swing-Klassen nicht selbst die erforderliche Synchronisation realisieren:
• 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.
• Andere Threads können mit den Methoden invokeLater() und invokeAndWait() der Klasse SwingUtilities den event dispatching thread“ zur Ausführung von Methoden veranlassen,
”
sobald alle übrigen Ereignisse abgearbeitet worden sind.
115
9.2. LAYOUT MANAGER
9.2 Layout Manager
Ein Layout Manager arrangiert Komponenten in einem Container. Die Schnittstelle eines Layout Managers
ist durch ein Interface festgelegt.
FlowLayout
«interface»
LayoutManager
+addLayoutComponent(name:String,comp:Component): void
+layoutContainer(parent:Container): void
+minimumLayoutSize(parent:Container): Dimension
+preferredLayoutSize(parent:Container): Dimension
+removeLayoutComponent(comp:Component): void
GridLayout
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
Bemerkungen:
• Manche Layout-Algorithmen laufen in zwei getrennten Phasen ab.
• Die geforderten 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.2.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 platziert werden können.
Abbildung 9.8: BorderLayoutDemo
// borderlayout/BorderLayoutDemo.java
import java.awt.BorderLayout;
116
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
import java.awt.Container;
import javax.swing.JFrame;
import 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);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String args[])
{
BorderLayoutDemo window = new BorderLayoutDemo();
window.setTitle("BorderLayoutDemo");
window.pack();
window.setVisible(true);
}
}
Bemerkungen:
• Beim verändern der Größe wird übrig bleibender Platz 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.2.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.
117
9.2. LAYOUT MANAGER
Abbildung 9.9: BoxLayoutDemo
// boxlayout1/BoxLayoutDemo.java
import
import
import
import
import
import
import
java.awt.Container;
java.awt.Component;
java.awt.Dimension;
javax.swing.Box;
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");
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);
}
}
118
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
Bemerkungen:
• Verfügbarer Platz, der nicht von den Komponenten benötigt wird, wird am Ende (in Layout-Richtung
gesehen) untergebracht.
• Mit Hilfe der Methoden setAlignmentX() und setAlignmentY() können Komponenten bestimmen, ob sie linksbündig, zentriert oder rechtsbündig dargestellt werden. 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 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:
// boxlayout2/BoxLayoutDemo.java
import
import
import
import
import
import
import
java.awt.Container;
java.awt.Component;
java.awt.Dimension;
javax.swing.Box;
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");
// 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);
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");
9.2. LAYOUT MANAGER
119
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);
}
}
Bemerkungen:
• 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.2.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)
// cardlayout/CardLayoutDemo.java
import java.awt.CardLayout;
import java.awt.BorderLayout;
120
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
import
import
import
import
import
import
import
import
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;
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();
121
9.2. LAYOUT MANAGER
window.setTitle("CardLayoutDemo");
window.pack();
window.setVisible(true);
}
}
Bemerkungen:
• 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.
Abbildung 9.12: TabbedPaneDemo
// tabbedpane/TabbedPaneDemo.java
import
import
import
import
import
import
import
import
import
java.awt.Container;
java.awt.BorderLayout;
java.awt.Dimension;
java.awt.Insets;
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();
122
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
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();
window.setSize(400, 120);
window.setVisible(true);
}
}
9.2.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
// flowlayout/FlowLayoutDemo.java
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[])
123
9.2. LAYOUT MANAGER
{
FlowLayoutDemo window = new FlowLayoutDemo();
window.setTitle("FlowLayoutDemo");
window.pack();
window.setVisible(true);
}
}
Bemerkungen:
• 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.2.5
Grid Layout
Die Klasse GridLayout implementiert einen Algorithmus, der Komponenten tabellenförmig darstellt. Die
einzelnen Zellen bekommen alle dieselbe Größe zugeordnet.
Abbildung 9.14: GridLayoutDemo
// gridlayout/GridLayoutDemo.java
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);
}
124
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
public static void main(String args[])
{
GridLayoutDemo window = new GridLayoutDemo();
window.setTitle("GridLayoutDemo");
window.pack();
window.setVisible(true);
}
}
Bemerkungen:
• Die ersten zwei Parameter definieren die Anzahl der Zeilen (rows) und Spalten (columns) und legen
das Format des Rasters (aber nicht deren Größe) fest.
• Die optionalen Parameter drei und vier definieren einen festen horizontalen bzw. vertikalen Abstand
zwischen den Zellen.
9.2.6
Grid Bag Layout
Die Klasse GridBagLayout implementiert den flexibelsten Layout Algorithmus. Er ordnet Komponenten
in einem tabellenförmigen Raster an, wobei die Spalten und Zeilen unterschiedliche Breite bzw. Höhe haben
können. Einzelne Komponenten können sich auch über mehrere Zellen ausdehnen.
Abbildung 9.15: GridBagLayoutDemo
// gridbaglayout/GridBagLayoutDemo.java
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);
9.2. LAYOUT MANAGER
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;
c.gridy = 1;
gridbag.setConstraints(button, c);
contentPane.add(button);
button = new JButton("Button 5");
c.ipady = 0;
//reset to default
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;
//aligned with button 2
c.gridwidth = 2;
//2 columns wide
c.gridy = 2;
//third row
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);
}
}
125
126
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
Bemerkungen:
• Für jede Komponente bestimmt ein GridBagConstraints Objekt die Layout-Parameter.
• 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, right.
• Ist die Komponente kleiner als der verfügbare Platz, dann bestimmt die anchor Instanzvariable, wo
die Komponente in verfügbaren Raum platziert wird. Zur Definition der Ausrichtung dienen die Konstanten NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST und NORTHWEST.
• 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.2.7
Absolute Positionierung
In einigen Spezialfällen ist es nicht sinnvoll, dass das Layout dynamisch erfolgt. In diesen Fällen kann man
die Komponenten absolut platzieren.
Abbildung 9.16: AbsoluteLayoutDemo
// absolutelayout/AbsoluteLayoutDemo.java
import
import
import
import
java.awt.Container;
java.awt.Insets;
javax.swing.JFrame;
javax.swing.JButton;
public class AbsoluteLayoutDemo extends JFrame
{
public AbsoluteLayoutDemo()
9.2. LAYOUT MANAGER
127
{
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);
}
}
Bemerkungen:
• Die Position wird mit der Methode setBounds direkt als Eigenschaft der Komponenten festgelegt.
9.2.8
Implementation eines Layout-Manager
Man kann relativ einfach neue Layout-Manager realisieren, indem man das LayoutManager oder das
erweiterte LayoutManager2 Interface implementiert. Im LayoutManager Interface sind die folgenden
Methoden festgelegt:
• Die Methode addLayoutComponent() wird von der add() Methode eines Containers aufgerufen, wenn eine Komponente einem Container hinzugefügt wird. Mit Hilfe dieser Methode können
Parameter an den Layout-Manager durchgereicht werden. Man beachte, dass der Container die enthaltenen Komponenten verwaltet und man unter Umständen auf eine eigene Verwaltung verzichten
kann.
• Die Methode removeLayoutComponent() wird von der Methode remove() oder der Methode
removeAll() des Containers aufgerufen.
• Die Methode preferredLayoutSize() bzw. minimumLayoutSize() wird aufgerufen, wenn
ein anderes Objekt oder ein anderer Layout-Manager die natürliche bzw. minimale Größe eines Containers erfahren möchte.
128
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
• Die Methode layoutContainer() wird jedesmal aufgerufen, wenn sich die Größe des Containers
verändert. In der Methode wird typischerweise die Position und Größe der im Container enthaltenen
Komponenten neu berechnet. Bei der Berechnung ist ein interner Rand des Containers (insets) zu
berücksichtigen.
Der folgende Quelltext implementiert einen Layout-Manager, der die einzelnen Komponenten diagonal von
der linken oberen Ecke zur rechten unteren Ecke anordnet.
// diagonallayout/DiagonalLayout.java
import
import
import
import
import
import
java.awt.LayoutManager;
java.awt.Container;
java.awt.Component;
java.awt.Dimension;
java.awt.Insets;
java.util.Vector;
public class DiagonalLayout implements LayoutManager
{
private int vgap;
private Dimension preferred = new Dimension();
private Dimension minimum = new Dimension();
private Dimension maximum = new Dimension();
public DiagonalLayout()
{
this(5);
}
public DiagonalLayout(int v)
{
vgap = v;
}
/**
* Required by the LayoutManager interface and ignored here since
* we have no need to keep a list of components ourself.
*/
public void addLayoutComponent(String name, Component comp) { }
/**
* Required by the LayoutManager interface and ignored here since
* we have no need to keep a list of components ourself.
*/
public void removeLayoutComponent(Component comp) { }
/**
* Compute the preferred and minimum width and height based on the
* parameters of the components and a layout where a subsequent
* component start horizontally in the middle of the previous
* component. Add a vgap between components.
*/
private void setSizes(Container parent)
{
9.2. LAYOUT MANAGER
129
int nComps = parent.getComponentCount();
Insets insets = parent.getInsets();
preferred.width = insets.left + insets.right;
preferred.height = insets.top + insets.bottom;
minimum.width = insets.left + insets.right;
minimum.height = insets.top + insets.bottom;
for (int i = 0; i < nComps; i++) {
Component c = parent.getComponent(i);
if (c.isVisible()) {
Dimension d = c.getPreferredSize();
preferred.width += (i == 0) ? d.width : d.width / 2;
preferred.height += (i == 0) ? d.height : d.height + vgap;
minimum.width
= Math.max(c.getMinimumSize().width,
minimum.width);
minimum.height = preferred.height;
}
}
maximum.width = parent.getWidth();
maximum.height = parent.getHeight();
}
/**
* Required by LayoutManager. Simply calculate the sizes and
* return the preferred size.
*/
public Dimension preferredLayoutSize(Container parent)
{
setSizes(parent);
return preferred;
}
/**
* Required by LayoutManager. Simply calculate the sizes and
* return the minimum size.
*/
public Dimension minimumLayoutSize(Container parent)
{
setSizes(parent);
return minimum;
}
/**
* Required by LayoutManager2. Simply calculate the sizes and
* return the maximum size.
*/
public Dimension maximumLayoutSize(Container parent)
{
setSizes(parent);
130
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
return maximum;
}
/**
* Required by LayoutManager. Arrange the components in diagonal
* if possible. Make sure components do not move outside of the
* container on the top or left.
*/
public void layoutContainer(Container parent)
{
int nComps = parent.getComponentCount();
Insets insets = parent.getInsets();
int x = insets.right, y = insets.top;
int xFudge = 0, yFudge = 0;
Dimension previous = new Dimension(0, 0);
setSizes(parent);
if (maximum.width != preferred.width) {
xFudge = (maximum.width - preferred.width)/(nComps - 1);
}
if (maximum.height != preferred.height) {
yFudge = (maximum.height - preferred.height)/(nComps - 1);
}
for (int i = 0 ; i < nComps ; i++) {
Component c = parent.getComponent(i);
if (c.isVisible()) {
Dimension d = c.getPreferredSize();
if (i > 0) {
x += Math.max(previous.width / 2 + xFudge, 0);
y += Math.max(previous.height + vgap + yFudge, 0);
}
c.setBounds(x, y, d.width, d.height);
previous = d;
}
}
}
public String toString()
{
return getClass().getName() + "[vgap=" + vgap + "]";
}
}
Bemerkungen:
• Die Implementierung verzichtet darauf, die enthaltenen Komponenten selbst zu verwalten. Stattdessen wird mit Hilfe von getComponentCount() und getComponent() auf die Verwaltung der
Komponenten im Container zurückgegriffen.
• Generell werden nur die Komponenten betrachtet, die auch aktuell sichtbar sind.
• Die Anpassung der Größe der Komponenten erfolgt mit Hilfe der Methode setBounds, die jede
Komponente implementiert.
Eine kleine Demonstration des neuen Layout-Managers:
9.2. LAYOUT MANAGER
// diagonallayout/DiagonalLayoutDemo.java
import
import
import
import
import
import
java.awt.Container;
java.awt.event.ActionEvent;
java.awt.event.ActionListener;
javax.swing.JFrame;
javax.swing.JButton;
DiagonalLayout;
public class DiagonalLayoutDemo extends JFrame
{
public DiagonalLayoutDemo()
{
Container contentPane = getContentPane();
contentPane.setLayout(new DiagonalLayout(0));
for (int i = 0; i < 5; i++) {
JButton b = new JButton("Button " + i);
b.addActionListener(new ButtonActionListener());
contentPane.add(b);
}
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
class ButtonActionListener implements ActionListener
{
public void actionPerformed(ActionEvent e) {
// JButton b = (JButton) e.getSource();
// b.setText("[" + b.getText() + "]");
}
}
public static void main(String args[])
{
DiagonalLayoutDemo window = new DiagonalLayoutDemo();
window.setTitle("DiagonalLayoutDemo");
window.pack();
window.setVisible(true);
}
}
131
132
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
9.3 Ränder
Oftmals ist es sinnvoll, einen Rand um eine Komponente zu zeichnen. Das Interface Border definiert die
Schnittstelle für einen Rand. Die abstrakte 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.3.1
Einfache Ränder
Einfache Ränder definieren einen Rahmen um eine Komponente, der unterschiedlich hervorgehoben oder
begrenzt sein kann.
Abbildung 9.18: BorderSimpleDemo
// bordersimple/BorderSimpleDemo.java
import java.awt.Container;
9.3. RÄNDER
import
import
import
import
import
import
import
import
import
import
133
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;
javax.swing.Box;
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)
{
JFrame window = new BorderSimpleDemo();
window.setTitle("BorderSimpleDemo");
window.pack();
window.setVisible(true);
134
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
}
}
Bemerkungen:
• 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.3.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
// bordertitle/BorderTitleDemo.java
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;
9.3. RÄNDER
135
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);
}
136
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
}
Bemerkungen:
• 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ässt sich die Ausrichtung und die Position festlegen.
9.3.3
Zusammengesetzte Ränder
Komplexe zusammengesetzte Ränder lassen sich aus einfacheren Ränder zusammensetzen.
Abbildung 9.20: BorderCompoundDemo
// bordercompound/BorderCompoundDemo.java
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");
9.3. RÄNDER
137
Border loweredbevel = BorderFactory.createLoweredBevelBorder();
addLabel(loweredbevel, "lowered bevel border");
Border compound
= BorderFactory.createCompoundBorder(raisedbevel, loweredbevel);
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);
}
}
Bemerkungen:
• 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.
138
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
9.4 Menüs
Menüs sind ein platzsparender Mechanismus, mit dem ein Benutzer eine von vielen möglichen Funktionen
auswählen kann.
Abbildung 9.21: MenuDemo
// menu/MenuDemo.java
import
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;
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");
9.4. MENÜS
139
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();
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);
140
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
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");
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);
}
}
Bemerkungen:
• 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.
141
9.4. MENÜS
9.4.1
Toolbars
Häufig gebrauchte Menüpunkte können in sogenannten Toolbars untergebracht werden.
Abbildung 9.22: ToolBarDemo
// toolbar/ToolBarDemo.java
import
import
import
import
import
import
import
import
import
import
import
java.awt.Dimension;
java.awt.BorderLayout;
java.awt.event.ActionEvent;
java.awt.event.ActionListener;
javax.swing.JToolBar;
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() {
142
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
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");
}
});
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);
}
}
Bemerkungen:
• 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, muss 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.
143
9.4. MENÜS
9.4.2
Actions
Wenn mehrere JButtons bzw. JMenuItems dieselbe Aktion ausführen, dann ist die Implementation einer
gemeinsamen Aktion (actions) sinnvoll, um die Konsistenz der Oberfläche zu gewährleisten und redundanten
Quelltext zu vermeiden.
<<interface>>
EventListener
<<interface>>
ActionListener
<<interface>>
Action
CutAction
Object
AbstractAction
TextAction
CopyAction
PasteAction
Abbildung 9.23: Einige Swing-Klassen für Aktionen
Bemerkungen:
• Ein Objekt, das das Action Interface implementiert, kapselt den Namen und das Icon im Menü bzw.
Toolbarbutton sowie den Zustand der Action.
• Änderungen des Zustands der Action werden automatisch an die eingetragenen Menü bzw. Toolbarbutton propagiert.
• Es gibt spezielle add() Methoden für JToolBar und JMenu Objekte, die eine Action entgegennehmen und einen passenden JButton bzw. JMenuItem erzeugen.
// action/ActionDemo.java
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;
144
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
import
import
import
import
javax.swing.JFrame;
javax.swing.JTextArea;
javax.swing.JScrollPane;
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("");
9.4. MENÜS
button.setToolTipText("This is the left button");
menuItem = mainMenu.add(leftAction);
menuItem.setIcon(null);
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);
145
146
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
cbmi = new JCheckBoxMenuItem("Right action enabled");
cbmi.setSelected(true);
cbmi.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
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);
}
}
147
9.5. SCROLLING
9.5 Scrolling
Mit Hilfe der Klasse JScrollPane und des Interfaces Scrollable lassen sich verschiebbare Ausschnitte einer Komponente darstellen. Der Ausschnitt wird durch ein Objekt der Klasse JViewport dargestellt.
Abbildung 9.24: Swing JScrollPane Funktionsmodell
Bemerkungen:
• Eine JScrollPane nimmt eine Komponente auf, die das Interface Scrollable implementieren
muss (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.
• 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.25: ScrollDemo
148
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
// scroll/ScrollDemo.java
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)
{
ScrollDemo window = new ScrollDemo();
window.setTitle("ScrollDemo");
window.pack();
window.setVisible(true);
}
}
149
9.6. SPLITTING
9.6 Splitting
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.26: SplitDemo
// split/SplitDemo.java
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,
splitPane, label);
getContentPane().add(topSplitPane);
150
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
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, dass die kleinen Knöpfe zum
Öffnen/Schließen der Teile sichtbar sind. Wenn man diesen Parameter nicht explizit setzt, dann 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, dass 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 entsprechenden View realisiert.
151
9.7. TEXT KOMPONENTEN
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 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.27: 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.
152
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
9.7.2
Ereignisse von Dokumenten
Abbildung 9.28: DocumentEventDemo
// documentevent/DocumentEventDemo.java
import
import
import
import
import
import
import
import
import
import
import
import
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);
9.7. TEXT KOMPONENTEN
153
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");
}
}
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.
154
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
Abbildung 9.29: EditorPaneDemo
// editorpane/EditorPaneDemo.java
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.ActionListener;
javax.swing.JFrame;
javax.swing.JTextField;
javax.swing.JPanel;
javax.swing.JEditorPane;
javax.swing.JScrollPane;
javax.swing.JOptionPane;
java.net.URL;
java.net.MalformedURLException;
java.io.IOException;
public class EditorPaneDemo extends JFrame
{
private JEditorPane editorPane;
private JTextField textField;
public EditorPaneDemo(String start)
{
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));
9.7. TEXT KOMPONENTEN
155
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)
{
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)
{
final String start = "http://www.vorlesungen.uni-osnabrueck.de/informatik/c02/";
if (args.length == 0) {
JFrame window = new EditorPaneDemo(start);
window.setTitle("EditorPaneDemo");
window.pack();
window.setVisible(true);
}
for (int i = 0; i < args.length; i ++) {
JFrame window = new EditorPaneDemo(args[i]);
window.setTitle("EditorPaneDemo");
window.pack();
156
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
window.setVisible(true);
}
}
}
Bemerkungen:
• Das Programm nutzt die Möglichkeit, einer Instanz der Klasse JEditorPane mit Hilfe der setPage
Methode einen URL zu geben. Das JEditorPane holt sich darauf das HTML-Dokument 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.30: TextComponentDemo
// textcomponent/TextComponentDemo.java
import
import
import
import
import
import
import
import
import
import
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;
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;
9.7. TEXT KOMPONENTEN
import
import
import
import
import
import
import
import
import
import
import
import
import
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()
{
doc = new DefaultStyledDocument();
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);
addKeymapBindings();
initDocument();
doc.addUndoableEditListener(new UndoableListener());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
protected class UndoableListener implements UndoableEditListener
157
158
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
{
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));
menu.addSeparator();
menu.add(getActionByName(DefaultEditorKit.selectAllAction));
return menu;
}
9.7. TEXT KOMPONENTEN
159
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,
attrs[i]);
}
} catch (BadLocationException ble) {
System.err.println("Couldn’t insert initial text.");
160
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
}
}
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)
{
9.7. TEXT KOMPONENTEN
161
try {
undo.undo();
} 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");
}
}
}
162
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
public static void main(String[] args)
{
TextComponentDemo window = new TextComponentDemo();
window.setTitle("TextComponentDemo");
window.pack();
window.setVisible(true);
}
}
Bemerkungen:
• 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.
163
9.8. DATEIDIALOGE
9.8 Dateidialoge
Die Klasse JFileChooser realisiert Dialoge, mit denen ein Programm einen Benutzer nach Dateinamen
fragen kann.
Abbildung 9.31: FileChooserDemo
// filechooser/FileChooserDemo.java
import
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;
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);
164
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
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);
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();
9.8. DATEIDIALOGE
}
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();
window.setTitle("FileChooserDemo");
window.pack();
window.setVisible(true);
}
165
166
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
}
Bemerkungen:
• 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.
167
9.9. BÄUME
9.9 Bäume
Die Klasse JTree stellt die Funktionalität bereit, Baumstrukturen darzustellen. Ein Modell für die Klasse JTree muss das Interface TreeModel implementieren. Eine Standardimplementation wird durch die
Klasse DefaultTreeModel zur Verfügung gestellt.
Abbildung 9.32: TreeDemo
// tree/TreeDemo.java
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;
private JTextArea text;
public TreeDemo()
{
DefaultMutableTreeNode treeModel = createTreeModel();
tree = new JTree(treeModel);
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);
168
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
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");
}
}
public class Entity
{
private String descr;
private String type;
private int index;
public Entity(int entIndex, String entType, String entDescr)
{
descr = entDescr;
type = entType;
index = entIndex;
}
public String toString()
{
return descr;
}
}
private DefaultMutableTreeNode createTreeModel()
{
DefaultMutableTreeNode top = null, lev1 = null,
lev2 = null, lev2_1 = null, lev2_1_1 = null,
169
9.9. BÄUME
lev3 = null, lev3_1 = null, lev3_1_1 = null, lev3_1_2 = null;
top = new DefaultMutableTreeNode(new Entity
(1, "chassis", "7206VXR chassis Hw Serial#: 12345"));
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);
return top;
}
public static void main(String[] args)
{
JFrame window = new TreeDemo();
window.setTitle("TreeDemo");
window.pack();
window.setVisible(true);
}
}
170
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 TableModel Interface stellt die
Klasse AbstractTableModel bereit.
Abbildung 9.33: SimpleTableDemo
// simpletable/SimpleTableDemo.java
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);
}
171
9.10. TABELLEN
public static void main(String[] args)
{
SimpleTableDemo window = new SimpleTableDemo();
window.setTitle("SimpleTableDemo");
window.pack();
window.setVisible(true);
}
}
Bemerkungen:
• 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.34: TableDemo
// table/TableDemo.java
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));
172
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
JScrollPane scrollPane = new JScrollPane(table);
getContentPane().add(scrollPane, BorderLayout.CENTER);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
class SportsTableModel extends AbstractTableModel
{
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);
}
9.10. TABELLEN
173
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;
}
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);
}
174
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
}
Bemerkungen:
• 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 Nummerierung 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.
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.35: 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.
9.11. ZWEIDIMENSIONALE GRAPHIK
175
• 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 Umriss 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 die Darstellung, wenn sich Objekte überlappen.
• transform: Bestimmt die Abbildung der Koordinaten auf das Koordinatensystem des Darstellungsgeräts und ermöglicht die Translation, Rotation, Skalierung.
• 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 Methoden repaint() und update(),
die in der Regel automatisch aufgerufen wird, wenn ein graphisches Objekt dargestellt werden muss oder
wenn sich der Zustand eines graphischen Objekts verändert hat. Ein repaint() ist rekursiv und führt
automatisch dazu, dass alle enthaltenen Objekte ebenfalls ein repaint() ausführen.
Die eigentliche graphische Darstellung eines graphischen Objekts erfolgt in der öffentlichen paint() Methode, die ihrerseits wiederum die geschützten Methoden paintComponent(), paintBorder() und
paintChildren() in dieser Reihenfolge aufruft. Ein Aufruf von update() bewirkt ebenfalls einen
Aufruf von paint().
Abbildung 9.36: SwingPaintDemo
// swingpaint/SwingPaintDemo.java
import
import
import
import
import
java.awt.Color;
java.awt.Graphics;
java.awt.Dimension;
javax.swing.JFrame;
javax.swing.JPanel;
class BullsEyePanel extends JPanel
176
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
{
public Dimension getPreferredSize()
{
// Figure out what the layout manager needs, take the
// maximum of (width, height, 100) and request a
// quadratic size in order to produce a ’round’ bullseye.
Dimension layoutSize = super.getPreferredSize();
int max = Math.max(Math.max(layoutSize.width, layoutSize.height), 100);
return new Dimension(max, max);
}
protected void paintComponent(Graphics g)
{
Dimension size = getSize();
int x = 0;
int y = 0;
int i = 0;
g.clearRect(0, 0, size.width, size.height);
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++;
}
}
}
class SwingPaintDemo
{
public static void main(String[] args)
{
JFrame f = new JFrame("SwingPaintDemo");
f.setContentPane(new BullsEyePanel());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
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.
177
9.11. ZWEIDIMENSIONALE GRAPHIK
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.37: Java 2D Shapes
Das folgende Programm demonstriert wie diese Shapes dargestellt werden können und wie die Darstellungsweise beeinflusst werden kann.
Abbildung 9.38: ShapesDemo
// shapes/ShapesDemo.java
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
178
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
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,
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();
9.11. ZWEIDIMENSIONALE GRAPHIK
179
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;
g2.clearRect(0, 0, d.width, d.height);
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);
int
int
int
int
int
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;
180
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
// 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;
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;
9.11. ZWEIDIMENSIONALE GRAPHIK
181
// 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};
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");
182
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.40: UML Diagramm für die Animation springender Bälle
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new ShapesDemo());
f.pack();
f.setSize(new Dimension(550,100));
f.setVisible(true);
}
}
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.39: 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.
9.11. ZWEIDIMENSIONALE GRAPHIK
183
2. Die Klasse BallsThread realisiert einen Thread, der periodisch die Darstellung der Bälle anstößt
und in der paint-Methode darstellt.
3. Die Klasse Ball repräsentiert einen Ball. Zu jedem Ball existieren mehrere Darstellungen in leicht
verschiedenen Farbverläufen.
// balls/BallsDemo.java
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;
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(); }
184
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
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));
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) {
9.11. ZWEIDIMENSIONALE GRAPHIK
185
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);
}
}
private void makeSizeMenu(JMenuBar menuBar)
{
JRadioButtonMenuItem item;
186
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
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);
9.11. ZWEIDIMENSIONALE GRAPHIK
187
}
}
/**
* 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);
}
}
188
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
lasttime = now;
g2.dispose();
g.drawImage(bimg, 0, 0, this);
}
/**
* 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[];
9.11. ZWEIDIMENSIONALE GRAPHIK
189
public int index = (int) (Math.random() * (nImgs-1));
static
static
static
static
static
private
private
private
private
private
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;
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) {
190
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
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;
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)
191
9.11. ZWEIDIMENSIONALE GRAPHIK
{
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) {
y = 0;
Vy = -Vy * inelasticity +
}
if (y + diameter >= h) {
y = h - diameter;
Vx *= inelasticity;
Vy = -Vy * inelasticity +
}
Vy = Vy + Ay * deltaT;
Vx = Vx + Ax * deltaT;
* deltaT * deltaT;
* deltaT * deltaT;
jitter;
jitter;
jitter;
jitter;
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();
}
}
192
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
Bemerkungen:
• 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.3
Beispiel: Santa Claus
Zum Abschluss noch ein kleiner Gruß von Santa Claus zur kommenden Weihnachtspause...
Abbildung 9.41: SantaClaus
// santaclaus/SantaClaus.java
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
{
private SantaClausThread scThread;
public SantaClaus()
{
JFrame frame = new JFrame();
scThread = new SantaClausThread();
frame.getContentPane().add(scThread);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setTitle("Santa Claus is coming soon...");
frame.pack();
frame.setSize(new Dimension(400,300));
frame.setVisible(true);
9.11. ZWEIDIMENSIONALE GRAPHIK
193
start();
}
public void start()
{
scThread.start();
}
public void stop()
{
scThread.stop();
}
private class SantaClausThread extends JPanel implements Runnable
{
private final int num = 4;
private ImageIcon imgs[];
private Thread thread;
private int index = 0;
private int x = 42;
private int y = 0;
private int dx = 0;
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);
y += 5;
index = (index + 1) % num;
194
KAPITEL 9. JAVA FOUNDATION CLASSES (SWING)
} 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 {
thread.sleep(80);
} catch (InterruptedException e) { break; }
}
thread = null;
}
}
public static void main(String argv[])
{
final SantaClaus sc = new SantaClaus();
}
}
Teil III
Software-Komponenten und Verteilte
Anwendungen
195
Kapitel 10
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 (LegoPrinzip). 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
sich eine Bean 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.
10.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 für die Properties einer Bean müssen gewissen Namenskonventionen genügen
(get<Property>, set<Property>, is<Property>).
• 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.
197
198
KAPITEL 10. JAVA BEANS
• 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ützt. Ein Customizer implementiert das Interface Customizer.
Das folgende Beispiel ist eine Bean mit einfachen Properties und indizierten Properties, wobei die Properties
verschiedenen Datentypen besitzen.
// beans/SimpleBean.java
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)
{
points[index] = value;
}
public void setPoints(float[] values)
{
points = values;
}
199
10.1. PROPERTIES
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 benachrichtigt werden wollen, müssen entsprechend
das Interface PropertyChangeListener implementieren. Bei der Verwaltung der registrierten Listener
hilft die Klasse PropertyChangeSupport.
«interface»
EventListener
«interface»
PropertyChangeListener
+propertyChange(e:PropertyChangeEvent): void
PropertyChangeSupport
+addPropertyChangeListener(pcl:PropertyChangeListener): void
+removePropertyChangeListener(pcl:PropertyChangeListener): void
Object
EventObject
PropertyChangeEvent
Abbildung 10.1: Java Klassen und Interfaces für gebundene Properties
// beans/BoundBean.java
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);
}
public void addPropertyChangeListener(PropertyChangeListener pcl)
200
KAPITEL 10. JAVA BEANS
{
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;
}
}
Bemerkungen:
• Die Erzeugung eines PropertyChangeEvents muss 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.
201
10.1. PROPERTIES
:Sender
setNumber
:BoundBean
addPropertyChangeListener
propertyChange
:Listener
Abbildung 10.2: Sequenzdiagramm für Bound Properties
// beans/BoundBeanListener.java
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());
}
}
}
202
KAPITEL 10. JAVA BEANS
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.
// beans/VetoableBean.java
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);
}
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,
203
10.1. PROPERTIES
new Integer(oldValue),
new Integer(newValue));
}
}
public int getPrice()
{
return price;
}
}
Bemerkungen:
• 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.
// beans/VetoableChangeListener.java
import
import
import
import
java.beans.PropertyChangeEvent;
java.beans.PropertyChangeListener;
java.beans.VetoableChangeListener;
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) {
204
KAPITEL 10. JAVA BEANS
System.out.println("New price: " + e.getNewValue());
}
}
}
Der typische Ablauf ist in folgendem Sequenzdiagramm dargestellt.
:Sender
setNumber
:VetoableBean
addVetoableChangeListener
addPropertyChangeListener
vetoableChange
propertyChange
:Listener
Abbildung 10.3: Sequenzdiagramm für Vetoable Properties
10.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.
• Wird statt eines Descriptors der Wert null zurückgeliefert, so wird sich ein Werkzeug Informationen
über die Bean mit Hilfe des Reflection-Mechanismums besorgen.
205
10.2. EXPLIZITE BESCHREIBUNG EINER BEAN
«interface»
BeanInfo
+getIcon(iconKind:int): Image
+getBeanDescriptor(): BeanDescriptor
+getPropertyDescriptor(): PropertyDescriptor[]
+getMethodDescriptor(): MethodDescriptor[]
+getEventSetDescriptor(): EventSetDescriptor[]
SimpleBeanInfo
BeanDescriptor
Object
PropertyDescriptor
FeatureDescriptor
EventSetDescriptor
MethodDescriptor
ParameterDescriptor
Abbildung 10.4: Interface BeanInfo und unterstützende Klassen
// beans/VetoableBeanBeanInfo.java
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",
"removePropertyChangeListener");
206
KAPITEL 10. JAVA BEANS
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;
}
}
10.3
Verpacken einer Bean
Beans werden typischerweise in einem Java Archiv (.jar) verpackt in dem auch zusätzliche Hilfsklassen
(z.B. BeanInfo) enthalten 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, muss 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üsselWert-Paar. Der Schlüssel wird durch einen Doppelpunkt und folgende Leerzeichen vom Wert getrennt. Zusammengehörende Schlüssel-Wert-Paare stehen in aufeinanderfolgenden 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
Name: VetoableBean
10.4. BEISPIEL
207
Java-Bean: True
Name: VetoableBeanBeanInfo
Depends-On: price16.gif
Depends-On: price32.gif
Design-Time-Only: True
Bemerkungen:
• 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.
10.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:
// beans/TimerListener.java
public interface TimerListener extends java.util.EventListener
{
public void onTime(java.awt.event.ActionEvent event);
}
Die Implementation der Timer Bean:
// beans/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;
transient private Vector listeners;
private PropertyChangeSupport propertySupport;
private boolean running;
private long delay;
208
KAPITEL 10. JAVA BEANS
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);
}
10.4. BEISPIEL
public void removeTimerListener(TimerListener l)
{
if (listeners == null) return;
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();
}
}
}
}
Zur Vollständigkeit nun noch die Implementation der Klasse TimerBeanInfo:
// beans/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 {
209
210
KAPITEL 10. JAVA BEANS
try {
PropertyDescriptor delay
= new PropertyDescriptor("Delay", Timer.class,
"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;
}
}
Kapitel 11
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.
11.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.
11.1.1
URIs, URLs und URNs
Globale Namensräume zur Adressierung 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.
11.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.
11.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:
211
212
KAPITEL 11. SERVLETS
• 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
Jeder HTTP-Methodenaufruf enthält eine Menge von Headern, die zusätzliche Informationen über den Aufruf beinhalten können oder Informationen über die Benutzer bzw. die verwendetet Software enthalten.
11.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).
11.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.
// servlets/HelloWorldServlet.java
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";
11.2. GENERISCHE SERVLETS
213
static final String DTD = "http://www.w3.org/TR/xhtml1/xhtml1-strict.dtd";
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
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>");
}
}
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 den Aufruf weiterer Hilfsmethoden 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.
// servlet/ParameterServlet.java
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;
public class ParameterServlet extends GenericServlet
{
public void service(ServletRequest req, ServletResponse res)
214
KAPITEL 11. 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());
// ... 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();
}
}
}
11.3
HTTP Servlets
Da HTTP eine so große Bedeutung hat, gibt es eine entsprechende Spezialisierung der Anfrage- und Antwortschnittstellen und eine Klasse HttpServlet, die Methoden für die einzelnen HTTP-Methoden bereitstellt.
// servlet/HeaderServlet.java
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)
throws ServletException, IOException
{
res.setContentType("text/plain");
215
11.3. HTTP SERVLETS
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);
}
}
}
Die wichtigsten Klassen und Interfaces sind in folgendem UML Klassendiagramm dargestellt:
«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 11.1: Java Servlet Klassen und Interfaces
Bemerkungen:
216
KAPITEL 11. 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.
11.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 wird die Methode destroy() von der Ausführungsumgebung aufgerufen.
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.
11.5 Sessions
In der Regel ist es notwendig, dass sich Servlets zwischen einzelnen Anfragen Zustandsinformationen merken können. Mit Hilfe des HttpSession Interfaces lässt sich das relativ einfach implementieren.
// servler/SessionServlet.java
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);
Integer counter = (Integer) session.getAttribute(COUNTER);
if (counter == null) {
counter = new Integer(0);
11.6. JAVASERVER PAGES (JSP)
217
}
counter = new Integer(counter.intValue() + 1);
session.setAttribute(COUNTER, counter);
res.setContentType("text/plain");
PrintWriter out = res.getWriter();
out.println("Page Counter: " + counter);
}
}
Bemerkungen:
• 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. Gebundene Objekte werden über Änderungen im Session-Objekt mit dem
Ereignis HttpSessionBindingEvent benachrichtigt.
11.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ässt sich der Unterschied an einem kleinen Beispiel demonstrieren:
// servlets/HelloWorldServlet.java
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
{
res.setContentType("text/html");
PrintWriter out = res.getWriter();
218
KAPITEL 11. SERVLETS
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ässt 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">
<%@ page session="false" %>
<html lang="en">
<head>
<title>HelloWorldJsp</title>
</head>
<body>
<p>Hello World!</p>
<hr/>
<p>Generated: <%= new java.util.Date() %></p>
</body>
</html>
Bemerkungen:
• Offensichtlich handelt es sich bei JSPs um HTML-Dokumente, wobei in speziellen Elementen JavaQuelltext 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 JSPImplementationen die neue XML-konforme Syntax.
11.6. JAVASERVER PAGES (JSP)
11.6.1
219
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.
11.6.2
Ausdrücke
Ein Ausdruck ist eine Java Ausdruck, der in eine Zeichenkette umgewandelt wird und anstelle des JSPAusdrucks in das HTML Dokument eingefügt wird.
Originale JSP-Notation:
<%= _expression_ %>
XML-Notation:
<jsp:expression>
_expression_
</jsp:expression>
11.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.
220
KAPITEL 11. SERVLETS
11.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.
11.6.5
Weiterleitungen
Eine JSP-Datei kann die Bearbeitung der Anfrage an andere JSP-Dateien oder statische HTML-Dokumente
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.
11.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.
• 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.
11.7. JAVASERVER PAGES UND BEANS
221
• info=" text "
Ein erläuternder statischer Text wird im erzeugten Servlet abgelegt. Auf den Text kann mit Hilfe der
Methode Servlet.getServletInfo() zugegriffen 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.
11.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 JSPKonstrukte, die insbesondere die Verarbeitung von Daten aus Web-Formularen vereinfachen helfen.
11.7.1 Bean-Benutzung
Durch die Benutzung von Beans lässt sich der Umfang von Java-Quelltext in HTML-Dokumenten deutlich
reduzieren. Außerdem lässt sich die Übergabe von Parametern aus HTML-Formularen 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_" />
Bemerkungen:
• 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.
– 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 muss einen public Konstruktor ohne
Argumente bereitstellen.
222
KAPITEL 11. SERVLETS
11.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="*" />
Bemerkungen:
• 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 Zeichenketten
automatisch in passende Datentypen konvertiert.
Achtung: Die Zuweisung der Properties in der dritten Form von setProperty erfolgt in einer nicht weiter
spezifizierten Reihenfolge.
11.8
Beispiel: Raten von Nummern
Der folgende Code implementiert eine einfache Bean zum Raten von Zahlen.
// servlets GuessNumberBean.java
public class GuessNumberBean implements java.io.Serializable
{
private int number;
// number to guess
private int guess;
// current guess
private int count;
// number of guesses so far
private String hint;
// hint for next guess
private boolean correct;
// correct guess?
public GuessNumberBean()
{
reset();
}
public void reset()
{
number = Math.abs(new java.util.Random().nextInt() % 100) + 1;
correct = false;
guess = -1;
count = 0;
hint = "";
}
public void setGuess(String value)
{
11.8. BEISPIEL: RATEN VON NUMMERN
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;
}
}
Die Integration in das Web erfolgt durch eine JSP:
<!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
223
224
KAPITEL 11. SERVLETS
<%= gnb.getCount() %> tries.
</p>
<p>
<% gnb.reset(); %>
<a href="GuessNumber.jsp">Try again</a>?
</p>
<% } 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>
11.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 Web-Servern
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.
• Tomcat unterstützt auch die Standards zur verteilten Erstellung und Pflege von Web-Dokumenten
(WebDAV).
Kapitel 12
Extensible Markup Language (XML)
Die Extensible Markup Language (XML) [4] ist eine Sprache zur Beschreibung von strukturierten Dokumenten. XML ist aus der Standard Generalized Markup Language (SGML) [13] hervorgegangen, die ursprünglich
von IBM entwickelt und später von der ISO standardisiert wurde.
Obwohl XML ursprünglich als Mechanismus zur Beschreibung von strukturierten Dokumenten entwickelt
wurde, findet XML zur Zeit auch generell zur Darstellung von strukturierten Daten insbesondere zum Austausch zwischen Applikationen Verwendung.
12.1
Struktur von XML Dokumenten
Jedes XML-Dokument besitzt eine Baumstruktur und besteht im wesentlichen aus den folgenden Strukturelementen:
• Ein ElementNode ist ein Strukturelement mit einem Namen, mit dem die Struktur des Baums aufgespannt wird. Die syntaktische Darstellung eines ElementNode mit dem Namen person geschieht
durch zwei Tags.
<person></person>
Zwischen den Tags können weitere Strukturelemente vorhanden sein.
<person>
<name></name>
</person>
Enthält ein ElementNode keine anderen Strukturelemente, dann kann die syntaktische Darstellung
verkürzt werden:
<person/>
• Ein TextNode ist ein Strukturelement, das Text enthält. Texte können in unterschiedlichen Zeichensätzen
abgelegt werden. Jede Implementation muß UTF8 und UTF16 unterstützen. Das folgende Beispiel
zeigt einen ElementNode, der einen TextNode enthält.
<email>[email protected]</email>
• Ein CommentNode ist ein Strukturelement, das einen Kommentar enthält. Die syntaktische Darstellung erfolgt nach folgendem Muster:
<!-- This is a comment. -->
225
226
KAPITEL 12. EXTENSIBLE MARKUP LANGUAGE (XML)
Aufgrund der syntaktischen Struktur ist klar, dass Kommentare nicht geschachtelt werden können.
• Ein EntityNode ist ein Strukturelment, das durch eine Ersetzung in einen TextNode verwandelt werden
kann. Die EntityNodes sind insbesondere zur Darstellung der XML Meta-Zeichen <, >, ’, " und &
notwendig:
< > ' &quote; &
• Ein ProcessingInstructionNode enthält Verarbeitungshinweise, die nicht wirklich Teil der in einem
Dokument enthaltenen Daten sind. Die syntaktische Darstellung erfolgt folgendermassen:
<?rfc toc?>
• Ein AttributeNode wird benutzt, um Attribute des umschliessenden Elements zu definieren. Ein AttributeNode spannt ebenfalls einen Teilbaum auf und besitzt analog zu einem ElementNode einen
Namen. Der Wert eines AttributeNodes befindet sich wiederum in einem untergeordneten TextNode.
Das folgende Beispiel definiert einen ElementNode mit dem Namen email, einem AttributeNode mit
dem Namen category und einen zugheörigen TextNode mit den Inhalt work.
<email category="work">[email protected]</email>
• Es gibt noch weitere Strukturelemente, die aber hier von geringerer Bedeutung sind.
Hier ist ein typisches Beispiel für ein XML-Dokument, dass die wichtigsten Informationen über die Mitarbeiter einer imaginären Organisation beschreibt.
<?xml version="1.0"?>
<!DOCTYPE staff SYSTEM "staff.dtd">
<staff>
<person>
<name>
<first>Peter</first>
<last>Mustermann</last>
</name>
<email>[email protected]</email>
<email category="private">[email protected]</email>
<phone category="work">+49 541 969 4242</phone>
<phone category="private">+49 541 123 4242</phone>
</person>
</staff>
Die interne Struktur dieses XML-Dokuments kann man sich z.B. mit Hilfe von xmllint anzeigen lassen.
Man beachte, dass die Leerzeichen zwischen den XML-Strukturelementen alle durch eigene TextNodes dargestellt werden, was auch den hohen Speicherbedarf zur internen Darstellung von XML-Dokumenten erklärt.
DOCUMENT
version=1.0
URL=/home/schoenw/xml/staff.xml
standalone=true
DTD(staff), SYSTEM staff.dtd
ELEMENT staff
TEXT content=
ELEMENT person
TEXT content=
12.2. DOCUMENT TYPE DEFINITIONS (DTD)
227
ELEMENT name
TEXT content=
ELEMENT first
TEXT content=Peter
TEXT content=
ELEMENT last
TEXT content=Mustermann
TEXT content=
TEXT content=
ELEMENT email
TEXT [email protected]
TEXT content=
ELEMENT email
ATTRIBUTE category
TEXT content=private
TEXT [email protected]
TEXT content=
ELEMENT phone
ATTRIBUTE category
TEXT content=work
TEXT content=+49 541 969 4242
TEXT content=
ELEMENT phone
ATTRIBUTE category
TEXT content=private
TEXT content=+49 541 123 4242
TEXT content=
TEXT content=
12.2
Document Type Definitions (DTD)
Die genaue Struktur eines XML-Dokuments kann man mit einer Document Type Definition (DTD) festlegen.
Formal definiert eine DTD eine Grammatik, die die Struktur aller XML-Dokumente beschreibt, die gültige
Worte der durch die in der DTD definierten Grammatik erzeugten Sprache sind.
Das Konzept der DTDs und die Syntax zur Definition von DTDs stammt vom XML-Vorläufer SGML. Das
folgende Beispiel definiert die Grammatik für das Beispiel XML-Dokument:
<!-- DTD for the staff.xml file -->
<!ENTITY % CTEXT "#PCDATA">
<!ELEMENT
<!ELEMENT
<!ELEMENT
<!ELEMENT
<!ELEMENT
<!ELEMENT
<!ELEMENT
<!ELEMENT
<!ELEMENT
staff (person*)>
person (name, email+, phone+)>
name (title?, first, middle?, last)>
title (%CTEXT;)>
first (%CTEXT;)>
middle (%CTEXT;)>
last (%CTEXT;)>
email (%CTEXT;)>
phone (%CTEXT;)>
<!ATTLIST email
228
KAPITEL 12. EXTENSIBLE MARKUP LANGUAGE (XML)
category (work|private|other) "work">
<!ATTLIST phone
category (work|private|other) "work">
Bemerkungen:
• Die Definition des Elements staff besagt, dass in diesem Element 0 oder mehrere Elemente person
enthalten sein können.
• Die Definition des Elements person besagt, dass in diesem Element genau ein Element name vorhanden sein muss, gefolgt von ein oder mehreren email-Elementen, denen wiederum ein oder mehrere phone-Elemente folgen.
• Die Definition des Element name legt fest, dass in diesem Element zunächst optional ein titleElement steht, gefolgt von genau einem first-Element, einem optionalen middle-Element und
genau einem last-Element.
• Die Elemente title, first, middle, last, email und phone beinhalten jeweils einen CTEXT.
• Der CTEXT ist wiederum als ein Entity definiert und wird durch den Wert #PCDATA (parsed character
data) substitutiert.
• Für die Elemente email und phone werden zum Schluss noch die Attribute festgelegt. Beide Elemente haben ein Attribut category, das jeweils einen der Werte work, private und other
annehmen kann. Ist das Attribut nicht vorhanden, dann wird der Wert work Standardwert angenommen.
XML-Dokumente, die mit der entsprechenden DTD konsistent sind, werden bezüglich der DTD als gültiges (valid) XML-Dokument bezeichnet. Dokumente, die lediglich der syntaktischen Struktur eines XMLDokuments entsprechen, werden all wohlgeformte (well-formed) XML-Dokumente bezeichnet. Wohlgeformtheit ist also eine sehr schwache minimale Forderung die ohne Kenntnis der DTD überprüfbar ist.
12.3 XML Namensräume
12.4 XML Schema (XSD)
Obwohl mit DTDs die Struktur eines XML-Dokuments beschrieben werden kann, hat dieser Ansatz einige
Schwächen:
• Die Ausdrucksfähigkeit der DTDs ist relativ begrenzt. Insbesondere kann nicht formal beschrieben
werden, welche Form die Daten in den Text-Elementen haben sollen. Es ist also durchaus möglich in
das email-Element eine Telefonnummer einzutragen, ohne dadurch die Gültigkeit der Dokuments
verändert wird.
• Die Wiederwendung von Definitionen in DTDs ist eingeschränkt.
• Die Verarbeitung einer DTD benötigt spezielle Parser, da die DTDs nicht selbst in XML definiert sind.
Entsprechend wurde eine auf XML basierende Sprache zur Beschreibung von XML-Dokumenten entwickelt.
Diese Sprache hat den Namen XML-Schema bekommen [].
<?xml version="1.0"?>
<!-- xml/staff.xsd -->
12.4. XML SCHEMA (XSD)
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:per="http://www.inf.uos.de/schoenw/person"
xml:lang="en">
<xsd:annotation>
<xsd:documentation>
This schema defines the formal syntax of the staff
structured XML schema type.
</xsd:documentation>
</xsd:annotation>
<!-- This has not been tested since I do not know how to feed
multiple schemas into one of the schema validators. -->
<xsd:complexType name="staff">
<xsd:sequence>
<xsd:element name="person" type="per:person"
minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
<?xml version="1.0"?>
<!-- xml/person.xsd -->
<xsd:schema targetNamespace="http://www.inf.uos.de/schoenw/person"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xml:lang="en">
<xsd:annotation>
<xsd:documentation>
This schema defines the formal syntax of the person
structured XML schema type.
</xsd:documentation>
</xsd:annotation>
<!-The following two complex types define the person and
name sequences of elements. This is still simple...
-->
<xsd:complexType name="person">
<xsd:sequence>
<xsd:element name="name" type="name"/>
<xsd:element name="email" type="email"
minOccurs="1" maxOccurs="unbounded"/>
<xsd:element name="phone" type="phone"
minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="name">
229
230
KAPITEL 12. EXTENSIBLE MARKUP LANGUAGE (XML)
<xsd:sequence>
<xsd:element name="title" type="xsd:string"
minOccurs="0" maxOccurs="1"/>
<xsd:element name="first" type="xsd:string"/>
<xsd:element name="middle" type="xsd:string"
minOccurs="0" maxOccurs="1"/>
<xsd:element name="last"
type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<!-The following two types have to be complex since only
complex types can have attributes. In fact, we have a
complex type which holds a simple value by extending
a simple type defined below.
-->
<xsd:complexType name="email">
<xsd:simpleContent>
<xsd:extension base="emailString">
<xsd:attributeGroup ref="categoryAttributeGroup"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:complexType name="phone">
<xsd:simpleContent>
<xsd:extension base="phoneString">
<xsd:attributeGroup ref="categoryAttributeGroup"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<!-These are our simple types for email and phone strings.
Regular expressions are used to restrict the set of legal
values.
-->
<xsd:simpleType name="emailString">
<xsd:restriction base="xsd:string">
<!-- <xsd:pattern value=""/> -->
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="phoneString">
<xsd:restriction base="xsd:string">
<xsd:pattern value="\+?[0-9 ]+"/>
</xsd:restriction>
</xsd:simpleType>
<!-The attribute group allows to define the category attribute
in one place. See the above reference to categoryAttributeGroup.
12.5. XML PATHS (XPATH)
-->
<xsd:attributeGroup name="categoryAttributeGroup">
<xsd:attribute name="category">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="work"/>
<xsd:enumeration value="private"/>
<xsd:enumeration value="other"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:attributeGroup>
</xsd:schema>
12.5 XML Paths (XPATH)
12.6 XML Stylesheet Language (XSL)
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml" version="1.0">
<!-- style sheet for the staff.xml file -->
<xsl:template match="staff">
<table>
<xsl:apply-templates select="person"/>
</table>
</xsl:template>
<xsl:template match="person">
<tr>
<td><xsl:apply-templates select="name"/></td>
<td><xsl:apply-templates select="email"/></td>
<td><xsl:apply-templates select="phone"/></td>
</tr>
</xsl:template>
<xsl:template match="name">
<xsl:if test="title">
<xsl:text> </xsl:text>
<xsl:value-of select="title"/>
</xsl:if>
<xsl:value-of select="first"/>
<xsl:if test="middle">
<xsl:text> </xsl:text>
<xsl:value-of select="middle"/>
</xsl:if>
<xsl:text> </xsl:text>
<xsl:value-of select="last"/>
231
232
KAPITEL 12. EXTENSIBLE MARKUP LANGUAGE (XML)
</xsl:template>
<xsl:template match="email">
<xsl:apply-templates/>
<xsl:text> </xsl:text>
</xsl:template>
<xsl:template match="phone">
<xsl:apply-templates/>
<xsl:text> </xsl:text>
</xsl:template>
</xsl:stylesheet>
12.7
Document Object Model (DOM)
12.8
Java DOM API
Kapitel 13
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.
13.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 13.1: Client/Server-Modell
Der Kommunikationsablauf im Client/Server-Modell besteht in der Regel aus zwei Nachrichten. Während
der Verarbeitung des Zugriffs ist im einfachsten Fall der Client blockiert.
Client/Server-System lassen sich elegant mit der Hilfe von entfernten Proceduraufrufen (remote procedure
calls, RPC) implementieren. Beim RPC wird eine lokale Funktion aufgerufen, die als Stub-Funktion 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 (marshalling). Außerdem können Stub-Funktionen das
notwendige Binden an einen passenden Server unterstützen.
233
234
KAPITEL 13. VERTEILTE ANWENDUNGEN
Client
Client
Stub
Server
Stub
Server
Access
Send
Access
Result
Send
Result
Abbildung 13.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.
13.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.
• 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.
235
13.2. N-TIER ARCHITEKTUREN
13.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 13.3: 2-Tier Architektur
13.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 13.4: 3-Tier Architektur
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.
13.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 Web-Browser erfolgt. Die Zugriffschnittstelle wird durch in der Regel mehrere Web-Server realisiert, wobei ein automatischer
236
KAPITEL 13. VERTEILTE ANWENDUNGEN
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 13.5: 4-Tier Architektur
Kapitel 14
CORBA
Die Common Object Request Broker Architecture (CORBA) [20, 24] wird seit 1989 durch die Object Management Group (OMG) entwickelt und standardisiert. Hinter der OMG verbirgt sich ein Zusammenschluss von
Herstellern, die durch die Schaffung einer gemeinsamen objekt-orientierten Kommunikations-Middleware
die Interoperabilität von Produkten erreichen wollen.
14.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 14.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.
237
238
KAPITEL 14. 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.
14.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 Prozess 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.
14.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 so genannte Kanäle zugestellt, wobei ein
Nachrichten-Verbraucher nicht notwendigerweise aktiv sein muss, 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 umfasst insbesondere Dienste zum Erzeugen, Löschen, Kopieren
oder Verschieben von Objekten.
14.4
Common Facilities
Neben den Object Services, die grundlegende Basisfunktionen bereitstellen, gibt es weitere standardisierte
Dienste, die in speziellen Anwendungsbereichen sinnvoll sind.
14.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 Un-
239
14.6. INTERFACE DEFINITION LANGUAGE (IDL)
abhä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 14.2: Komponenten eines Object Request Brokers (ORB)
Bemerkungen:
• 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.
14.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 muss 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 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, dass für einige Sprachen Einschränkungen existieren. Beispielsweise verfügt Java über keine
vorzeichenlosen Zahlentypen, die aber in der IDL existieren. Die Spezifikation überlässt es in solchen Fällen
dem Programmierer, die sich daraus ergebenden Probleme korrekt zu behandeln.)
IDL-Definitionen befinden sich in so genannten 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 so genannten Interfaces. Ein Interface definiert eine CORBA Objektklasse, die durch die Menge der Attribute und Operationen beschrieben wird. Ein
240
KAPITEL 14. CORBA
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 Groß-/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.
14.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 14.3: Datentypen der Interface Definition Language (IDL)
Bemerkungen:
• Die Datentypen short, long und long long sind ganze vorzeichenbehaftete Zahlen (16, 32 bzw.
64 Bit).
• Die Datentypen unsigned short, unsigned long und unsigned long long sind 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 eine Variable vom Datentyp wstring Zeichen enthalten kann, die aus mehreren Bytes bestehen.
14.6. INTERFACE DEFINITION LANGUAGE (IDL)
241
• Der Datentyp enum definiert eine Aufzählung.
• Der Datentyp octet repräsentiert ein einzelnes Byte, das während einer Übertragung nicht verändert
wird.
• Der Datentyp any kann den Wert eines beliebigen anderen Datentyps enthalten, wobei die Wert mit
einem so genannten TypeCode assoziiert ist.
• Datentypen können zu einer Struktur struct zusammengefasst 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
muss (discriminated union).
• Eine sequence ist eine Sequenz von einem Datentyp und entspricht einem Feld oder Array.
14.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>
14.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>* "}"
14.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.
<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> ";"
Bemerkungen:
• 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).
242
KAPITEL 14. CORBA
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> }* ")"
Bemerkungen:
• Die Parameter-Attribute zeigen ob 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 raisesKlausel angegeben.
• Kontextinformation des Client können der Objektimplementation zur Verfügung gestellt, um z.B. das
Leistungsverhalten einer Objektimplementation zu beeinflussen.
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> }*
14.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>+ "}"
14.7 Objekt-Referenzen
Damit ein Client ein CORBA-Objekt aufrufen kann, muss 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.
243
14.8. JAVA LANGUAGE MAPPING
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 14.1: Abbildung der Basis-Datentypen
• Eine stringified object reference ist eine ASCII-Darstellung einer Objekt-Referenz.
14.8 Java Language Mapping
Die Abbildung der CORBA-IDL auf die Implementationssprache Java ist in [19] definiert. Die Abbildung ist
in vielen Teilen relativ einfach, da Java der IDL sehr nahe kommt. Allerdings gibt es in einigen Details auch
durchaus Überraschungen.
• IDL-Module werden auf Java-Packages abgebildet.
• Die Basis-Datentypen werden entsprechend Tabelle 14.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.
• Für definierte Datentypen werden so genannte 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 so genannte Helper
Classes erzeugt.
Das folgende Beispiel definiert ein IDL-Interface Hello mit der Operation sayHello() in dem Modul
HelloModule.
244
KAPITEL 14. CORBA
// hello/Hello.idl
module HelloModule {
interface Hello {
string sayHello();
};
};
Die Übersetzung des IDL Moduls nach Java liefert die in Abbildung 14.4 dargestellten Klassen.
«interface»
HelloOperations
«interface»
Object
HelloHelper
«interface»
Hello
«interface»
IDLEntity
_HelloStub
ObjectImpl
_HelloImplBase
Abbildung 14.4: Klassendiagramm für die von Hello.idl erzeugten Java Klassen
Der Quellcode für eine Implementation des Hello-Interfaces:
// hello/HelloServant.java
import HelloModule._HelloImplBase;
class HelloServant extends _HelloImplBase
{
public String sayHello()
{
return "Hello World";
}
}
Der Quellcode für einen einfachen Client:
// hello/HelloClient.java
import
import
import
import
import
import
HelloModule.Hello;
HelloModule.HelloHelper;
org.omg.CORBA.ORB;
org.omg.CosNaming.NameComponent;
org.omg.CosNaming.NamingContext;
org.omg.CosNaming.NamingContextHelper;
14.8. JAVA LANGUAGE MAPPING
public class HelloClient
{
public static void main(String args[])
{
try {
org.omg.CORBA.Object obj;
NamingContext ncxt;
// Initialize the ORB and retrieve and narrow the
// reference to the CORBA naming service object.
ORB orb = ORB.init(args, null);
obj = orb.resolve_initial_references("NameService");
ncxt = NamingContextHelper.narrow(obj);
// Lookup the server object in the naming service and
// narrow it down.
NameComponent nc = new NameComponent("Hello", "");
NameComponent path[] = { nc };
Hello helloRef = HelloHelper.narrow(ncxt.resolve(path));
// Now we are ready to invoke methods on the potentially
// remote server object.
String hello = helloRef.sayHello();
System.out.println(hello);
} catch (Exception e) {
System.err.println("HelloClient: " + e.toString());
System.exit(1);
}
}
}
Der Quellcode für einen einfachen Server:
// hello/HelloServer.java
import
import
import
import
import
HelloServant;
org.omg.CORBA.ORB;
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
{
try {
org.omg.CORBA.Object obj;
NamingContext ncxt;
// Initialize the ORB and retrieve and narrow the
245
246
KAPITEL 14. CORBA
// reference to the CORBA naming service object.
ORB orb = ORB.init(args, null);
obj = orb.resolve_initial_references("NameService");
ncxt = NamingContextHelper.narrow(obj);
// Create the server object and register it in the ORB.
HelloServant hello = new HelloServant();
orb.connect(hello);
// Register the server object in the naming service.
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 (Exception e) {
System.err.println("HelloServer: " + e.toString());
System.exit(1);
}
}
}
14.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.
• 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:
14.9. NAMING SERVICE
247
//File: CosNaming.idl
//The only module of the Naming Service
#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;
};
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)
248
KAPITEL 14. CORBA
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_ */
14.10
Mau Mau
In diesem Abschnitt soll ein verteiltes Mau-Mau-Spiel realisiert werden. Die IDL-Schnittstelle sieht wie folgt
aus:
/*
* maumau/MauMau.idl
*
* 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)
*/
249
14.10. MAU MAU
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,
inout Color color);
//
//
//
//
card on the stack
age of card on the stack
card played (if any)
new color (if any)
void info(in
in
in
in
in
//
//
//
//
//
identity of the player
card on the stack
age of card on the stack
new color (if any)
# cards taken or 0
unsigned short player,
Card stack,
unsigned short age,
Color color,
unsigned short taken);
250
KAPITEL 14. CORBA
oneway void bye(in string s);
// 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 14.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:
251
14.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 14.6: Sequenzdiagramm für das Mau-Mau-Spiel
Die Implementation der Klasse PlayerServant:
// maumau/PlayerServant.java
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
{
252
KAPITEL 14. CORBA
private short ident;
private short players;
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) {
14.10. MAU MAU
253
return serve(c, ch, wh);
}
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.
254
KAPITEL 14. 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:
// maumau/PlayerServer.java
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
{
14.10. MAU MAU
255
static final int numPlayers = 3;
static final String context = "maumau";
static NamingContext addContext(NamingContext ncxt, String id, String kind)
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:
256
KAPITEL 14. CORBA
// maumau/Game.java
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
MauMau.Player;
MauMau.PlayerHelper;
MauMau.Card;
MauMau.CardHolder;
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,
257
14.10. MAU MAU
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
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;
258
KAPITEL 14. CORBA
BindingHolder bh = new BindingHolder();
BindingListHolder blh = new BindingListHolder();
BindingIteratorHolder bih = new BindingIteratorHolder();
ncxt.list(0, blh, bih);
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;
}
}
}
14.10. MAU MAU
/**
* 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()
{
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) {
259
260
KAPITEL 14. CORBA
System.out.println(player + ": receives card " + deck[k].card);
}
}
return count;
}
/**
* 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
{
14.10. MAU MAU
261
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");
}
} 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;
262
KAPITEL 14. CORBA
color = stack.color;
for (i = 0; true; i = (short) ((i+1) % players.length)) {
if (verbose) {
System.out.println();
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 " +
stack
+ color);
2;
stack);
263
14.10. MAU MAU
}
}
}
info(i, taken);
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:
// maumau/OwnedCard.java
import MauMau.Card;
264
KAPITEL 14. CORBA
import MauMau.Color;
import MauMau.Value;
public class OwnedCard
{
public Card card;
public short owner;
public OwnedCard(Card card, short owner)
{
this.card = card;
this.owner = owner;
}
}
14.11 Schlußbemerkungen
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 15
Remote Method Invocation (RMI)
Besteht eine komplexe verteilte Applikation komplett aus Java-Komponenten, so ist die Verwendung von
CORBA als Kommunikations-Middleware unter Umständen zu aufwendig. Als Alternative bietet sich in
diesem Fall die Java Remote Method Invocation (RMI) an, bei der Schnittstellen von entfernten Objekten
einfach durch Java Interfaces beschrieben werden.
Ein Java Interface wird als Remote Interface bezeichnet, wenn es die folgenden Anforderungen erfüllt:
1. Das Interface muss public deklariert sein.
2. Das Interface muss von der Klasse java.rmi.Remote abgeleitet sein.
3. Da ein Methodenaufruf an ein entferntes Objekt aus verschiedenen Gründen fehlschlagen kann, muss
für jede Methode die Ausnahme RemoteException (oder eine Oberklasse von RemoteException)
deklariert werden.
4. Alle Datenobjekte, die als Argumente übergeben oder als Resultat einer RMI-Methode benutzt werden, müssen als Remote Interface deklariert sein.
Ein einfaches Beispiel, das den Anforderungen an ein Remote Interface genügt, ist das folgende Interface
Hello:
// hello/Hello.java
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Hello extends Remote
{
String sayHello() throws RemoteException;
}
Bei der Implementation eines Remote Interface ist festzulegen, welcher Kommunikationsmechanismus benutzt werden soll und ob das Server-Objekt ständig verfügbar sein soll oder bei Bedarf aktiviert werden
soll. Im einfachsten Fall, wenn das Server-Objekt ständig verfügbar ist und die Kommunikation über das
Transmission Control Protocol (TCP) abgewickelt wird, kann man die Server-Klasse von der Hilfsklasse
UnicastRemoteObject ableiten.
// hello/HelloServant.java
265
266
KAPITEL 15. REMOTE METHOD INVOCATION (RMI)
import Hello;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class HelloServant extends UnicastRemoteObject
implements Hello
{
public HelloServant() throws RemoteException
{
super();
}
public String sayHello()
{
return "Hello World";
}
}
Bemerkungen:
• Der Konstruktor muss ebenfalls eine RemoteException deklarieren, da das neue Server-Objekt
im Konstruktor der Klasse UnicastRemoteObject exportiert wird und dabei eine entsprechende
Ausnahme auftreten kann.
Damit ein Server-Objekt benutzt werden kann, muss es in einem Server erzeugt werden und in einem Namensdienst registriert werden. Der folgende Quelltext realisiert einen HelloServer für ein HelloServantObjekt.
// hello/HelloServer.java
import HelloServant;
import java.rmi.Naming;
public class HelloServer
{
public static void main(String args[])
{
try {
// Create the server object.
HelloServant hello = new HelloServant();
// Register the server object in the RMI registry.
Naming.rebind("//localhost/HelloServant", hello);
} catch (Exception e) {
System.err.println("HelloServer: " + e.toString());
System.exit(1);
}
}
}
Bemerkungen:
• In der Methode main() wird zunächst ein Security Manager installiert, sofern noch kein Security
Manager installiert worden ist. Der Security Manager kontrolliert, welche Klassen zur Laufzeit geladen werden dürfen.
267
• Der Konstruktor der Klasse HelloServant exportiert das Server-Objekt automatisch. Damit Benutzer einfach eine Referenz auf das Server-Objekt bekommen können, wird das Server-Objekt im
RMI Namensdienst registriert.
Ein einfacher Client, der ein Server-Objekt benutzt, hat folgende Struktur:
// hello/HelloClient.java
import Hello;
import java.rmi.Naming;
public class HelloClient
{
public static void main(String args[])
{
String message;
try {
Hello hello = (Hello) Naming.lookup("//localhost/HelloServant");
message = hello.sayHello();
System.out.println(message);
} catch (Exception e) {
System.err.println("HelloClient: " + e.toString());
System.exit(1);
}
}
}
Bemerkungen:
•
268
KAPITEL 15. REMOTE METHOD INVOCATION (RMI)
Kapitel 16
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) [27] 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 16.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.
16.1 JNDI-Terminologie
Zunächst ist es wichtig, die JNDI-Terminologie zu verstehen:
• Ein atomarer Name (atomic name) ist ein nicht zerlegbarer Teil eines zusammengesetzten Namens.
269
270
KAPITEL 16. JAVA NAME AND DIRECTORY INTERFACE (JNDI)
• 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.
Ein 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. LDAP
Version 3 wird als weit verbreiteter Verzeichnisdienst besonders unterstützt. Entsprechend besteht die JNDI
Programmierschnittstelle 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.
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.
271
16.2. 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 16.2: JNDI Klassen und Interfaces
16.2 Zugriff auf Namensdienste
Die Abbildung 16.2 zeigt die wesentlichen JNDI Klassen und Interfaces zum Zugriff auf Namensdienste.
Bemerkungen:
• 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.
Das folgende Beispiel demonstriert, wie mit Hilfe eines JNDI Service Providers für das lokale Dateisystem
Dateinamen aufgelöst werden können.
// jndi/Lookup.java
import
import
import
import
import
java.io.File;
java.util.Hashtable;
javax.naming.Context;
javax.naming.InitialContext;
javax.naming.NamingException;
public class Lookup
{
272
KAPITEL 16. JAVA NAME AND DIRECTORY INTERFACE (JNDI)
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);
System.exit(1);
}
}
}
}
Bemerkungen:
• Der Service Provider wird über eine Hashtabelle ausgewählt und parametrisiert.
• Der initiale Kontext muss 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.
// jndi/List.java
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
{
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 {
16.3. ZUGRIFF AUF VERZEICHNISDIENSTE
273
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);
System.exit(1);
}
}
}
Bemerkungen:
• 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.
16.3 Zugriff auf Verzeichnisdienste
Ein Verzeichnis ist in JNDI eine Erweiterung eines Namensdienstes, wobei 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 Abbildung 16.3 zeigt die wesentlichen Klassen und Interfaces zum Zugriff auf Verzeichnisse.
Das Lesen von Attributen kann beispielsweise folgendermassen realisiert werden:
// java/LookupAttributes.java
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;
274
KAPITEL 16. 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 16.3: JNDI Klassen und Interfaces für Verzeichnisdienste
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);
// GetattrsAll.printAttrs(answer);
ctx.close();
} catch (NamingException e) {
System.err.println("Lookup failed: " + e);
System.exit(1);
16.3. ZUGRIFF AUF VERZEICHNISDIENSTE
275
}
}
}
Bemerkungen:
• LDAP Filter für Suchanfragen sind in RFC 2254 [12] definiert, z.B. (&(sn=Geisel)(mail=*)).
• Mit Hilfe der SearchControls kann die Ergebnismenge und die Bearbeitung der Suchanfrage
parametrisiert werden.
276
KAPITEL 16. JAVA NAME AND DIRECTORY INTERFACE (JNDI)
Kapitel 17
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 Java-spezifischen 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.
17.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ässt.
• EJB-Container stellen zusätzliche Dienste bereit (z.B. Transaktionen, Sicherheit).
• EJB-Container können sofern erforderlich EBJs ein- und auslagern. Dazu muss 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.
• Die Lebenszeit einer Session Bean erstreckt sich typischerweise über eine Sitzung, die mehrere Client/ServerInteraktionen umfasst. (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.
277
278
KAPITEL 17. ENTERPRISE JAVA BEANS
• 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).
17.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.
• 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.
279
17.3. AUFBAU EINER EJB
• 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.
17.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
EJB Container
aClient
4 Call EJBObject
aEJBObject
5. Call EJB
aEJBInternalObjekt
3. EJBObject Reference
2. Create EJBObject
1. EJBObject?
aHomeObject
Abbildung 17.1: Zusammenspiel zwischen Home-Objekt und EJB-Objekt
Prinzipieller Ablauf beim Zugriff auf eine EJB (Abbildung 17.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 17.2):
280
KAPITEL 17. ENTERPRISE JAVA BEANS
«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 17.2: Enterprise Java Beans Interfaces
Kapitel 18
Java Database Connectivity (JDBC)
Mit Hilfe der Java Database Connectivity API (JDBC) können Java Programme Operationen auf relationalen
Datenbanken ausführen, die die deskriptive Structured Query Language (SQL) als Datenbanksprache unterstützen. Für die verschiedenen Implementationen von relationalen Datenbanken gibt es passende Treiber,
die den Zugriff für eine konkrete Datenbank-Implementation realisieren.
18.1 Relationale Datenbanken
Relationale Datenbanken [6] sind in den 70er Jahren entwickelt worden. Sie basieren auf einer formalen
Relationen-Algebra und unterstützen im Gegensatz zu anderen Ansätzen deskriptive nicht-prozedurale Anfragesprachen wie SQL.
Die praktische Betrachtungsweise von Relationen sind Tabellen:
• Eine Tabelle (Relation) enthält mehrere Datensätze (Zeilen, Tupel).
• Ein Datensatz besitzt mehrere Attribute, die sich aus den Spalten der Tabelle ergeben.
• Um einem Datensatz eindeutig identifizieren zu können benötigt man einen Primärschlüssel (primary
key). Ein Primärschlüssel kann aus einer Kombination von Attributen bestehen.
• Verweise auf Datensätze in anderen Relationen werden durch Attribute realisiert, deren Werte eindeutig Datensätze in einer anderen Tabelle (Relation) identifizieren (Fremdschlüssel).
Anfragen können als Terme der Relationenalgebra interpretiert werden und setzen sich aus folgenden Grundoperationen zusammen:
• Vereinigung: Die Vereinigung von zwei Objekt-Mengen mit gleichem Schema.
• Differenz: Die Differenz von zwei Objekt-Mengen mit gleichem Schema.
• Kartesisches Produkt: Die Bildung aller Kombinationen zweier Objekt-Mengen.
• Projektion: Die Auswahl und Vertauschung von Spalten.
• Selektion: Die Auswahl von Zeilen einer Objekt-Menge, die einer gegebenen Bedingung genügen. Die
Bedingung können logische Operatoren, relationale Operatoren und Konstanten benutzt werden.
• Umbenennung: Umbenennung der Attributnamen in einer Menge.
• Natürlicher Verbund: Bildung der Kombinationen von zwei Objekt-Mengen anhand gemeinsamer Attribute.
281
282
KAPITEL 18. JAVA DATABASE CONNECTIVITY (JDBC)
Bemerkungen:
• Mit den Termen der Relationenalgebra ist es nicht möglich, zu einer zweistelligen Relation R(x, y)
die transitive Hülle
R∗ = {(x, y)|∃x1 , . . . , xk : (x, x1 ) ∈ R ∧ (x1 , x2 ) ∈ R ∧ . . . ∧ (xk , y) ∈ R}
zu berechnen.
• Bei der Auswertung von Termen sind umfangreiche Optimierungen möglich. In der Regel werden daher Terme (Anfragen) zunächst von einem Optimierer umgeschrieben, bevor sie ausgewertet werden.
18.2 Structured Query Language
Die wesentlichen Elemente der Structured Query Language (SQL) sollen an einem Beispiel demonstriert
werden, das aus zwei Tabellen (Relationen) besteht:
name
Riese
Klein
Meier
Weiland
vorname
Adam
Eva
Max
Walter
matrikelnr
123456
123457
214321
105432
Tabelle 18.1: Tabelle studenten mit dem Primärschlüssel matrikelnr
matrikelnr
123456
123456
123457
fach
Informatik
Mathematik
Informatik
note
1.2
3.4
2.3
Tabelle 18.2: Tabelle fachnote mit dem Primärschlüssel matrikelnr,fach
Die Befehle der Sprache SQL werden allgemein in drei Bereiche unterteilt:
• Data Definition Language (DDL)
• Data Manipulation Language (DML)
• Data Control Language (DCL)
18.2.1
Data Definition Language
Erzeugen von Tabellen
Das Erzeugen von neuen Tabellen geschieht mit Hilfe des CREATE TABLE Befehls. Die Tabellen studenten
und fachnote werden beispielsweise mit folgenden SQL-Befehlen erzeugt:
18.2. STRUCTURED QUERY LANGUAGE
283
CREATE TABLE studenten(name VARCHAR(20),
vorname VARCHAR(20),
matrikelnr INT NOT NULL PRIMARY KEY)
CREATE TABLE fachnoten(matrikelnr INT NOT NULL,
fach VARCHAR(20) NOT NULL,
note DECIMAL(2,1))
Löschen von Tabellen
Tabellen können mit Hilfe von DROP TABLE wieder entfernt werden. Beispielsweise würden die Befehle
DROP TABLE studenten
DROP TABLE fachnote
die soeben erzeugten Tabellen wieder löschen. Vorsicht: Nach einem DROP TABLE sind alle in der Tabelle
enthaltenen Daten unwiderruflich gelöscht.
Ändern von Tabellen
Mit Hilfe des ALTER TABLE Befehls kann eine existierende Tabelle verändert werden. Beispielsweise
können Spalten hinzugefügt oder gelöscht werden.
18.2.2
Data Manipulation Language
Die Data Manipulation Language stellt Befehle bereit, mit denen Datensätze eingefügt, gelöscht, verändert
und ausgelesen werden können.
Einfügen von Datensätzen
Der Befehl INSERT INTO erlaubt das Einfügen von neuen Datensätzen. Dabei werden entweder die Werte
eines Datensatzes passend zur Struktur einer Tabelle angegeben oder man gibt explizit vor, welche Spalten
mit Werten belegt werden sollen.
INSERT
INSERT
INSERT
INSERT
INTO
INTO
INTO
INTO
studenten
studenten
studenten
studenten
VALUES
VALUES
VALUES
VALUES
(’Riese’, ’Adam’, 123456)
(’Klein’, ’Eva’, 123457)
(’Meier’, ’Max’, 214321)
(’Weiland’, ’Walter’, 105432)
INSERT INTO fachnoten VALUES (123456, ’Informatik’, 1.2)
INSERT INTO fachnoten VALUES (123456, ’Mathematik’, 3.4)
INSERT INTO fachnoten VALUES (123457, ’Informatik’, 2.3)
Löschen von Datensätzen
Der Befehl DELETE FROM zum Löschen von Datensätzen arbeitet auf Mengen. Gelöscht werden alle Datensätze, die der Bedingung in der WHERE-Klausel entsprechen. Will man genau einen Datensatz löschen, so
ist der entsprechende Schlüssel in der WHERE-Klausel zu benutzen.
284
KAPITEL 18. JAVA DATABASE CONNECTIVITY (JDBC)
DELETE FROM studenten WHERE matrikelnr = 123456
Man beachte, dass dieser Befehl unter Umständen die Datenbank in einem inkonsistenten Zustand hinterlässt.
Es existieren jetzt nämlich potentiell noch Einträge in der Tabelle fachnoten zu dem nun nicht mehr
existierenden Studenten.
Verändern von Datensätzen
Datensätze können mit Hilfe des UPDATE-Befehls aktualisiert werden. Eine WHERE-Klausel selektiert wiederum die Datensätze, die verändert werden sollen.
UPDATE studenten SET name = ’Mueller’, vorname = ’Ben’
WHERE matrikelnr = 135322
Anfragen
Anfragen zum Auslesen von Datensätzen können beliebige Terme der Relationen-Algebra verwenden. Allerdings ist die Anfragesyntax des Befehls SELECT weniger formal als die zugrundeliegende Relationenalgebra.
• Beispiel für eine Selektion:
SELECT * FROM studenten WHERE matrikelnr = 123456
• Beispiel für eine Projektion:
SELECT vorname, name FROM studenten
• Beispiel für einen Verbund:
SELECT vorname, name, fach, note
FROM studenten, fachnoten
WHERE studenten.matrikelnr = fachnoten.matrikelnr
• Zusätzlich können Ergebnismengen aufsteigend (ASC) oder absteigend (DESC) sortiert werden:
SELECT vorname, name, fach, note
FROM studenten, fachnoten
WHERE studenten.matrikelnr = fachnoten.matrikelnr
ORDER BY note ASC
18.2.3
Data Control Language
Die Data Control Language dient zur Steuerung von Transaktionen, mit denen mehrere Datenbankzugriffe
als eine atomare Einheit behandelt werden können. Tritt während der Durchführung einer Transaktion ein
Fehler auf, so kann die Transaktion mit Hilfe des ROLLBACK-Befehls abgebrochen werden. Die Datenbank
wird daraufhin alle in der Transaktion durchgeführten Änderungen zurücknehmen.
Wird die Transaktion erfolgreich durchgeführt, so wird die Transaktion mit Hilfe des COMMIT-Befehls abgeschlossen. Alle Änderungen werden nun endgültig in den neuen Datenbankzustand übernommen.
18.3. SQL ZUGRIFFE AUS JAVA MIT JDBC
18.3 SQL Zugriffe aus Java mit JDBC
-- MySQL dump 8.22
--- Host: localhost
Database: infoc
---------------------------------------------------------- Server version 3.23.54
--- Table structure for table ’fachnoten’
-CREATE TABLE fachnoten (
matrikelnr int(11) NOT NULL default ’0’,
fach varchar(20) NOT NULL default ’’,
note decimal(2,1) default NULL
) TYPE=MyISAM;
--- Dumping data for table ’fachnoten’
--
INSERT INTO fachnoten VALUES (123456,’Informatik’,1.2);
INSERT INTO fachnoten VALUES (123456,’Mathematik’,3.4);
INSERT INTO fachnoten VALUES (123457,’Informatik’,2.3);
--- Table structure for table ’studenten’
-CREATE TABLE studenten (
name varchar(20) default NULL,
vorname varchar(20) default NULL,
matrikelnr int(11) NOT NULL default ’0’,
PRIMARY KEY (matrikelnr)
) TYPE=MyISAM;
--- Dumping data for table ’studenten’
--
INSERT
INSERT
INSERT
INSERT
INTO
INTO
INTO
INTO
studenten
studenten
studenten
studenten
VALUES
VALUES
VALUES
VALUES
(’Riese’,’Adam’,123456);
(’Klein’,’Eva’,123457);
(’Meier’,’Max’,214321);
(’Weiland’,’Walter’,105432);
// jdbc/studenten1/Studenten.java
import java.sql.Connection;
import java.sql.Statement;
285
286
KAPITEL 18. JAVA DATABASE CONNECTIVITY (JDBC)
import
import
import
import
import
import
import
java.sql.PreparedStatement;
java.sql.ResultSet;
java.sql.DriverManager;
java.sql.SQLException;
java.lang.ClassNotFoundException;
java.lang.InstantiationException;
java.lang.IllegalAccessException;
public class Studenten
{
private Connection con = null;
/**
* Establish a connection to the database.
*/
public void connect(String url)
throws SQLException, ClassNotFoundException,
InstantiationException, java.lang.IllegalAccessException
{
disconnect();
// ensure we are not connected ...
Class.forName("com.mysql.jdbc.Driver").newInstance();
con = DriverManager.getConnection(url);
}
/**
* Disconnect from a database.
*/
public void disconnect()
throws SQLException
{
if (con != null) con.close();
}
/**
* This method uses a Statement to show the students in the
* database.
*/
public void show()
throws SQLException
{
final String query =
"SELECT name, vorname, matrikelnr FROM studenten";
Statement stmt;
ResultSet rs;
stmt = con.createStatement();
rs = stmt.executeQuery(query);
while (rs.next()) {
System.out.println(rs.getString(1) +
"\t" + rs.getString(2) +
"\t" + rs.getInt(3));
18.3. SQL ZUGRIFFE AUS JAVA MIT JDBC
287
}
stmt.close();
}
/**
* This method uses a PreparedStatement to insert a bunch
* of students into the database.
*/
public void insert(String[] name, String[] vorname, int[] matrikelnr)
throws SQLException
{
final String template =
"INSERT INTO studenten(name, vorname, matrikelnr) " +
"VALUES (?, ?, ?)";
PreparedStatement stmt;
stmt = con.prepareStatement(template);
for (int i = 0; i < name.length; i++) {
stmt.setString(1, name[i]);
stmt.setString(2, vorname[i]);
stmt.setInt(3, matrikelnr[i]);
stmt.executeUpdate();
}
stmt.close();
}
/**
* This method uses a batch of PreparedStatement to remove a
* bunch of students from the database.
*/
public void remove(int[] matrikelnr)
throws SQLException
{
final String template =
"DELETE FROM studenten WHERE matrikelnr = ?";
PreparedStatement stmt;
stmt = con.prepareStatement(template);
for (int i = 0; i < matrikelnr.length; i++) {
stmt.setInt(1, matrikelnr[i]);
stmt.addBatch();
}
stmt.executeBatch();
stmt.close();
}
/**
* This method uses a ResultSet from a Statement which allows to
* modify the result set and to write back modified rows to the
* database.
*/
288
KAPITEL 18. JAVA DATABASE CONNECTIVITY (JDBC)
public void update(String[] name, String[] vorname, int[] matrikelnr)
throws SQLException
{
final String query =
"SELECT name, vorname, matrikelnr FROM studenten";
Statement stmt;
ResultSet rs;
stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
rs = stmt.executeQuery(query);
for (int i = 0; i < name.length; i++) {
rs.beforeFirst();
while (rs.next()) {
if (rs.getInt(3) == matrikelnr[i]) {
rs.updateString(1, name[i]);
rs.updateString(2, vorname[i]);
rs.updateRow();
break;
}
}
}
stmt.close();
}
public static void main(String[] args)
throws Exception
{
final String url
= "jdbc:mysql://localhost/infoc?user=infoc&password=$infoc!";
String[] name
= { "Kafka", "Mueller", "Schreiner", "Kaufmann" };
String[] vorname = { "Franz", "Martin", "Corina", "Claudia" };
int[] matrikelnr = { 123456, 234567, 345678, 456789 };
Studenten s = new Studenten();
s.connect(url);
System.out.println("\nOriginal state of the database:\n");
s.show();
s.remove(matrikelnr);
System.out.println("\nIntermediate state of the database:\n");
s.show();
s.insert(name, vorname, matrikelnr);
System.out.println("\nFinal state of the database:\n");
s.show();
s.disconnect();
}
}
// jdbc/studenten2/Studenten.java
18.3. SQL ZUGRIFFE AUS JAVA MIT JDBC
import
import
import
import
import
import
import
import
import
289
java.sql.Connection;
java.sql.Statement;
java.sql.PreparedStatement;
java.sql.ResultSet;
java.sql.DriverManager;
java.sql.SQLException;
java.lang.ClassNotFoundException;
java.lang.InstantiationException;
java.lang.IllegalAccessException;
public class Studenten
{
private Connection con = null;
/**
* Establish a connection to the database.
*/
public void connect(String url)
throws SQLException, ClassNotFoundException,
InstantiationException, java.lang.IllegalAccessException
{
disconnect();
// ensure we are not connected ...
Class.forName("com.mysql.jdbc.Driver").newInstance();
con = DriverManager.getConnection(url);
}
/**
* Disconnect from a database.
*/
public void disconnect()
throws SQLException
{
if (con != null) con.close();
}
/**
* Start a new transaction by disabling automatic commit.
*/
public void startTransaction()
throws SQLException
{
con.setAutoCommit(false);
}
/**
* Commit the current transaction and reenable automatic commit.
*/
public void commitTransaction()
throws SQLException
{
290
KAPITEL 18. JAVA DATABASE CONNECTIVITY (JDBC)
con.commit();
con.setAutoCommit(true);
}
/**
* Abort the current transaction by rolling back and reenable
* automatic commit.
*/
public void abortTransaction()
throws SQLException
{
con.rollback();
con.setAutoCommit(true);
}
/**
* This method uses a Statement to show the students in the
* database.
*/
public void show()
throws SQLException
{
final String query =
"SELECT name, vorname, matrikelnr FROM studenten";
Statement stmt;
ResultSet rs;
stmt = con.createStatement();
rs = stmt.executeQuery(query);
while (rs.next()) {
System.out.println(rs.getString(1) +
"\t" + rs.getString(2) +
"\t" + rs.getInt(3));
}
stmt.close();
}
/**
* This method uses a PreparedStatement to insert a bunch
* of students into the database.
*/
public void insert(String[] name, String[] vorname, int[] matrikelnr)
throws SQLException
{
final String template =
"INSERT INTO studenten(name, vorname, matrikelnr) " +
"VALUES (?, ?, ?)";
PreparedStatement stmt;
stmt = con.prepareStatement(template);
for (int i = 0; i < name.length; i++) {
18.3. SQL ZUGRIFFE AUS JAVA MIT JDBC
291
stmt.setString(1, name[i]);
stmt.setString(2, vorname[i]);
stmt.setInt(3, matrikelnr[i]);
stmt.executeUpdate();
}
stmt.close();
}
/**
* This method uses a batch of PreparedStatement to remove a
* bunch of students from the database.
*/
public void remove(int[] matrikelnr)
throws SQLException
{
final String template =
"DELETE FROM studenten WHERE matrikelnr = ?";
PreparedStatement stmt;
stmt = con.prepareStatement(template);
for (int i = 0; i < matrikelnr.length; i++) {
stmt.setInt(1, matrikelnr[i]);
stmt.addBatch();
}
stmt.executeBatch();
stmt.close();
}
/**
* This method uses a ResultSet from a Statement which allows to
* modify the result set and to write back modified rows to the
* database.
*/
public void update(String[] name, String[] vorname, int[] matrikelnr)
throws SQLException
{
final String query =
"SELECT name, vorname, matrikelnr FROM studenten";
Statement stmt;
ResultSet rs;
stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
rs = stmt.executeQuery(query);
for (int i = 0; i < name.length; i++) {
rs.beforeFirst();
while (rs.next()) {
if (rs.getInt(3) == matrikelnr[i]) {
rs.updateString(1, name[i]);
rs.updateString(2, vorname[i]);
292
KAPITEL 18. JAVA DATABASE CONNECTIVITY (JDBC)
rs.updateRow();
break;
}
}
}
stmt.close();
}
public static void main(String[] args)
throws Exception
{
final String url
= "jdbc:mysql://localhost/infoc?user=infoc&password=$infoc!";
String[] name
= { "Kafka", "Mueller", "Schreiner", "Kaufmann" };
String[] vorname = { "Franz", "Martin", "Corina", "Claudia" };
int[] matrikelnr = { 123456, 234567, 345678, 456789 };
Studenten s = new Studenten();
s.connect(url);
System.out.println("\nOriginal state of the database:\n");
s.show();
s.startTransaction();
s.remove(matrikelnr);
System.out.println("\nIntermediate state of the database:\n");
s.abortTransaction();
s.show();
s.insert(name, vorname, matrikelnr);
System.out.println("\nFinal state of the database:\n");
s.show();
s.disconnect();
}
}
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“.
”
(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“ benutzen?
”
(k) Erklären Sie die Begriffe Komponenten- und Verteilungsdiagramm.
293
294
KAPITEL 18. JAVA DATABASE CONNECTIVITY (JDBC)
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?
(h) Erläutern Sie den Unterschied zwischen statischen und dynamischen Software-Metriken.
(i) Was versteht man unter dem Fan-in bzw. dem Fan-out einer Funktion oder Methode?
(j) Auf welcher Basis ist die zyklomatische Komplexität definiert?
(k) Welche Konstrukte eines Quelltextes werden bei den Halstead-Metriken analysiert?
(l) Nennen Sie typische Indikatoren für schlecht riechenden“ Quelltext.
”
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?
(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?
18.3. SQL ZUGRIFFE AUS JAVA MIT JDBC
295
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. Java Beans
(a) Was versteht man unter einer Software-Komponente? Wie verwendet man Software-Komponenten
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?
11. 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?
296
KAPITEL 18. JAVA DATABASE CONNECTIVITY (JDBC)
(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?
12. Extensible Markup Language (XML)
(a) Aus welchen grundlegenden Strukturelementen bestehen XML-Dokumente?
(b) Was versteht man unter einem wohlgeformten XML-Dokument? In welchem Verhältnis steht
ein wohlgeformtes XML-Dokument zu einem validierten XML-Dokument?
(c) Vergleichen Sie Document Type Definitions (DTDs) mit XML Schemas? Nennen Sie Beispiele,
die sich im mächtigeren Ansatz sehr wohl ausdrücken lassen, aber nicht im weniger mächtigern
Ansatz darstellbar sind.
(d) Was versteht man unter dem Document Object Model (DOM)?
13. 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 Stub-Prozeduren?
(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.
14. 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?
(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?
15. Remote Method Invocation (RMI)
(a) Vergleichen Sie Java RMI mit CORBA. Was sind die wesentlichen Vor- und Nachteile? Was
sind die Gemeinsamkeiten?
(b) Was muss getan werden, damit Methoden eine Java-Klasse via RMI von anderen Programmen
aufgerufen werden können?
16. Java Name and Directory Interface
18.3. SQL ZUGRIFFE AUS JAVA MIT JDBC
297
(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?
17. 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.
18. Datenbankzugriffe mit JDBC
(a) Welche Grundoperationen gibt es in der Relationenalgebra?
(b) Nennen Sie eine Relation, die nicht mit Termen der Relationenalgebra berechnet werden kann.
(c) Geben Sie eine SQL-Anfrage an, die die Namen aller Studenten liefert, die in Informatik eine
Note besser oder gleich 2.0 haben.
(d) Was versteht man im Zusammenhang mit JDBC unter einem Batch? Wann sind Batches sinnvoll?
(e) Vergleichen Sie einfache Anfragen (Statement) mit vorbereiteten Anfragen (PreparedStatement).
(f) Was versteht man unter einer Transaktion und wie wird eine Transaktion in JDBC realisiert?
298
KAPITEL 18. JAVA DATABASE CONNECTIVITY (JDBC)
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] T. Bray, J. Paoli, and C. M. Sperberg-McQueen. Extensible Markup Language (XML) 1.0. W3C
Recommendation, Textuality and Netscape, Microsoft, University of Illinois, February 1998.
[5] F. P. Brooks. The Mythical Man-Month. Addison Wesley, 1975.
[6] C.J. Date. An Introduction to Database Systems. Addison-Wesley, 4 edition, 1986.
[7] T. DeMarco. Der Termin. Hanser Verlag, 1998.
[8] K. Beck E. Gamma. Junit a cook’s tour. Technical report, 1998.
[9] K. Beck E. Gamma. Junittest infected: Programmers love writing tests. Java Report, 3(7):51–58, 1998.
[10] E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design Patterns: Elements of Reusable ObjectOriented Software. Addison Wesley, 1995.
[11] Michael Herczeg. Software-Ergonomie: Grundlagen der Mensch-Computer-Kommunikation. Addison
Wesley, 1994.
[12] T. Howes. The String Representation of LDAP Search Filters. RFC 2254, Netscape Communications
Corp., December 1997.
[13] ISO. Information processing systems – Text and Office Systems – Standard Generalized Markup Language (SGML). International Standard ISO 8879, ISO, 1986.
[14] 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.
[15] G.J. Myers. The Art of Software Testing. John Wiley & Sons, 1979.
[16] Object Management Group. Unified Modeling Language Specification Version 1.3. Formal Specification, Object Management Group, March 2000.
[17] Object Management Group. Unified Modeling Language Specification Version 1.4. Formal Specification, Object Management Group, September 2001.
[18] B. Oestereich. Objektorientierte Softwareentwicklung: Analyse und Design mit der Unified Modeling
Language. Oldenbourg, 4 edition, 1998.
[19] OMG. IDL to Java Language Mapping Specification. Technical Report Revision 1.1, Object Management Group, June 2001.
[20] OMG. The Common Object Request Broker: Architecture and Specification. Technical Report Revision
2.6, Object Management Group, December 2001.
[21] R. Reißing. Extremes Programmieren. Informatik Spektrum, 23(2):118–121, 2000.
299
300
LITERATURVERZEICHNIS
[22] 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.
[23] B. Shneiderman. Designing the User Interface: Strategies for Effective Human-Computer Interaction.
Addison Wesley, 3 edition, 1997.
[24] J. Siegel. CORBA Fundamentals and Programming. Wiley & Sons, 1996.
[25] I. Sommerville. Software Engineering. Addison Wesley, 2001.
[26] P. Stevens and R. Pooley. UML - Softwareentwicklung mit Objekten und Komponenten. Addison
Wesley, 2000.
[27] Sun. Java Naming and Directory Interface Application Programming Interface (JNDI API). Technical
Report 1.2, Sun Microsystems, July 1999.
[28] K. Walrath and M. Campione. The JFC Swing Tutorial: A Guide to Constructing GUIs. Addison
Wesley, 3 edition, 1999.
[29] A. Zeller and J. Krinke. Programmierwerkzeuge. dpunkt, 2000.
Herunterladen