1.1 AWT - Sebastian Wilke

Werbung
Skript "Software-Engineering1"
< @Prof. Dr. Peter Edelmann >
1
ARCHITEKTUR GRAFISCHER BENUTZUNGSOBERFLÄCHEN ............................................... 11
1.1 AWT ........................................................................................................................................................ 11
1.1.1
Beispiel Calculator ......................................................................................................................... 11
1.1.2
AWT und Swing ............................................................................................................................. 12
1.1.2.1 AWT........................................................................................................................................... 12
1.1.2.2 Swing ......................................................................................................................................... 12
1.1.2.3 Überblick über AWT Components ............................................................................................. 13
1.1.3
Erstellen eines normalen Fensters oder eines Dialogs ................................................................. 13
1.1.3.1 Beispiel zum Erzeugen eines Frames ....................................................................................... 13
1.1.3.2 Aktionen zum Erstellen .............................................................................................................. 14
1.1.3.2.1 Anlegen eines Dialogfensters .............................................................................................. 14
1.1.3.2.2 Zuordnen eines Layoutmanagers ........................................................................................ 14
1.1.3.2.3 Einfügen von Dialogelementen ............................................................................................ 14
1.1.3.2.4 Anzeigen des Dialogfensters ............................................................................................... 15
1.1.3.3 Beispiel Anpassen eines Frames .............................................................................................. 15
1.1.4
Eventmodell ................................................................................................................................... 16
1.1.4.1 Beispiel ActionEvent .................................................................................................................. 16
1.1.4.2 Überblick .................................................................................................................................... 16
1.1.4.3 Ereignisse .................................................................................................................................. 17
1.1.4.3.1 Diagramm............................................................................................................................. 17
1.1.4.3.2 Primitive Ereignisse.............................................................................................................. 17
1.1.4.3.3 Ereignisse zur Interaktion verschiedener Komponenten ...................................................... 18
1.1.4.4 Listener ...................................................................................................................................... 18
1.1.4.4.1 Konzept ................................................................................................................................ 18
1.1.4.4.2 Listenerdiagramm ................................................................................................................ 18
1.1.4.4.3 Listener-Schnittstellen .......................................................................................................... 18
1.1.4.5 Ereignisquellen .......................................................................................................................... 19
1.1.4.5.1 Konzept ................................................................................................................................ 19
1.1.4.5.2 Ereignisquellen .................................................................................................................... 19
1.1.4.6 Aktivierung von Lauschobjekten ................................................................................................ 19
1.1.4.7 Beispiel MouseEventsDemoEventTechniken ............................................................................ 20
1.1.4.7.1 Implementation Listener durch Frame ................................................................................. 20
1.1.4.7.2 Adapter................................................................................................................................. 21
1.1.4.7.3 Anonyme Klasse .................................................................................................................. 21
1.1.4.8 Adapter Klassen ........................................................................................................................ 22
1.1.4.9 Adapter als innere Klassen ........................................................................................................ 22
1.1.4.10
Beispiele ................................................................................................................................ 23
1.1.4.10.1 Beispiel Schließen eines Frames mit Escape .................................................................... 23
1.1.4.10.1.1 Implementation Keylistener ......................................................................................... 23
1.1.4.10.1.2 Innerere Klasse und Adapter ....................................................................................... 23
1.1.4.10.1.3 Anonyme Klasse .......................................................................................................... 24
1.1.4.10.1.4 Überlagern der Event-Handler in den Komponenten................................................... 24
1.1.4.10.2 Key Events ......................................................................................................................... 26
1.1.4.10.3 Mouse Events ..................................................................................................................... 29
1.1.4.10.4 Component Events ............................................................................................................. 29
1.1.4.10.5 Focus Events...................................................................................................................... 29
1.1.5
Layoutmanager .............................................................................................................................. 29
1.1.5.1 Beispiel Layout .......................................................................................................................... 29
1.1.5.2 Konzept ..................................................................................................................................... 30
1.1.5.3 FlowLayout ................................................................................................................................ 30
1.1.5.4 GridLayout ................................................................................................................................. 31
1.1.5.5 BorderLayout ............................................................................................................................. 31
1.1.5.6 CardLayout ................................................................................................................................ 32
1.1.5.7 GridBagLayout ........................................................................................................................... 32
1.1.5.8 Null-Layout ................................................................................................................................ 32
1.1.6
Modale Dialoge .............................................................................................................................. 32
1.1.6.1 Beispiel Dialoge ......................................................................................................................... 32
1.1.6.2 Warten auf die Eingabe ............................................................................................................. 33
1.1.6.3 Beispiel VariableDialoge ............................................................................................................ 33
1.2 SWING ...................................................................................................................................................... 33
1.2.1
Beispiel Geburtsmonat .................................................................................................................. 33
1.2.2
Probleme des AWT ....................................................................................................................... 35
1.2.3
Leichtgewichtige Komponenten ..................................................................................................... 35
1.2.4
Pluggable Look-and-Feel .............................................................................................................. 35
1.2.5
Das Model-View-Controller-Prinzip ................................................................................................ 35
1.2.6
Container und Menüs .................................................................................................................... 36
1.2.6.1 Hauptfenster .............................................................................................................................. 36
1.2.6.1.1 Beispiel MDI ......................................................................................................................... 36
2
1.2.6.1.2 JFrame ................................................................................................................................. 37
1.2.6.1.2.1 Struktur .......................................................................................................................... 37
1.2.6.1.2.2 Erzeugung ..................................................................................................................... 37
1.2.6.1.2.3 Zugriff ............................................................................................................................ 37
1.2.6.1.3 JInternalFrame ..................................................................................................................... 38
1.2.6.1.3.1 Konzept ......................................................................................................................... 38
1.2.6.1.3.2 Der Desktop .................................................................................................................. 38
1.2.6.1.3.3 Die Kindfenster .............................................................................................................. 38
1.2.6.1.4 Beispiel SplashScreen ........................................................................................................ 38
1.2.6.1.5 JWindow .............................................................................................................................. 38
1.2.6.1.6 JDialog ................................................................................................................................. 39
1.2.6.1.7 Beispiel Quartal .................................................................................................................... 39
1.2.6.1.8 JOptionPane ........................................................................................................................ 39
1.2.6.1.9 JApplet ................................................................................................................................. 39
1.2.6.2 Menüs ........................................................................................................................................ 40
1.2.6.2.1 Einfache Menüs ................................................................................................................... 40
1.2.6.2.1.1 Beispiel SimpleMenue ................................................................................................... 40
1.2.6.2.1.2 Grundlagen von Swing-Menüs ...................................................................................... 41
1.2.6.2.2 Weitere Möglichkeiten .......................................................................................................... 41
1.2.6.2.2.1 Beispiel Extended Menue ( funktioniert nicht mit VisualCafe) ....................................... 41
1.2.6.2.2.2 Untermenüs ................................................................................................................... 42
1.2.6.2.2.3 Icons in Menüeinträgen ................................................................................................. 42
1.2.6.2.2.4 Checkboxes und Radiobuttons in Menüeinträgen ......................................................... 43
1.2.6.2.3 Kontextmenue ...................................................................................................................... 43
1.2.6.2.3.1 Beispiel Kontextmenue .................................................................................................. 43
1.2.6.2.3.2 Kontextmenüs ............................................................................................................... 43
1.2.7
Swing Komponenten ..................................................................................................................... 43
1.2.7.1 Einfache Swing Komponenten................................................................................................... 43
1.2.7.1.1 Beispiel Swing Components ................................................................................................ 43
1.2.7.1.2 Label und Textfelder ............................................................................................................ 48
1.2.7.1.2.1 JLabel ............................................................................................................................ 48
1.2.7.1.2.2 JTextField ...................................................................................................................... 48
1.2.7.1.2.3 JPasswordField ............................................................................................................. 48
1.2.7.1.2.4 JTextArea ...................................................................................................................... 49
1.2.7.1.3 Buttons ................................................................................................................................. 49
1.2.7.1.3.1 JButton .......................................................................................................................... 49
1.2.7.1.3.2 JCheckBox .................................................................................................................... 49
1.2.7.1.3.3 JRadioButton ................................................................................................................. 49
1.2.7.1.4 Listen und Comboboxen ...................................................................................................... 49
1.2.7.1.4.1 JList ............................................................................................................................... 50
1.2.7.1.4.2 JComboBox ................................................................................................................... 50
1.2.7.1.5 Quasi-analoge Komponenten .............................................................................................. 50
1.2.7.1.5.1 JScrollBar ...................................................................................................................... 50
1.2.7.1.5.2 JSlider ........................................................................................................................... 51
1.2.7.2 Jtree ........................................................................................................................................... 52
1.2.7.2.1 Beispiel SelectionTree ......................................................................................................... 52
1.2.7.2.2 Bäume .................................................................................................................................. 53
1.2.7.2.3 Interfaces für das Model-View Konzept................................................................................ 53
1.2.7.2.4 Default-Implementationen der Interfaces ............................................................................. 54
1.2.7.2.5 Erzeugen eines Baumes ...................................................................................................... 55
1.2.7.2.6 Selektieren von Knoten ........................................................................................................ 55
1.2.7.2.7 Beispiel MutableTree ........................................................................................................... 56
1.2.7.2.8 Verändern der Baumstruktur ................................................................................................ 57
1.2.7.3 JTabbedPane ............................................................................................................................ 58
1.2.7.3.1 Beispiel TabbedPane ........................................................................................................... 58
1.2.7.3.2 JTabbedPane ....................................................................................................................... 59
1.3 GRAFIKEN UND BILDER ................................................................................................................................ 59
1.3.1
Grundlagen der Grafikausgabe ..................................................................................................... 59
1.3.1.1 Beispiel PrimitiveRoutines ......................................................................................................... 59
1.3.1.2 Die Methode paint...................................................................................................................... 60
1.3.1.3 Die Klasse Graphics .................................................................................................................. 60
1.3.1.4 Das grafische Koordinatensystem ............................................................................................. 60
1.3.1.5 Elementare Grafikroutinen ......................................................................................................... 61
1.3.1.5.1 Linien ................................................................................................................................... 61
1.3.1.5.2 Rechteck .............................................................................................................................. 61
1.3.1.5.3 Polygon ................................................................................................................................ 61
1.3.1.5.4 Kreis ..................................................................................................................................... 62
1.3.1.5.5 Kreisbogen ........................................................................................................................... 62
1.3.1.6 Linien und Füllmodus ................................................................................................................ 62
1.3.1.7 Kopieren und Löschen von Flächen .......................................................................................... 62
2
3
1.3.1.8 Die Clipping-Region ................................................................................................................... 63
1.3.2
Bitmaps und Animation .................................................................................................................. 63
1.3.2.1 Bitmaps ...................................................................................................................................... 63
1.3.2.1.1 Beispiel BitmapOhneRepaintPaintMechanismus ................................................................. 63
1.3.2.1.2 Laden einer Bitmap .............................................................................................................. 63
1.3.2.1.3 Die Klasse Toolkit ................................................................................................................ 64
1.3.2.1.4 Ausgeben einer Bitmap ........................................................................................................ 64
1.3.2.1.5 Die Klasse MediaTracker ..................................................................................................... 64
1.3.2.1.5.1 Problem ......................................................................................................................... 64
1.3.2.1.5.2 Lösung durch einen MediaTracker ................................................................................ 64
1.3.2.1.5.3 Beschreibungen aus der Online Hilfe ............................................................................ 65
1.3.2.1.6 Verbleibendes Problem ........................................................................................................ 66
1.3.2.1.7 Beispiel Bitmap (Mit repaint-paint Mechanismus) ................................................................ 66
1.3.2.1.8 Der repaint() – paint() Mechanismus .................................................................................... 66
1.3.2.1.8.1 Die Methode paint() ....................................................................................................... 66
1.3.2.1.8.2 Die Methode repaint() .................................................................................................... 66
1.3.2.1.8.3 Die Methode update() .................................................................................................... 67
1.3.2.1.8.4 Anmerkungen ................................................................................................................ 67
1.3.2.1.8.5 Unterschiede zwischen AWT und Swing ....................................................................... 67
1.3.2.1.8.5.1 AWT........................................................................................................................ 67
1.3.2.1.8.5.2 Swing ...................................................................................................................... 68
1.3.2.2 Animation ................................................................................................................................... 68
1.3.2.2.1 Beispiel BitmapAnimation .................................................................................................... 68
1.3.2.2.2 Konzept ................................................................................................................................ 69
1.3.2.2.3 Beispiel FlackerndesImageApplet ........................................................................................ 70
1.3.2.2.4 Reduktion des Bildschirmflackerns ...................................................................................... 70
1.3.2.2.5 Beispiel PartialRemovingImageApplet ................................................................................. 71
1.3.2.2.6 Selektives Löschen .............................................................................................................. 72
1.3.2.2.7 Beispiel DoubleBufferingImageApplet.................................................................................. 72
1.3.2.2.8 Doppelpufferung................................................................................................................... 73
1.3.2.2.9 Beispiel ExclusiveOrImageApplet ........................................................................................ 73
1.3.2.2.10 Löschen mit Exclusiv-Oder................................................................................................. 74
1.4 OBJEKTSERIALISERUNG............................................................................................................................... 74
1.4.1
Serialisierung von einfachen Objekten .......................................................................................... 74
1.4.1.1 Beispiel Einfache Serialisierung ................................................................................................ 74
1.4.1.2 Begriffsbestimmung ................................................................................................................... 75
1.4.1.3 Schreiben von Objekten ............................................................................................................ 75
1.4.1.3.1 Die Klasse ObjectOutputStream .......................................................................................... 75
1.4.1.3.2 Methoden von ObjectOutputStream ..................................................................................... 75
1.4.1.3.3 Konzept ................................................................................................................................ 75
1.4.1.3.4 Das Interface Serializable .................................................................................................... 76
1.4.1.4 Lesen von Objekten ................................................................................................................... 76
1.4.1.5 Anmerkungen ............................................................................................................................ 76
1.4.1.6 Auszug aus der Online Hilfe ...................................................................................................... 77
1.4.1.7 Nicht-serialisierte Membervariablen .......................................................................................... 77
1.4.2
Serialiserung von Graphen ............................................................................................................ 78
1.4.2.1 Beispiel Graph Serialiserung Familie ........................................................................................ 78
1.4.2.2 Objektreferenzen ....................................................................................................................... 79
1.4.2.2.1 Problem ................................................................................................................................ 79
1.4.2.2.2 Lösung ................................................................................................................................. 79
1.4.2.2.3 Warnungen .......................................................................................................................... 79
1.4.3
Serialisierung von Collections ....................................................................................................... 80
1.4.3.1 Beispiel ObjectStore .................................................................................................................. 80
1.4.3.2 Serialisierbare Standardklassen ................................................................................................ 81
1.4.4
Kopieren durch Serialisieren ......................................................................................................... 81
1.4.4.1 Beispiel Kopieren durch Serialisierung ...................................................................................... 82
1.4.4.2 Serialisieren in Byte Array ......................................................................................................... 83
1.5 EXCEPTION HANDLING ................................................................................................................................ 83
1.5.1
Behandlung von Exceptions .......................................................................................................... 83
1.5.1.1 Beispiel Simple Exception ......................................................................................................... 83
1.5.1.2 Die try-catch-Anweisung ............................................................................................................ 84
1.5.1.3 Das Fehlerobjekt ....................................................................................................................... 84
1.5.1.4 Die Fehlerklassen von Java ...................................................................................................... 84
1.5.1.5 Fortfahren nach Fehlern ............................................................................................................ 85
1.5.1.6 Beispiel MultipleCatchException................................................................................................ 86
1.5.1.7 Mehrere catch-Klauseln ............................................................................................................. 86
1.5.1.8 Die finally-Klausel ...................................................................................................................... 86
1.5.2
Weitergabe von Exceptions ........................................................................................................... 87
1.5.2.1 Beispiel Divide ........................................................................................................................... 87
1.5.2.2 Die catch-or-throw-Regel ........................................................................................................... 88
3
4
1.5.2.3 Auslösen von Ausnahmen ......................................................................................................... 88
1.6 GLOBALISIERUNG ....................................................................................................................................... 88
1.6.1
Problem ......................................................................................................................................... 88
1.6.2
Beispiel LocalizedClock ................................................................................................................. 88
1.6.3
Die Klasse Locale .......................................................................................................................... 89
1.6.4
Die Klasse DataFormat ................................................................................................................. 90
1.6.5
Beispiel SimpleGlobalMenue ......................................................................................................... 90
1.6.6
Die Klasse ResourceBundle .......................................................................................................... 91
2
DIE UNIFIED MODELING LANGUAGE (UML) UND DER RATIONAL UNIFIED SOFTWARE
DEVELOPMENT PROCESS (RUP) ........................................................................................................ 91
2.1 UML IM ÜBERBLICK .................................................................................................................................... 91
2.1.1
Dokumentation .............................................................................................................................. 91
2.1.1.1 Wozu dient die Dokumentation ................................................................................................. 91
2.1.1.2 Anmerkungen zur Dokumentation ............................................................................................. 91
2.1.2
Geschichte der UML ...................................................................................................................... 92
2.1.3
Views ............................................................................................................................................. 92
2.2 RUP IM ÜBERBLICK .................................................................................................................................... 93
2.3 AKTIVITÄTEN DES RUP UND DIE ZUGEHÖRIGEN UML DIAGRAMME ................................................................... 93
2.3.1
Business Modeling ........................................................................................................................ 93
2.3.2
Requirements ................................................................................................................................ 94
2.3.2.1 Erstellen einer Vision ................................................................................................................. 94
2.3.2.1.1 Beispiel Getränkeautomat .................................................................................................... 94
2.3.2.1.2 Beispiel Bibliothek ................................................................................................................ 94
2.3.2.1.3 Konzept ................................................................................................................................ 94
2.3.2.2 Aufnehmen der Stakeholder Requests ...................................................................................... 94
2.3.2.3 Erstellen eines Use Case Models .............................................................................................. 95
2.3.2.3.1 Beispiel Getränkeautomat .................................................................................................... 95
2.3.2.3.2 Beispiel Bibliothek ................................................................................................................ 95
2.3.2.3.3 Überblick .............................................................................................................................. 96
2.3.2.3.4 System ................................................................................................................................. 96
2.3.2.3.5 Actor ..................................................................................................................................... 96
2.3.2.3.5.1 Charakterisierung .......................................................................................................... 96
2.3.2.3.5.2 Beziehungen zwischen Actors....................................................................................... 97
2.3.2.3.6 Use Case ............................................................................................................................. 97
2.3.2.3.6.1 Charakterisierung .......................................................................................................... 97
2.3.2.3.6.2 Spezifikation .................................................................................................................. 97
2.3.2.3.6.3 Beziehungen zwischen Use Cases ............................................................................... 98
2.3.2.3.7 Erweiterung der UML ........................................................................................................... 98
2.3.2.3.8 Wie findet man Actors und Use Cases ................................................................................ 98
2.3.2.4 Erstellen von Prototypen für User Interfaces ............................................................................. 99
2.3.2.4.1 Beispiel Bibliothek ................................................................................................................ 99
2.3.2.4.2 Getränkeautomat ............................................................................................................... 100
2.3.2.5 Festlegen der Supplementary Requirements .......................................................................... 100
2.3.2.5.1 Beispiel Bibliothek .............................................................................................................. 100
2.3.2.5.2 Beispiel Getränkeautomat .................................................................................................. 100
2.3.2.6 Festlegen der Supplementary Requirements .......................................................................... 101
2.3.2.6.1 Beispiel Bibliothek .............................................................................................................. 101
2.3.2.6.2 Beispiel Getränkeautomat .................................................................................................. 101
2.3.2.6.3 Fragenkatalog für Supplementary Requirements (aus Industrieprojekt für
Bildverarbeitung) ................................................................................................................................. 101
2.3.2.6.3.1 Auslieferungstermine/Zeitresourcen ............................................................................ 101
2.3.2.6.3.2 Kostengrenze/Budget/Geldresourcen ......................................................................... 101
2.3.2.6.3.3 Nationale oder Internationale Regularien .................................................................... 101
2.3.2.6.3.4 Conditions ................................................................................................................... 101
2.3.2.6.3.5 Produktsupport ............................................................................................................ 101
2.3.2.6.3.6 Juristische Anforderungen ........................................................................................... 101
2.3.2.6.3.7 Technologische Randbedingungen für die Lösung (Solution Constrains) .................. 101
2.3.2.6.3.8 Anforderungen bzgl. Datenvolumen ............................................................................ 102
2.3.2.6.3.9 Zeitanforderungen/Performance ................................................................................. 102
2.3.2.6.3.10 Anforderungen an die Präzision des Systems ........................................................... 102
2.3.2.6.3.11 Lastanforderungen .................................................................................................... 102
2.3.2.6.3.12 Anforderungen an Fehlertoleranz, Robustheit des Systems ..................................... 102
2.3.2.6.3.13 Anforderungen an die Benutzbarkeit ......................................................................... 102
2.3.2.6.3.14 Anforderungen an die technische Sicherheit bzgl. Anwender und collaborierende
Hardware
102
2.3.2.6.3.15 Anforderungen an die Datensicherheit ...................................................................... 102
2.3.2.6.3.16 Anforderungen an die Interoperabilität mit anderen Systemen/
Installationsumgebung (Implementation Environment) ................................................................... 102
4
5
2.3.2.6.3.17 Anforderungen an die Dokumentation (Benutzer+Wartung) ..................................... 103
2.3.2.6.3.18 Anforderungen an die Wartbarkeit ............................................................................ 103
2.3.2.6.3.19 Anforderungen an die Portabilität .............................................................................. 103
2.3.2.7 Erstellen einer Liste für mögliche Anforderungen .................................................................... 103
2.3.2.7.1 Beispiel Bibliothek .............................................................................................................. 103
2.3.2.7.2 Beispiel Getränkeautomat .................................................................................................. 103
2.3.2.8 Erstellen eines Glossary .......................................................................................................... 103
2.3.2.8.1 Beispiel Bibliothek .............................................................................................................. 103
2.3.2.8.2 Beispiel Getränkeautomat .................................................................................................. 103
2.3.2.9 Beispiel Bankautomat .............................................................................................................. 104
2.3.3
Analyse & Design ........................................................................................................................ 104
2.3.3.1 Identifizierung von Klassen und Objekten ............................................................................... 104
2.3.3.1.1 Beispiel Bibliothek .............................................................................................................. 104
2.3.3.1.2 Beispiel Getränkeautomat .................................................................................................. 104
2.3.3.1.3 Grundlegende Probleme .................................................................................................... 105
2.3.3.1.4 Studieren des Problem Domains ....................................................................................... 105
2.3.3.1.4.1 Entity Class ................................................................................................................. 106
2.3.3.1.4.2 Boundary Class ........................................................................................................... 106
2.3.3.1.4.3 Control Class ............................................................................................................... 106
2.3.3.1.5 Studium der sprachlichen Beschreibung des Problems .................................................... 106
2.3.3.1.6 Studium existierender Software ......................................................................................... 107
2.3.3.2 Erstellen eines Class Diagrams ............................................................................................... 107
2.3.3.2.1 Beispiel Getränkeautomat .................................................................................................. 107
2.3.3.2.2 Beispiel Bibliothek .............................................................................................................. 107
2.3.3.2.3 Ziel ..................................................................................................................................... 107
2.3.3.2.4 UML Darstellung der Klassen ............................................................................................ 107
2.3.3.2.5 Identifizieren der Beziehungen zwischen Klassen ............................................................. 108
2.3.3.2.5.1 Association .................................................................................................................. 108
2.3.3.2.5.1.1 Allgemein .............................................................................................................. 108
2.3.3.2.5.1.2 Aggregation .......................................................................................................... 108
2.3.3.2.5.1.3 Composition Aggregation ..................................................................................... 109
2.3.3.2.5.2 Realization ................................................................................................................... 109
2.3.3.2.5.3 Generalization ............................................................................................................. 109
2.3.3.2.5.4 Dependency ................................................................................................................ 109
2.3.3.2.6 Templates .......................................................................................................................... 110
2.3.3.3 Weitere Beispiele .................................................................................................................... 110
2.3.3.4 Erstellen der Use Case Realisierungen ................................................................................... 112
2.3.3.4.1 Beispiel Getränkeautomat .................................................................................................. 112
2.3.3.4.2 Beispiel Bibliothek .............................................................................................................. 112
2.3.3.4.3 Erstellen eines Use Case spezifischen Class Diagrams.................................................... 112
2.3.3.4.4 Erstellen eines Object Diagrams ........................................................................................ 112
2.3.3.4.4.1 Konzept ....................................................................................................................... 112
2.3.3.4.4.2 Associations ................................................................................................................ 112
2.3.3.4.5 Erstellen eines Collaboration Diagram ............................................................................... 113
2.3.3.4.6 Erstellen eines Sequence Diagrams .................................................................................. 113
2.3.3.5 Spezifizierung der Klassen ...................................................................................................... 114
2.3.3.5.1 Beispiel Getränkeautomat .................................................................................................. 114
2.3.3.5.2 Beispiel Bibliothek .............................................................................................................. 114
2.3.3.5.3 Identifizieren von Elementfunktionen und Attributen .......................................................... 114
2.3.3.5.4 Beschreibung der Verantwortlichkeiten der Klassen .......................................................... 116
2.3.3.5.5 Erstellen der Beschreibungen der Schnittstellenfunktion ................................................... 116
2.3.3.5.6 Updaten der Beziehungen zwischen den Klassen ............................................................. 117
2.3.3.5.7 Statechart Diagram ............................................................................................................ 117
2.3.3.5.7.1 Beispiel Getränkeautomat ........................................................................................... 117
2.3.3.5.7.2 Beispiel Bibliothek ....................................................................................................... 117
2.3.3.5.7.3 Konzept ....................................................................................................................... 117
2.3.3.5.7.4 States .......................................................................................................................... 117
2.3.3.5.7.5 Events ......................................................................................................................... 118
2.3.3.5.7.6 Transitions ................................................................................................................... 118
2.3.3.5.8 Activity Diagram ................................................................................................................. 118
2.3.3.5.8.1 Beispiel Getränkeautomat ........................................................................................... 119
2.3.3.5.8.2 Konzept ....................................................................................................................... 119
2.3.3.5.9 Identifizierung aktiver Klassen ........................................................................................... 119
2.3.3.6 Erstellen eines Package Diagrams .......................................................................................... 119
2.3.3.6.1 Beispiel Getränkeautomat .................................................................................................. 119
2.3.3.6.2 Beispiel Bibliothek .............................................................................................................. 120
2.3.3.6.3 Packages ........................................................................................................................... 120
2.3.3.6.4 Beziehungen zwischen Packages ...................................................................................... 121
2.3.3.6.5 Zugriffssteuerung ............................................................................................................... 121
2.3.3.7 Erstellen des Deployment Diagrams ....................................................................................... 121
5
6
2.3.3.7.1 Beispiel Getränkeautomat .................................................................................................. 121
2.3.3.7.2 Beispiel Bibliothek .............................................................................................................. 122
2.3.3.7.3 Konzept .............................................................................................................................. 123
2.3.3.8 Unterschied zwischen Analyse und Design ............................................................................. 123
2.3.3.8.1 Analyse .............................................................................................................................. 123
2.3.3.8.2 Design ................................................................................................................................ 124
2.3.3.9 Weitere Beispiele .................................................................................................................... 124
2.3.4
Implementation ............................................................................................................................ 128
2.3.4.1 Erstellen des Component Diagrams ........................................................................................ 128
2.3.4.1.1 Beispiel Getränkeautomat .................................................................................................. 128
2.3.4.1.2 Beispiel Bibliothek .............................................................................................................. 128
2.3.4.1.3 Konzept .............................................................................................................................. 128
2.3.4.2 Erstellen des Integration Build Plan ......................................................................................... 129
2.3.4.2.1 Beispiel Getraenkeautomat ................................................................................................ 129
2.3.4.2.2 Bottom Up Integration ........................................................................................................ 129
2.3.4.3 Implementieren der Components ............................................................................................ 131
2.3.4.3.1 Ziel ..................................................................................................................................... 131
2.3.4.3.2 Programmierregeln ............................................................................................................ 131
2.3.4.3.2.1 Java Code Conventions (Selbststudium) .................................................................... 131
2.3.4.3.3 Round Trip Engineering ..................................................................................................... 146
2.3.4.3.3.1 Beispiel Bank ............................................................................................................... 146
2.3.4.3.3.2 Beispiel Wertpapier ..................................................................................................... 146
2.3.4.3.3.3 Sinn und Zweck ........................................................................................................... 148
2.3.4.3.3.4 Tutorial für Round Trip Engineering mit Rational Rose ............................................... 148
2.3.4.3.3.4.1 Grundeinstellungen............................................................................................... 148
2.3.4.3.3.4.1.1 Setzen des CLASSPATH ............................................................................... 148
2.3.4.3.3.4.1.2 Anlegen eines Projektverzeichnisses ............................................................ 148
2.3.4.3.3.4.1.3 Starten von Rose ........................................................................................... 148
2.3.4.3.3.4.1.4 Hinzufügen des Projektverzeichnisses zum CLASSPATH ............................ 149
2.3.4.3.3.4.1.5 Speichern des Rose Projektes ....................................................................... 149
2.3.4.3.3.4.2 Vorgehensweise am Beispiel HelloWorld ............................................................. 149
2.3.4.3.3.4.2.1 LogicalView: Erzeugen der Start Klasse ........................................................ 149
2.3.4.3.3.4.2.2 Erzeugen des Codes der Klasse.................................................................... 149
2.3.4.3.3.4.2.3 Bearbeiten des Codes ................................................................................... 150
2.3.4.3.3.4.2.4 ReverseEngineering der HelloWorld Klasse .................................................. 150
2.3.4.3.3.4.2.5 Erzeugen von HelloWorldGUI ........................................................................ 151
2.3.4.3.3.4.2.6 Erzeugen des Codes der Klasse.................................................................... 152
2.3.4.3.3.4.2.7 Bearbeiten des Codes ................................................................................... 152
2.3.4.3.3.4.2.8 ReverseEngineering der HelloWorldGUI Klasse ........................................... 152
2.3.4.4 Integrieren des Systems .......................................................................................................... 153
2.3.5
Test .............................................................................................................................................. 153
2.3.5.1 Beispiel Klassentest MyMath ................................................................................................... 153
2.3.5.1.1 BlackBoxTest ..................................................................................................................... 153
2.3.5.1.1.1 Testling Interface ......................................................................................................... 153
2.3.5.1.1.2 Testapplikation ............................................................................................................ 153
2.3.5.1.1.3 Tracer .......................................................................................................................... 154
2.3.5.1.1.4 TestController .............................................................................................................. 155
2.3.5.1.1.5 Testprozedur ............................................................................................................... 156
2.3.5.1.1.6 Einige Testfälle ............................................................................................................ 156
2.3.5.1.1.6.1 Test calcDurch() ................................................................................................... 156
2.3.5.1.1.6.1.1 Testfall Standarddivision ................................................................................ 156
2.3.5.1.1.6.1.1.1 Voraussetzungen .................................................................................... 156
2.3.5.1.1.6.1.1.2 Eingabe ................................................................................................... 156
2.3.5.1.1.6.1.1.3 Erwartete Ausgabe .................................................................................. 157
2.3.5.1.1.6.1.1.4 Ausgabe .................................................................................................. 157
2.3.5.1.1.6.1.2 Testfall Division durch negative Zahl ............................................................. 157
2.3.5.1.1.6.1.2.1 Voraussetzungen .................................................................................... 157
2.3.5.1.1.6.1.2.2 Eingabe ................................................................................................... 157
2.3.5.1.1.6.1.2.3 Erwartete Ausgabe .................................................................................. 157
2.3.5.1.1.6.1.2.4 Ausgabe .................................................................................................. 157
2.3.5.1.1.6.1.3 Testfall Division durch Null ............................................................................. 157
2.3.5.1.1.6.1.3.1 Voraussetzungen .................................................................................... 157
2.3.5.1.1.6.1.3.2 Eingabe ................................................................................................... 157
2.3.5.1.1.6.1.3.3 Erwartete Ausgabe .................................................................................. 157
2.3.5.1.1.6.1.3.4 Ausgabe .................................................................................................. 157
2.3.5.1.1.6.2 Test calcPotenz() .................................................................................................. 158
2.3.5.1.1.6.2.1 Testfall Standard ............................................................................................ 158
2.3.5.1.1.6.2.1.1 Voraussetzungen .................................................................................... 158
2.3.5.1.1.6.2.1.2 Eingabe ................................................................................................... 158
2.3.5.1.1.6.2.1.3 Erwartete Ausgabe .................................................................................. 158
6
7
2.3.5.1.1.6.2.1.4 Ausgabe .................................................................................................. 158
2.3.5.1.1.6.2.2 Testfall Negativer Exponent ........................................................................... 158
2.3.5.1.1.6.2.2.1 Voraussetzungen .................................................................................... 158
2.3.5.1.1.6.2.2.2 Eingabe ................................................................................................... 158
2.3.5.1.1.6.2.2.3 Erwartete Ausgabe .................................................................................. 158
2.3.5.1.1.6.2.2.4 Ausgabe .................................................................................................. 158
2.3.5.1.1.6.2.3 Testfall Exponent Null .................................................................................... 158
2.3.5.1.1.6.2.3.1 Voraussetzungen .................................................................................... 159
2.3.5.1.1.6.2.3.2 Eingabe ................................................................................................... 159
2.3.5.1.1.6.2.3.3 Erwartete Ausgabe .................................................................................. 159
2.3.5.1.1.6.2.3.4 Ausgabe .................................................................................................. 159
2.3.5.1.2 WhiteBoxTest ..................................................................................................................... 159
2.3.5.1.2.1 Testling ........................................................................................................................ 159
2.3.5.1.2.2 Test Log ...................................................................................................................... 160
2.3.5.1.2.3 Kontrollflussgraph ........................................................................................................ 162
2.3.5.2 Beispiel für Test Guidelines ..................................................................................................... 163
2.3.5.2.1 Klassentest ........................................................................................................................ 163
2.3.5.2.1.1 Ziel............................................................................................................................... 163
2.3.5.2.1.2 Zu testende Klassen .................................................................................................... 163
2.3.5.2.1.3 Black Box Test (Funktionaler Test) ............................................................................. 163
2.3.5.2.1.3.1 Ziel ........................................................................................................................ 163
2.3.5.2.1.3.2 Definition der Testfälle .......................................................................................... 163
2.3.5.2.1.3.2.1 Allgemeine Hinweise ...................................................................................... 163
2.3.5.2.1.3.2.2 Äquivalenzklassenbildung .............................................................................. 163
2.3.5.2.1.3.2.3 Grenzwertanalyse .......................................................................................... 164
2.3.5.2.1.3.2.4 Test spezieller Werte (special values testing)................................................ 165
2.3.5.2.1.3.2.5 Zufallstest ....................................................................................................... 165
2.3.5.2.1.3.2.6 Ableitung von Testfällen aus State Charts ..................................................... 165
2.3.5.2.1.4 White Box Test (Strukturtest, Glass Box Test) ............................................................ 165
2.3.5.2.1.4.1 Ziel ........................................................................................................................ 165
2.3.5.2.1.4.2 Definition der Testfälle beim Kontrollflussorientierte Strukturtestverfahren .......... 165
2.3.5.2.1.4.2.1 Anweisungsüberdeckung ............................................................................... 165
2.3.5.2.1.4.2.2 Zweigüberdeckung ......................................................................................... 166
2.3.5.2.1.4.2.3 Pfadüberdeckung ........................................................................................... 166
2.3.5.2.1.4.2.4 Bedingungsüberdeckung ............................................................................... 166
2.3.5.2.2 Integrationstest .................................................................................................................. 167
2.3.5.2.2.1 Ziel............................................................................................................................... 167
2.3.5.2.2.2 Für welche Gruppen von Klassen ist ein Integrationstest durchzuführen ................... 167
2.3.5.2.2.3 Definition der Testfälle ................................................................................................. 167
2.3.5.2.3 Regressionstest ................................................................................................................. 167
2.3.5.2.4 Systemtest ......................................................................................................................... 167
2.3.5.2.4.1 Ziel............................................................................................................................... 167
2.3.5.2.4.2 Definition der Testfälle ................................................................................................. 167
2.3.5.2.5 Abnahmetest (Acceptance Test) ........................................................................................ 168
2.3.5.2.6 Testdurchführung ............................................................................................................... 168
2.3.5.2.6.1 Testumgebung ............................................................................................................ 168
2.3.5.2.6.2 Durchführung eines kombinierten White und Black Box Test für Klassen .................. 169
2.3.5.2.6.3 Durchführung eines Integrationstests für eine Gruppe von Klassen ........................... 169
2.3.5.2.6.4 Wer führt den Test durch ............................................................................................ 170
2.3.5.2.6.5 Testendekriterium ........................................................................................................ 170
2.3.5.2.6.6 Verwaltung der gefundenen Fehler ............................................................................. 170
2.3.5.2.6.7 Verwaltung der Testumgebung und Testdaten ........................................................... 170
2.3.5.3 Erstellen eines Test Case ........................................................................................................ 170
2.3.5.4 Erstellen einer Test Prozedur .................................................................................................. 171
2.3.5.5 Erstellen eines Test Log .......................................................................................................... 171
2.3.5.6 Erstellen einer Test Suite (vgl. JUnit) ...................................................................................... 171
2.3.5.7 Unit Tests mit dem Test-Tool JUnit ......................................................................................... 171
2.3.5.7.1 Sinn und Zweck .................................................................................................................. 171
2.3.5.7.2 Download und Installation (Selbststudium) ........................................................................ 171
2.3.5.7.3 Ausführung der Tests mittels TestRunner .......................................................................... 172
2.3.5.7.4 Wichtige Klassen des Frameworks .................................................................................... 173
2.3.5.7.5 SingleTestCase .................................................................................................................. 173
2.3.5.7.5.1 Beispiel EuroTestSingleTestCase ............................................................................... 173
2.3.5.7.5.1.1 Requirements ....................................................................................................... 173
2.3.5.7.5.1.2 Schreiben des Testfalles in Form der Testmethode ............................................. 173
2.3.5.7.5.1.3 Schreiben der zu testenden Methode ................................................................... 174
2.3.5.7.5.2 Assert .......................................................................................................................... 174
2.3.5.7.5.3 AssertionFailedError .................................................................................................... 175
2.3.5.7.6 MultipleTestCases .............................................................................................................. 176
2.3.5.7.6.1 Beispiel EuroTestMultipleTestCases ........................................................................... 176
7
8
2.3.5.7.6.2 Definition von mehreren Testfällen in der Klasse EuroTest ........................................ 176
2.3.5.7.6.3 Generierung von EuroTest Instanzen für jeden Testfall .............................................. 177
2.3.5.7.7 TestCaseTree .................................................................................................................... 178
2.3.5.7.7.1 Beispiel AllTests .......................................................................................................... 178
2.3.5.7.7.2 TestSuite ..................................................................................................................... 178
2.3.5.7.8 Exceptions.......................................................................................................................... 178
2.3.5.7.8.1 Testen von Exceptions ................................................................................................ 179
2.3.5.7.8.2 Unerwartete Exceptions .............................................................................................. 179
2.3.5.7.9 Implementationsdetails des Frameworks ........................................................................... 179
2.3.6
Configuration&Change Management .......................................................................................... 184
2.3.6.1 Beispiel für Configuration Management Guidelines................................................................. 184
2.3.6.1.1 Software-Element ............................................................................................................... 184
2.3.6.1.2 Version ............................................................................................................................... 184
2.3.6.1.3 Checkin-Checkout Modell .................................................................................................. 184
2.3.6.1.4 Konfiguration (Lineup) und Release (Baseline) ................................................................. 185
2.3.6.1.5 Configuration Specification (Konfigurations-Identifikationsdokument KID) ........................ 185
2.3.6.1.6 Single Stream Versioning................................................................................................... 185
2.3.6.1.7 Varianten von Versionen .................................................................................................... 185
2.3.6.1.8 Branches ............................................................................................................................ 186
2.3.6.1.9 Parallel Stream Versioning................................................................................................. 186
2.3.6.1.10 File based System ............................................................................................................ 187
2.3.6.1.11 View based System .......................................................................................................... 187
2.3.6.1.12 Zustandsübergänge bei der Bearbeitung eines Software-Elementes .............................. 187
2.3.6.2 Beispiel Configuration Management mit CVS (Tutorial, Selbststudium) .................................. 187
2.3.6.2.1 Struktur des Projektes ........................................................................................................ 187
2.3.6.2.2 Allgemeine Vorbereitungen ................................................................................................ 188
2.3.6.2.2.1 Download von CVS ..................................................................................................... 188
2.3.6.2.2.2 Installation von CVS .................................................................................................... 188
2.3.6.2.2.3 Setzen des Classpath für den Taschenrechner .......................................................... 188
2.3.6.2.2.4 Anlegen des Projektverzeichnisses ............................................................................. 188
2.3.6.2.2.5 Anlegen des Repository .............................................................................................. 189
2.3.6.2.2.6 Setzen der CVSROOT Variablen ................................................................................ 189
2.3.6.2.2.7 Sonderbehandlung von Binärdateien einstellen .......................................................... 189
2.3.6.2.3 Erstellen des Main Trunk durch den Projektleiter .............................................................. 190
2.3.6.2.3.1 Anlegen des Projektes ................................................................................................ 190
2.3.6.2.3.2 Erzeugen des Package rechner .................................................................................. 191
2.3.6.2.3.3 Erzeugen des Interfaces ............................................................................................. 191
2.3.6.2.3.4 Erzeugen einer Dummy Implementation des Interfaces ............................................. 192
2.3.6.2.3.5 Aufnehmen der Dateien in das Projekt ........................................................................ 192
2.3.6.2.3.6 Einchecken der Dateien .............................................................................................. 192
2.3.6.2.3.7 Labeln der Konfiguration ............................................................................................. 192
2.3.6.2.3.8 Erzeugen das Package gui und der Datei TaschenRechnerGUI ............................... 192
2.3.6.2.3.9 Erzeugen der Main Klasse TaschenRechner.java ...................................................... 193
2.3.6.2.3.10 Anlegen der Branches ............................................................................................... 193
2.3.6.2.3.11 Zugriff auf zentrales Repository ermöglichen ............................................................ 194
2.3.6.2.4 Entwickeln der GUI durch Entwickler1 ............................................................................... 194
2.3.6.2.4.1 Auschecken der Arbeitskopie vom Branch Entwickler-1-GUI...................................... 194
2.3.6.2.4.2 Codieren der GUI Klasse ............................................................................................ 194
2.3.6.2.4.3 Einchecken der Arbeitskopie in den Branch Entwickler-1-GUI.................................... 197
2.3.6.2.5 Entwickeln des Rechners durch Entwickler2 ..................................................................... 197
2.3.6.2.5.1 Auschecken der Arbeitskopie vom Branch Entwickler-2-Rechner .............................. 197
2.3.6.2.5.2 Codieren der Rechnerlogik – Datei TaschenRechnerImpl.java................................... 197
2.3.6.2.5.3 Codieren der GUI Klasse als Testtreiber ..................................................................... 198
2.3.6.2.5.4 Einchecken der Arbeitskopie in den Branch Entwickler-2-Rechner ............................ 199
2.3.6.2.6 Zusammenführen (Mergen) der Entwicklungsstränge durch den Projektleiter ................. 199
2.3.6.2.7 Darstellung der Entwicklungsstränge ................................................................................. 200
2.3.6.3 Beispiel Configuration Management Unterstützung durch Rational Rose ............................... 202
2.3.6.3.1 Controlled Units.................................................................................................................. 202
2.3.6.3.2 Verwalten von controlled units ........................................................................................... 202
2.3.6.4 Bereitstellen eines Project Repository ..................................................................................... 203
2.3.6.5 Erstellen von Base Lines ......................................................................................................... 203
2.3.6.6 Erstellen von Change Requests .............................................................................................. 203
2.3.7
Deployment ................................................................................................................................. 203
2.3.7.1 Erstellen von Training Material ................................................................................................ 203
2.3.7.2 Erstellen von User Support Material ........................................................................................ 204
2.3.7.3 Erstellen von Release Notes ................................................................................................... 204
2.3.7.4 Ausliefern an ß-Tester ............................................................................................................. 204
2.3.8
Project Management ................................................................................................................... 204
2.3.8.1 Erstellen einer Risk List ........................................................................................................... 204
2.3.8.2 Erstellen eines Iteration Plan ................................................................................................... 204
8
9
2.3.8.3 Aquire Staff .............................................................................................................................. 204
2.3.8.4 Erstellen von Work Orders ...................................................................................................... 204
2.3.9
Environment ................................................................................................................................ 205
2.3.9.1 Erstellen von Templates .......................................................................................................... 205
2.3.9.2 Erstellen von Guidelines .......................................................................................................... 205
2.3.9.3 Bereitstellen von Tools ............................................................................................................ 205
2.4 PHASEN DES RUP.................................................................................................................................... 205
2.4.1
Zitate zum Begriff Komplexität ..................................................................................................... 205
2.4.2
Die Struktur komplexer Systeme ................................................................................................. 205
2.4.3
Grundlegende Konzepte des RUP .............................................................................................. 206
2.4.3.1 Use Case Driven...................................................................................................................... 206
2.4.3.2 Architecture Centric ................................................................................................................. 207
2.4.3.3 Iterative and Incremental ......................................................................................................... 207
2.4.3.4 Strukturierung des Entwicklungsprozesses ............................................................................. 207
2.4.3.4.1 Phasen-Aktivitäten Diagramm ............................................................................................ 207
2.4.3.4.2 Disciplines .......................................................................................................................... 208
2.4.3.4.3 Zyklen................................................................................................................................. 208
2.4.3.4.4 Phasen ............................................................................................................................... 208
2.4.3.4.5 Milestones .......................................................................................................................... 209
2.4.4
Phasen des RUP und zugehörige primäre Aktivitäten................................................................. 209
2.4.4.1 Inception .................................................................................................................................. 209
2.4.4.2 Elaboration .............................................................................................................................. 210
2.4.4.3 Construction ............................................................................................................................ 211
2.4.4.4 Transition ................................................................................................................................. 212
3
KOMPONENTENBASIERTE SOFTWAREENTWICKLUNG AM BEISPIEL VON ENTERPRISE
JAVA BEANS ....................................................................................................................................... 212
3.1 LITERATUR .............................................................................................................................................. 212
3.2 INTRODUCTION ......................................................................................................................................... 212
3.2.1
Distributed Object Architectures .................................................................................................. 213
3.2.1.1 Three Tier Architecture ............................................................................................................ 213
3.2.1.2 Stub und Skeleton ................................................................................................................... 213
3.2.1.3 Interface und Server Technologie am vereinfachten Modell ................................................... 214
3.2.2
Server-Side Components (Selbststudium) .................................................................................. 215
3.2.3
Component Transaction Monitors (Selbststudium)...................................................................... 216
3.3 ARCHITECTURAL OVERVIEW ...................................................................................................................... 217
3.3.1
Beispiel CruiseBean .................................................................................................................... 217
3.3.2
Entity beans ................................................................................................................................. 218
3.3.3
Beispiel TravelAgentBean ........................................................................................................... 218
3.3.4
Session beans ............................................................................................................................. 220
3.3.5
The bean container contract ........................................................................................................ 221
3.3.6
Beispiel Cruise Interface and Class CruiseHome ........................................................................ 221
3.3.7
Classes and Interfaces ................................................................................................................ 221
3.3.8
Implementation des remote interface .......................................................................................... 222
3.3.9
Implementation des home interface ............................................................................................ 223
3.3.10
Beispiel deployment descriptor .................................................................................................... 224
3.3.11
Deployment descriptors and JAR files ......................................................................................... 227
3.4 EXAMPLE CRUISE ..................................................................................................................................... 228
3.5 CONTAINER AND BEAN MANAGED PERSISTENCE OF ENTITY BEANS ............................................................... 228
3.5.1
Container Managed Persistence ................................................................................................. 228
3.5.2
Bean Managed Persistence ........................................................................................................ 228
3.6 STATEFUL AND STATELESS SESSION BEANS ................................................................................................. 228
3.6.1
Stateful session beans ................................................................................................................ 228
3.6.2
Stateless session beans .............................................................................................................. 229
3.7 RESOURCE MANAGEMENT ......................................................................................................................... 229
3.7.1
Problem ....................................................................................................................................... 229
3.7.2
Pooling......................................................................................................................................... 229
3.7.3
Swapping ..................................................................................................................................... 230
3.7.4
The Life Cycle of an Entity Bean ................................................................................................. 230
3.7.4.1 States ...................................................................................................................................... 230
3.7.4.2 Does Not Exist ......................................................................................................................... 231
3.7.4.3 Pooled ..................................................................................................................................... 231
3.7.4.4 Ready ...................................................................................................................................... 231
3.7.4.4.1 Transition from pooled to ready via creation ...................................................................... 231
3.7.4.4.2 Transition from pooled to ready via activation .................................................................... 232
3.7.4.4.3 Transition from ready to pooled via passivation ................................................................. 232
3.7.4.4.4 Transition from ready to pooled via removal ...................................................................... 232
3.7.4.4.5 Anmerkungen ..................................................................................................................... 232
3.7.5
The Life Cycle of a Stateless Session Bean ................................................................................ 233
9
10
3.7.5.1 States ...................................................................................................................................... 233
3.7.5.2 DoesNotExist ........................................................................................................................... 233
3.7.5.3 Method-Ready Pool ................................................................................................................. 233
3.7.5.3.1 Transition to the Method-Ready Pool ................................................................................. 233
3.7.5.3.2 Life in the Method-Ready Pool ........................................................................................... 234
3.7.5.3.3 Transition out of the Method-Ready Pool ........................................................................... 234
3.7.6
The Life Cycle of a Stateful Session Bean .................................................................................. 234
3.7.6.1 The Activation Mechanism ....................................................................................................... 234
3.7.6.2 States ...................................................................................................................................... 235
3.7.6.3 Does Not Exist ......................................................................................................................... 235
3.7.6.4 Method Ready ......................................................................................................................... 235
3.7.6.4.1 Transition to Method Ready ............................................................................................... 235
3.7.6.4.2 Transition to Does Not Exist ............................................................................................... 235
3.7.6.5 Passivated ............................................................................................................................... 235
3.7.6.5.1 Transition to Passivated ..................................................................................................... 235
3.7.6.5.2 Transition to Method Ready ............................................................................................... 236
3.8 PRIMARY SERVICES .................................................................................................................................. 236
3.8.1
Overview ...................................................................................................................................... 236
3.8.2
Concurrency ................................................................................................................................ 236
3.8.3
Persistence .................................................................................................................................. 237
3.8.4
Distributed Objects ...................................................................................................................... 238
3.8.5
Naming ........................................................................................................................................ 239
10
11
Software-Engineering 1
Themenüberblick:
Architektur grafischer Benutzungsoberflächen (AWT, Swing, MFC)
The Unified Modeling Language (UML)
The Unified Software Development Process (RUP)
Projekt (MS Project, CVS)
Frameworks (EJB)
1
Architektur grafischer Benutzungsoberflächen
1.1
AWT
1.1.1 Beispiel Calculator
Das Beispiel „Taschenrechner“ liefert einen Überblick über eine Reihe von Techniken.
Diese Techniken werden im Detail erst beim Studium der nachfolgenden Skriptabschnitte
verständlich.
Fenster
AWT
SimpleCalculatorAufgabe1
public class Calculator extends Frame implements ActionListener {
public static void main( String[] args ) {
Frame frame = new Calculator( "Calculator" );
frame.addWindowListener(
new WindowAdapter() {
public void windowClosing( WindowEvent e ) {
Runtime.getRuntime().exit( 0 );
}
}
);
frame.pack();
frame.setVisible( true );
}
private TextField numberField = null;
private Panel controlPanel = null;
private double leftOperand = 0.0, rightOperand = 0.0, result = 0.0;
private boolean operandBufferEmpty = true;
private char operand = '=';
public Calculator(String title) {
super(title);
setBackground( Color.red ); setForeground( Color.black );
setLayout( new BorderLayout() );
// control panel
controlPanel = new Panel();
controlPanel.setLayout( new GridLayout( 4, 4, 1, 1 ) );
// GridLayout(int rows, int cols, int hgap, int vgap)
// Creates a grid layout with the specified number of rows and columns.
String[] controlNames = { "7", "8", "9", "/",
"4", "5", "6", "*",
"1", "2", "3", "-",
"0", ".", "=", "+" };
Button button = null;
for ( int i = 0; i < controlNames.length; i++ ) {
controlPanel.add( button = new Button( controlNames[ i ] ) );
button.addActionListener( this );
}
// number field
numberField = new TextField( "0", 9 );
// insert control panel and number field in frame
add( "South", numberField ); add( "Center", controlPanel );
}
11
12
public void actionPerformed( ActionEvent e ) {
// get first character of the pressed buttons title
String arg = ( (Button) e.getSource()).getLabel();
char arg0 = arg.charAt( 0 );
// initialize operand buffer
double operandBuffer = 0.0;
switch( arg0 ) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
if ( operandBufferEmpty ) numberField.setText( arg );
else numberField.setText( numberField.getText() + arg );
operandBufferEmpty = false;
return;
case '.':
if ( operandBufferEmpty ) numberField.setText( "0." );
else numberField.setText( numberField.getText() + arg );
operandBufferEmpty = false;
return;
case '*': case '/': case '+': case '-':
try
{
operandBuffer
=
Double.valueOf(
numberField.getText()
).doubleValue(); }
catch ( NumberFormatException nfe ) { return; }
operandBufferEmpty = true;
leftOperand = operandBuffer;
operand = arg0;
return;
case '=':
try
{
operandBuffer
=
Double.valueOf(
numberField.getText()
).doubleValue(); }
catch ( NumberFormatException nfe ) { return; }
operandBufferEmpty = true;
rightOperand = operandBuffer;
switch ( operand ) {
case '/': result = leftOperand / rightOperand; break;
case '*': result = leftOperand * rightOperand; break;
case '-': result = leftOperand - rightOperand; break;
case '+': result = leftOperand + rightOperand; break;
}
numberField.setText( String.valueOf( result ) );
leftOperand = 0.0;
rightOperand = 0.0;
operand = '=';
return;
}
}
}
1.1.2
1.1.2.1
AWT und Swing
AWT
Im Gegensatz zu den meisten anderen Programmiersprachen wurde Java von Anfang an
mit dem Anspruch entwickelt, ein vielseitiges, aber einfach zu bedienendes System für die
Gestaltung grafischer Oberflächen zur Verfügung zu stellen. Das Resultat dieser
Bemühungen steht seit dem JDK 1.0 als Grafikbibliothek unter dem Namen Abstract
Windowing Toolkit (AWT) zur Verfügung.
1.1.2.2
Swing
Neben dem AWT gibt es eine zweite Bibliothek für die Gestaltung grafischer
Benutzeroberflächen. Sie heißt Swing und ist seit der Version 1.1 fester Bestandteil des
Java Development Kit. Da Swing-Anwendungen in ihren Möglichkeiten weit über das
hinausgehen, was das AWT bietet, werden heute die meisten GUI-Programme mit Swing
geschrieben.
Es macht Sinn, sich zunächst mit dem AWT zu beschäftigen.
12
13
Einerseits ist das AWT einfacher zu erlernen, denn es ist weniger umfangreich als Swing.
Zum anderen werden viele der AWT-Features auch in Swing benötigt, und das angeeignete
Wissen ist nicht verloren.
Drittens gibt es auch heute noch Anwendungen für das AWT, etwa bei der AppletProgrammierung, oder wenn die Verwendung von plattformspezifischen Dialogelementen
zwingend erforderlich ist.
1.1.2.3
1.1.3
1.1.3.1
Überblick über AWT Components
Erstellen eines normalen Fensters oder eines Dialogs
Beispiel zum Erzeugen eines Frames
SimpleFrameManipulations
CreateFrame
/* CreateFrame.java */
import java.awt.*;
public class CreateFrame
{
public static void main(String[] args)
{
// Anlegen eines Fensters
Frame frame = new Frame ("Frame entfernen");
Frame.setSize(300,200);
// Zuordnen eines Layoutmanagers
frame.setLayout(new FlowLayout(FlowLayout.LEFT,20,20);
// Einfügen von Dialogelementen
frame.add(new
frame.add(new
frame.add(new
frame.add(new
frame.add(new
Button("Button
Button("Button
Button("Button
Button("Button
Button("Button
1"));
2"));
3"));
4"));
5"));
// Anzeigen des Fensters
frame.pack();
frame.setVisible(true);
13
14
// Pause von 3 Sekunden
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
//nichts
}
// Verstecken des Fensters
frame.setVisibe(false);
// Freigeben der Ressourcen
frame.dispose();
// Beenden des Programmes
System.exit(0);
}
}
1.1.3.2
Aktionen zum Erstellen
1.1.3.2.1
Anlegen eines Dialogfensters
Da Java prinzipiell keinen Unterschied zwischen Fenstern zur Ausgabe eines Dialogs und
solchen zur Anzeige von Grafiken macht, ist es möglich, ein Dialogfenster wahlweise aus
Frame oder Dialog abzuleiten.
Die Klasse Dialog erlaubt es, das Verändern der Fenstergröße durch den Anwender zu
unterbinden, und bietet die Möglichkeit, den Dialog modal zu machen. Dadurch wird die
Interaktion des Anwenders mit anderen Fenstern der Anwendung bis zum Schließen des
Dialogfensters blockiert. Im Gegensatz zu Frame fehlt jedoch die Möglichkeit, eine
Menüleiste zu erzeugen oder dem Fenster ein Icon zuzuordnen.
1.1.3.2.2
Zuordnen eines Layoutmanagers
Die Layoutmanager sind in Java für die Anordnung der Dialogelemente im Fenster
verantwortlich. Jeder Layoutmanager verfolgt dabei eine eigene Strategie, Elemente zu
plazieren und in der Größe so anzupassen, dass sie aus seiner Sicht optimal präsentiert
werden.
Die Zuordnung eines Layoutmanagers zu einem Fenster wird in der Klasse Container
realisiert. Container ist direkt aus Component abgeleitet, und beide zusammen bilden das
Gerüst für alle anderen Fensterklassen. Die Klasse Container stellt eine Methode setLayout
zur Verfügung, mit der der gewünschte Layoutmanager dem Fenster zugeordnet werden
kann:
public void setLayout(LayoutManager mgr)
Java stellt standardmäßig die fünf Layoutmanager FlowLayout, GridLayout, BorderLayout,
CardLayout und GridBagLayout zur Verfügung. Der einfachste Layoutmanager ist
FlowLayout, er positioniert die Dialogelemente zeilenweise hintereinander. Passt ein
Element nicht mehr in die aktuelle Zeile, so wird es in der nächsten plaziert usw.
1.1.3.2.3
Einfügen von Dialogelementen
Das Einfügen von Dialogelementen in das Fenster erfolgt mit der Methode add der Klasse
Container:
Sollen Komponenten, die bereits an das Fenster übergeben wurden, wieder daraus entfernt
werden, so kann dazu die Methode remove verwendet werden. Als Parameter ist dabei das
zu löschende Objekt zu übergeben.
Container stellt auch Methoden zur Verfügung, um auf die bereits eingefügten
Dialogelemente zuzugreifen:
Mit getComponentCount kann die Anzahl aller eingefügten Komponenten ermittelt werden.
getComponent liefert die Komponente mit dem angegebenen Index, und getComponents
gibt ein Array mit allen eingefügten Komponenten zurück.
14
15
1.1.3.2.4
Anzeigen des Dialogfensters
Wurden alle Komponenten an den Container übergeben, kann der Dialog formatiert und
durch einen Aufruf von setVisible angezeigt werden. Zweckmäßigerweise sollte vorher die
Methode pack der Klasse Window aufgerufen werden, um die Größe des Fensters an den
zur Darstellung der Dialogelemente erforderlichen Platz anzupassen.
1.1.3.3
Beispiel Anpassen eines Frames
SimpleFrameManipulations
CustomizeFrame
/* CustomizeFrame.java */
import java.awt.*;
import java.awt.event.*;
public class CustomizeFrame
extends Frame
{
public static void main(String[] args)
{
CustomizeFrame wnd = new CustomizeFrame();
wnd.setSize(300,200);
wnd.setLocation(50,50);
wnd.setVisible(true);
}
public CustomizeFrame()
{
super("");
assignTitle();
assignIcon();
assignCursor();
assignColors();
assignFont();
addWindowListener(new WindowClosingAdapter(true));
}
private void assignTitle()
{
setTitle("Veränderte Fensterelemente");
}
private void assignIcon()
{
Image img = getToolkit().getImage("testicon.gif");
MediaTracker mt = new MediaTracker(this);
mt.addImage(img, 0);
try {
//Warten, bis das Image vollständig geladen ist,
mt.waitForAll();
} catch (InterruptedException e) {
//nothing
}
setIconImage(img);
}
private void assignCursor()
{
setCursor(new Cursor(Cursor.WAIT_CURSOR));
}
private void assignColors()
{
setForeground(Color.green);
setBackground(Color.blue);
}
private void assignFont()
{
setFont(new Font("Serif", Font.PLAIN, 28));
}
15
16
public void paint(Graphics g)
{
g.drawString("Test in Vordergrundfarbe",10,70);
}
}
1.1.4
Eventmodell
1.1.4.1
Beispiel ActionEvent
public class calculator extends Frame implements ActionListener {
public static void main( String[] args ) {
Frame frame = new Calculator( "Calculator" );
frame.addWindowListener(
new WindowAdapter() {
public void windowClosing( WindowEvent e ) {
Runtime.getRuntime().exit( 0 );
}
}
);
frame.pack();
frame.setVisible( true );
}
private TextField numberField = null;
public Calculator(String title) {
super(title);
setBackground( Color.blue );
setLayout(new FlowLayout(FlowLayout.LEFT));
Button button = null;
add(button = new Button( "Press" ) );
button.addActionListener(this);
numberField = new TextField( "No Comment", 15 );
add(numberField );
}
public void actionPerformed(ActionEvent e){
System.out.println("Pressed");
Numberfield.setText("Pressed");
}
}
1.1.4.2
Überblick
Delegation Event Model oder Delegation Based Event Handling
Ereignisgesteuerte Programmierung ist der Hauptbestandteil der Programmierung von
Benutzungsoberflächen. Der Benutzer löst durch das Bewegen, Klicken der Maus, Eingabe
von Text usw. Ereignisse aus, auf die das Programm reagiert. Applets können das
genauso.
Die Statusänderungen der Eingabegeräte, also z.B. das Bewegen der Maus, das Drücken
der Maustaste oder der Tasten auf der Tastatur, werden dem Java System in Form von
Events gemeldet.
Das JDK-1.1 führte ein Ereignismodell ein, das auf der Weiterleitung eines Ereignisses von
einer Quelle zu einem vorher zu installierenden Lauschobjekt (Listener) beruht. Ist kein
Listener für dieses Ereignis aktiviert, wird es auch nicht weitergeleitet.
Events sind Objekte, die von einem anderen Objekt ausgesendet werden, der EventSource.
Ein Event-Listener teilt der Event-Source sein Interesse an bestimmten Events mit, indem er
eine ihrer Registrierungsmethoden aufruft.
Er bekommt fortan die entsprechenden Events mitgeteilt bis er sich wieder abmeldet.
Die Mitteilung besteht umgekehrt im Aufruf der passenden Methode im Event-Listener, die
als Argument eine entsprechende Unterklasse von EventObject erhält.
Um sicher zu stellen, dass jeder Event-Listener die benötigten Methoden bereit stellt,
müssen die passenden Schnittstellen implementiert werden.
16
17
Die Oberflächenkomponenten erben von der Klasse Component viele der dazu
notwendigen Methoden.
Überschreibt sie der Programmierer, kann er die Reaktion des Oberflächenkomponenten
auf Ereignisse selbst festlegen.
1.1.4.3
Ereignisse
1.1.4.3.1
Diagramm
Die Klassenhierarchie enthält eine Wurzelklasse für alle Ereignisse:
java.util.EventObject. Damit unterscheiden sich Ereignisse nicht durch bestimmte
Identifikatoren, sondern durch ihre Zugehörigkeit zu unterschiedlichen Klassen. Erst wenn
verschiedene Ereignisse zu derselben Ereignisklasse gehören, bestimmt ein zusätzlicher
Identifikator das Ereignis.
Die zu einem Ereignis gehörenden Daten (z.B. die aktuellen Koordinaten der Maus) sind
nicht mehr direkt nach außen sichtbar. Stattdessen dienen spezielle Zugriffsmethoden der
Abfrage der Werte.
Mittels getSource() erhält man das mit den Event verknüpfte Objekt, mittels getID() die den
Event kennzeichnende Event-ID.
Das Ereignismodell unterscheidet zwischen primitiven Ereignissen (low-level event) und
Ereignissen zur Interaktion verschiedener Komponenten (semantic event). Zu Komponenten
zählen sowohl AWT-Komponenten als auch andere Einheiten (wie z.B. Timer), die in ein
Ereignismodell passen.
1.1.4.3.2
Primitive Ereignisse
Als primitive Ereignisse implementiert das AWT im Paket java.awt.event:
ComponentEvent (Größen- und Positionsänderung), ContainerEvent (Hinzufügen oder
Löschen einer Komponente aus dem Container),
FocusEvent (Abgabe und Zuweisung des Eingabefokus),
WindowEvent (Änderung eines Fensters),
17
18
InputEvent mit den Subklassen KeyEvent (Tastatur-Eingaben) und MouseEvent
(Mausbewegungen und Maustasten).
1.1.4.3.3
Ereignisse zur Interaktion verschiedener Komponenten
ActionEvent(Aufforderung zur Ausführung einer Aktion),
AdjustmentEvent (Wertänderung, z.B. bei Scrollbar),
ItemEvent (Änderung der Einstellung einer Auswahl, z.B. bei Choice oder Checkbox)
und
TextEvent (Änderung eines Textes).
1.1.4.4
Listener
1.1.4.4.1
Konzept
Lauschobjekte (Listener) empfangen und verarbeiten Ereignisse.
Um diese Funktion ausführen zu können, muss der Listener eine spezielle auf die jeweiligen
Ereignisse angepasste Schnittstelle implementieren. Die Schnittstelle bestimmt, welche
Methoden im Listener implementiert werden müssen, um das Ereignis verarbeiten zu
können. Im Allgemeinen enthält die Schnittstelle Bearbeitungsmethoden für jedes Ereignis,
das die zu bedienende Ereignisklasse repräsentiert.
Erst die Installation eines entsprechenden Listeners führt auch zur Weiterleitung des
Ereignisses. Hierin liegt ein entscheidender Performanzgewinn. Insbesondere
hochfrequente Ereignisse, die zum Beispiel beim Bewegen der Maus entstehen, werden nur
geliefert, wenn die Komponente sie auch wirklich benötigt.
1.1.4.4.2
Listenerdiagramm
1.1.4.4.3
Listener-Schnittstellen
Für primitive Ereignisse enthält das AWT in java.awt.event die Listener-Schnittstellen
ComponentListener,
ContainerListener,
MouseMotionListener,
MouseListener,
FocusListener,
18
19
KeyListener und
WindowListener,
für die Ereignisse zur Interaktion
ActionListener,
AdjustmentListener,
ItemListener und
TextListener.
Einige dieser Schnittstellen enthalten mehrere Methoden für verschiedene Ereignisse,
MouseListener z.B.
mouseClicked(),
mouseEntered(),
mouseExited(),
mousePressed() und
mouseReleased().
Diese Wahl wurde getroffen, um nicht für jedes Ereignis eine Listener-Klasse zu benötigen.
1.1.4.5
Ereignisquellen
1.1.4.5.1
Konzept
Eine Ereignisquelle liefert Ereignisse an Listener. Das können sowohl ein als auch mehrere
Listener sein, je nachdem, wie sie installiert wurden. Ist kein Listener zu einem Ereignis
installiert, so wird es nicht geliefert. Sendet die Quelle ein Ereignis an mehrere Listener, so
darf der Programmierer über die Reihenfolge der bedienten Listener keine Annahme treffen.
Er sollte also kein Programm schreiben, das von einer bestimmten Reihenfolge ausgeht.
Jeder Listener erhält dabei eine Kopie des ursprünglichen Ereignisses, so dass eventuelle
Änderungen des Ereignisses in einem Listener keine Wirkung auf einen anderen Listener
haben.
1.1.4.5.2
Ereignisquellen
Quellen von primitiven Ereignissen sind die GUI-Komponenten des AWT:
Component ( ComponentEvent, FocusEvent, KeyEvent, MouseEvent,
MouseMotionEvent),
Dialog ( WindowEvent) und
Frame ( WindowEvent)
Quellen von Ereignissen zur Interaktion von Komponenten sind
Button ( ActionEvent),
MenuItem ( ActionEvent),
List ( ActionEvent, ItemEvent),
Choice ( ItemEvent),
Checkbox ( ItemEvent),
CheckboxMenuItem ( ItemEvent) und
Scrollbar ( AdjustmentEvent).
Mit einer Klasse sind natürlich auch die von ihr abgeleiteten Klassen Quellen der
entsprechenden Ereignisse.
1.1.4.6
Aktivierung von Lauschobjekten
Jede Quelle besitzt für die von ihr gelieferten Ereignisse Methoden, Listener für diese
Ereignisse zu aktivieren. Will der Programmierer mehrere Listener je Ereignis aktivieren,
19
20
verwendet er die addXXXListener()-Methoden, anderenfalls die setXXXListener()Methoden. XXX steht dabei für das jeweilige Ereignis.
1.1.4.7
Beispiel MouseEventsDemoEventTechniken
1.1.4.7.1
Implementation Listener durch Frame
/* MouseEvents.java */
import java.awt.*;
import java.awt.event.*;
public class MouseEvents
extends Frame implements MouseListener
{
Canvas c1 = null;
Canvas c2 = null;
public static void main(String[] args)
{
MouseEvents wnd = new MouseEvents();
wnd.setVisible(true);
}
public MouseEvents()
{
super("Mausklicks");
setSize(500,400);
setLayout(new FlowLayout());
setLocation(200,100);
int fx = getSize().height / 2;
int fy = getSize().width / 2;
c1 = new Canvas();
c1.setBackground(Color.blue);
c1.setSize(fx, fy);
add(c1);
c2 = new Canvas();
c2.setBackground(Color.red);
c2.setSize(fx, fy);
add(c2);
pack();
addWindowListener(new WindowClosingAdapter(true));
c1.addMouseListener(this);
}
// Variante Frame implementiert MouseListener selbst
public void mouseClicked(MouseEvent e)
{
}
public void mouseEntered(MouseEvent e)
{
}
public void mouseExited(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
}
public void mousePressed(MouseEvent event)
{
Graphics g = c1.getGraphics();
int x = event.getX();
int y = event.getY();
g.drawOval(x-10,y-10,20,20);
}
}
20
21
1.1.4.7.2
Adapter
/* MouseEvents.java */
import java.awt.*;
import java.awt.event.*;
public class MouseEvents
extends Frame
{
Canvas c1 = null;
Canvas c2 = null;
public static void main(String[] args)
{
MouseEvents wnd = new MouseEvents();
wnd.setVisible(true);
}
public MouseEvents()
{
super("Mausklicks");
setSize(500,400);
setLayout(new FlowLayout());
setLocation(200,100);
int fx = getSize().height / 2;
int fy = getSize().width / 2;
c1 = new Canvas();
c1.setBackground(Color.blue);
c1.setSize(fx, fy);
add(c1);
c2 = new Canvas();
c2.setBackground(Color.red);
c2.setSize(fx, fy);
add(c2);
pack();
addWindowListener(new WindowClosingAdapter(true));
c1.addMouseListener(new MyMouseListener());
}
// Variante Frame erzeugt Objekt einer Klasse, die von MouseAdapter abgeleitet ist
class MyMouseListener extend MouseAdapter {
public void mousePressed(MouseEvent event)
{
Graphics g = c1.getGraphics();
int x = event.getX();
int y = event.getY();
g.drawOval(x-10,y-10,20,20);
}
}
}
1.1.4.7.3
Anonyme Klasse
/* MouseEvents.java */
import java.awt.*;
import java.awt.event.*;
public class MouseEvents
extends Frame
{
Canvas c1 = null;
Canvas c2 = null;
public static void main(String[] args)
{
MouseEvents wnd = new MouseEvents();
wnd.setVisible(true);
}
public MouseEvents()
{
super("Mausklicks");
setSize(500,400);
21
22
setLayout(new FlowLayout());
setLocation(200,100);
int fx = getSize().height / 2;
int fy = getSize().width / 2;
c1 = new Canvas();
c1.setBackground(Color.blue);
c1.setSize(fx, fy);
add(c1);
c2 = new Canvas();
c2.setBackground(Color.red);
c2.setSize(fx, fy);
add(c2);
pack();
addWindowListener(new WindowClosingAdapter(true));
// Variante Frame erzeugt Objekt einer Klasse, die von MouseAdapter abgeleitet ist
c1.addMouseListener(new MouseAdapter(){
public void mousePressed(MouseEvent e) {
Graphics g = c1.getGraphics();
g.drawOval(e.getX(), e.getY(), 20 , 20);
}
}
);
}
}
1.1.4.8
Adapter Klassen
Wenn z.B. die MouseListener Klasse keine Schnittstelle sondern eine beerbbare normale
Klasse wäre und Java Mehrfachvererbung erlauben würde, könnte man in der
MouseListener Klasse für jedes Ereignis eine Standardreaktion implementieren. Eine
spezifische von MouseListener abgeleitete Klasse müsste nur diejenige Handlerfunktion
überschreiben, die neu definiert werden soll. Da es sich jedoch bei der Klasse
MouseListener um eine reine Schnittstelle handelt ist keine der Handlerfunktionen
implementiert. Daher müsste die von MouseListener abgeleitete Klasse wirklich alle
Handlerfunktionen ausfüllen.
Es wäre jedoch für den Programmierer sehr umständlich, jede der enthaltenen Methoden
ausfüllen zu müssen, auch wenn er nur ein einziges Ereignis (z.B. mousePressed())
behandeln will. Für jede der oben genannten Schnittstellen enthält das AWT deshalb eine
sogenannte Adapter-Klasse, eine Klasse, die genau diese Schnittstelle implementiert. Diese
Klassen kann der Programmierer erweitern und muss dann nur noch die für das
gewünschte Ereignis zuständige Methode überschreiben.
Die vorhandenen Adapter sind :
ComponentAdapter,
ContainerAdapter,
FocusAdapter,
KeyAdapter,
MouseAdapter,
MouseMotionAdapter und
WindowAdapter.
Da die Schnittstellen für die Listener der Interaktionsereignisse jeweils nur eine Methode
enthalten, existieren für sie keine Adapter.
1.1.4.9
Adapter als innere Klassen
Das Einerben eines Adapters funktioniert wegen der verbotenen Mehrfachvererbung nicht,
wenn die Klasse bereits von einer anderen Klasse abgeleitet wurde.
In diesem Falle wird der Adapter als innere Klasse realisert.
22
23
1.1.4.10 Beispiele
1.1.4.10.1
Beispiel Schließen eines Frames mit Escape
Fenster
AWT
Events
SchliessenMitEscape
1.1.4.10.1.1
Implementation Keylistener
/* FrameManip.java */
import java.awt.*;
import java.awt.event.*;
public class FrameManip
extends Frame
implements KeyListener
{
public static void main(String[] args)
{
FrameManip wnd = new FrameManip();
}
public FrameManip()
{
super("Nachrichtentransfer");
setBackground(Color.lightGray);
setSize(300,200);
setLocation(200,100);
setVisible(true);
addKeyListener(this);
}
public void paint(Graphics g)
{
g.setFont(new Font("Serif",Font.PLAIN,18));
g.drawString("Zum Beenden bitte ESC drücken...",10,50);
}
public void keyPressed(KeyEvent event)
{
if (event.getKeyCode() == KeyEvent.VK_ESCAPE) {
setVisible(false);
dispose();
System.exit(0);
}
}
public void keyReleased(KeyEvent event)
{
}
public void keyTyped(KeyEvent event)
{
}
}
1.1.4.10.1.2
Innerere Klasse und Adapter
import java.awt.*;
import java.awt.event.*;
public class FrameManip
extends Frame
{
public static void main(String[] args)
{
FrameManip wnd = new FrameManip();
}
23
24
public FrameManip()
{
super("Nachrichtentransfer");
setBackground(Color.lightGray);
setSize(300,200);
setLocation(200,100);
setVisible(true);
addKeyListener(new MyKeyListener());
}
public void paint(Graphics g)
{
g.setFont(new Font("Serif",Font.PLAIN,18));
g.drawString("Zum Beenden bitte ESC drücken...",10,50);
}
class MyKeyListener
extends KeyAdapter
{
public void keyPressed(KeyEvent event)
{
if (event.getKeyCode() == KeyEvent.VK_ESCAPE) {
setVisible(false);
dispose();
System.exit(0);
}
}
}
}
1.1.4.10.1.3
Anonyme Klasse
import java.awt.*;
import java.awt.event.*;
public class Listing2804
extends Frame
{
public static void main(String[] args)
{
Listing2804 wnd = new Listing2804();
}
public Listing2804()
{
super("Nachrichtentransfer");
setBackground(Color.lightGray);
setSize(300,200);
setLocation(200,100);
setVisible(true);
addKeyListener(
new KeyAdapter() {
public void keyPressed(KeyEvent event)
{
if (event.getKeyCode() == KeyEvent.VK_ESCAPE) {
setVisible(false);
dispose();
System.exit(0);
}
}
}
);
}
public void paint(Graphics g)
{
g.setFont(new Font("Serif",Font.PLAIN,18));
g.drawString("Zum Beenden bitte ESC drücken...",10,50);
}
}
1.1.4.10.1.4
Überlagern der Event-Handler in den Komponenten
Jede Ereignisquelle besitzt eine Reihe von Methoden, die für das Aufbereiten und Verteilen
der Nachrichten zuständig sind. Soll eine Nachricht weitergereicht werden, so wird dazu
24
25
zunächst innerhalb der Nachrichtenquelle die Methode processEvent aufgerufen. Diese
verteilt die Nachricht anhand ihres Typs an spezialisierte Methoden, deren Name sich nach
dem Typ der zugehörigen Ereignisklasse richtet. So ist beispielsweise die Methode
processActionEvent für das Handling von Action-Events und processMouseEvent für das
Handling von Mouse-Events zuständig:
protected void processEvent(AWTEvent e)
protected void processComponentEvent(ComponentEvent e)
protected void processFocusEvent(FocusEvent e)
Beide Methodenarten können in einer abgeleiteten Klasse überlagert werden, um die
zugehörigen Ereignisempfänger zu implementieren. Wichtig ist dabei, dass in der
abgeleiteten Klasse die gleichnamige Methode der Basisklasse aufgerufen wird, um das
Standardverhalten sicherzustellen. Wichtig ist weiterhin, dass sowohl processEvent als
auch processActionEvent usw. nur aufgerufen werden, wenn der entsprechende Ereignistyp
für diese Ereignisquelle aktiviert wurde. Dies passiert in folgenden Fällen:
Wenn ein passender Ereignisempfänger über die zugehörige addEventListener-Methode
registriert wurde.
Wenn der Ereignistyp explizit durch Aufruf der Methode enableEvents aktiviert wurde.
Die Methode enableEvents erwartet als Argument eine Maske, die durch eine bitweise
Oder-Verknüpfung der passenden Maskenkonstanten aus der Klasse AWTEvent
zusammengesetzt werden kann:
protected final void enableEvents(long eventsToEnable)
java.awt.Component
Die verfügbaren Masken sind analog zu den Ereignistypen benannt und heißen
ACTION_EVENT_MASK, ADJUSTMENT_EVENT_MASK, COMPONENT_EVENT_MASK
usw.
Das folgende Beispiel überlagert die Methode processKeyEvent in der Klasse Frame (die
sie aus Component geerbt hat). Durch Aufruf von enableEvents wird die Weiterleitung der
Tastaturereignisse aktiviert, und das Programm zeigt dasselbe Verhalten wie die vorigen
Programme.
import java.awt.*;
import java.awt.event.*;
public class FrameManip
extends Frame
{
public static void main(String[] args)
{
FrameManip wnd = new FrameManip();
}
public FrameManip()
{
super("Nachrichtentransfer");
setBackground(Color.lightGray);
setSize(300,200);
setLocation(200,100);
setVisible(true);
enableEvents(AWTEvent.KEY_EVENT_MASK);
}
public void paint(Graphics g)
{
g.setFont(new Font("Serif",Font.PLAIN,18));
g.drawString("Zum Beenden bitte ESC drücken...",10,50);
}
public void processKeyEvent(KeyEvent event)
25
26
{
if (event.getID() == KeyEvent.KEY_PRESSED) {
if (event.getKeyCode() == KeyEvent.VK_ESCAPE) {
setVisible(false);
dispose();
System.exit(0);
}
}
super.processKeyEvent(event);
}
}
Diese Art der Ereignisbehandlung ist nur sinnvoll, wenn Fensterklassen oder
Dialogelemente überlagert werden und ihr Aussehen oder Verhalten signifikant verändert
wird. Alternativ könnte natürlich auch in diesem Fall ein EventListener implementiert und die
entsprechenden Methoden im Konstruktor der abgeleiteten Klasse registriert werden.
Das hier vorgestellte Verfahren umgeht das Delegation Event Model vollständig und hat
damit die gleichen inhärenten Nachteile wie das Event-Handling des alten JDK. Die
Dokumentation zum JDK empfiehlt daher ausdrücklich, für alle »normalen«
Anwendungsfälle das Delegation Event Model zu verwenden.
1.1.4.10.2
Key Events
Unter Windows werden alle Tastatureingaben an die fokussierte Komponente gesendet. Ein
Empfänger für Key-Events muss das Interface KeyListener implementieren und
bekommt Events des Typs KeyEvent übergeben. KeyEvent erweitert die Klasse
InputEvent, die ihrerseits aus ComponentEvent abgeleitet ist, und stellt neben getID
und getSource eine ganze Reihe von Methoden zur Verfügung, mit denen die Erkennung
und Bearbeitung der Tastencodes vereinfacht wird.
Zeichentasten sind solche Tasten, mit denen Buchstaben, Ziffern oder sonstige gültige
Unicode-Zeichen eingegeben werden, wie z.B. [a], [A], [B], [1], [2], [%], [+], aber auch [ESC],
[LEER] oder [TAB].
Zu den Funktionstasten gehören beispielsweise [F1], [F2], [POS1] oder [CURSORLINKS],
aber auch die Umschalttasten [STRG], [ALT] und [UMSCHALT].
Die Methode keyTyped wird immer dann aufgerufen, wenn eine Zeichentaste gedrückt
wurde.
Beim Drücken einer Funktionstaste wird sie dagegen nicht aufgerufen. Im Gegensatz dazu
wird keyPressed bei jedem Tastendruck aufgerufen, unabhängig davon, ob es sich um eine
Zeichentaste oder eine Funktionstaste handelt.
Beide Methoden erhalten auch Tastatur-Repeats, werden also bei längerem Festhalten
einer Taste wiederholt aufgerufen. Die Methode keyReleased wird aufgerufen, wenn eine
gedrückte Taste losgelassen wurde, unabhängig davon, ob es sich um eine Zeichen- oder
Funktionstaste handelt.
getKeyChar liefert das Zeichen, das der gedrückten Zeichentaste entspricht, also ein 'a',
wenn die Taste [A] gedrückt wurde, und ein 'A', wenn die Tastenkombination
[UMSCHALT]+[A] gedrückt wurde. getKeyCode liefert dagegen virtuelle Tastencodes, die
in KeyEvent als symbolische Konstanten definiert wurden. Hier wird beim Drücken der
Taste [A] immer der Code VK_A geliefert, unabhängig davon, ob [UMSCHALT] gedrückt
wurde oder nicht. Die folgende Tabelle gibt eine Übersicht der wichtigsten virtuellen
Keycodes der Klasse KeyEvent.
Symbolischer Name
VK_0 ... VK_9
VK_A ... VK_Z
VK_ENTER
VK_SPACE
Bedeutung
[0] ... [9]
[A] ... [Z]
[ENTER]
[LEER]
26
27
VK_TAB
VK_ESCAPE
VK_BACK_SPACE
VK_F1 ... VK_F12
VK_HOME, VK_END
VK_INSERT, VK_DELETE
VK_PAGE_UP, VK_PAGE_DOWN
VK_DOWN, VK_UP
VK_LEFT, VK_RIGHT
[TAB]
[ESC]
[RÜCK]
Die Funktionstasten [F1] ... [F12]
[HOME], [END]
[EINFG], [ENTF]
[BILDHOCH], [BILDRUNTER]
[CURSORHOCH], [CURSORRUNTER]
[CURSORLINKS], [CURSORRECHTS]
Am einfachsten ist es, innerhalb von keyTyped mit getKeyChar die Zeichentasten
abzufragen. Dabei liefert getKeyChar stets den ASCII-Code der gedrückten Zeichentaste,
Funktionstasten werden nicht übertragen. Der Rückgabewert von getKeyCode ist in
diesem Fall immer KeyEvent.VK_UNDEFINED. Sollen dagegen auch Funktionstasten
abgefragt werden, muß die Methode keyPressed überlagert werden. Hier ist etwas
Vorsicht geboten, denn es wird auf alle Tastendrücke reagiert, und sowohl getKeyCode als
auch getKeyChar liefern Werte zurück. Die Unterscheidung von Zeichen- und
Funktionstasten kann in diesem Fall mit Hilfe von getKeyChar vorgenommen werden,
deren Rückgabewert die Konstante KeyEvent.CHAR_UNDEFINED ist, wenn eine
Funktionstaste gedrückt wurde.
Die is-Methoden sind bereits bekannt, mit ihnen können die Umschalttasten abgefragt
werden. Das ist beispielsweise sinnvoll, um bei einer Funktionstaste herauszufinden, ob sie
mit gedrückter Umschalttaste ausgelöst wurde oder nicht.
keyTyped
keyPressed
getKeyCode
getKeyChar
Zeichentaste: VK_UNDEFINED
Funktionstaste: -Zeichentaste: VK_...
Funktionstaste: VK_...
Zeichentaste: Taste als char
Funktionstaste: -Zeichentaste: Taste als char
Funktionstaste:
CHAR_UNDEFINED
Das Beispiel demonstriert die Abfrage der Tastaturereignisse. Es implementiert
keyPressed, um die Funktionstasten [F1] bis [F3] und den Status der Umschalttasten
abzufragen. Jeder Tastendruck wird in einen String übersetzt, in msg1 gespeichert und
durch Aufruf von repaint auf dem Bildschirm angezeigt. Nach dem Loslassen der Taste
wird die Anzeige wieder vom Bildschirm entfernt. Weiterhin wurde keyTyped überlagert,
um die Zeichentasten abzufragen. Jeder Tastendruck wird in msg2 gespeichert und
ebenfalls auf dem Bildschirm angezeigt. Im Gegensatz zu den Funktionstasten bleibt die
Ausgabe auch erhalten, wenn die Taste losgelassen wird. Bei jedem weiteren Tastendruck
wird sie um ein Zeichen ergänzt. Zusätzlich werden die einzelnen Ereignisse auf der
Konsole dokumentiert.
/* KeyEvents.java */
import java.awt.*;
import java.awt.event.*;
public class KeyEvents
extends Frame
implements KeyListener
{
String msg1 = "";
String msg2 = "";
public static void main(String[] args)
{
KeyEvents wnd = new KeyEvents();
}
public KeyEvents()
27
28
{
super("Tastaturereignisse");
addKeyListener(this);
addWindowListener(new WindowClosingAdapter(true));
setBackground(Color.lightGray);
setSize(300,200);
setLocation(200,100);
setVisible(true);
}
public void paint(Graphics g)
{
if (msg1.length() > 0) {
draw3DRect(g,20,50,250,30);
g.setColor(Color.black);
g.drawString(msg1,30,70);
}
if (msg2.length() > 0) {
draw3DRect(g,20,100,250,30);
g.setColor(Color.black);
g.drawString(msg2,30,120);
}
}
void draw3DRect(Graphics g,int x,int y,int width,int height)
{
g.setColor(Color.darkGray);
g.drawLine(x,y,x,y+height);
g.drawLine(x,y,x+width,y);
g.setColor(Color.white);
g.drawLine(x+width,y+height,x,y+height);
g.drawLine(x+width,y+height,x+width,y);
}
public void keyPressed(KeyEvent event)
{
msg1 = "";
System.out.println(
"key pressed: " +
"key char = " + event.getKeyChar() + " " +
"key code = " + event.getKeyCode()
);
if (event.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
int key = event.getKeyCode();
//Funktionstaste abfragen
if (key == KeyEvent.VK_F1) {
msg1 = "F1";
} else if (key == KeyEvent.VK_F2) {
msg1 = "F2";
} else if (key == KeyEvent.VK_F3) {
msg1 = "F3";
}
//Modifier abfragen
if (msg1.length() > 0) {
if (event.isAltDown()) {
msg1 = "ALT + " + msg1;
}
if (event.isControlDown()) {
msg1 = "STRG + " + msg1;
}
if (event.isShiftDown()) {
msg1 = "UMSCHALT + " + msg1;
}
}
}
repaint();
}
public void keyReleased(KeyEvent event)
{
System.out.println("key released");
msg1 = "";
repaint();
}
public void keyTyped(KeyEvent event)
{
char key = event.getKeyChar();
System.out.println("key typed: " + key);
if (key == KeyEvent.VK_BACK_SPACE) {
if (msg2.length() > 0) {
msg2 = msg2.substring(0,msg2.length() - 1);
}
28
29
} else if (key >= KeyEvent.VK_SPACE) {
if (msg2.length() < 40) {
msg2 += event.getKeyChar();
}
}
repaint();
}
}
1.1.4.10.3
Mouse Events
nur elektronisch
1.1.4.10.4
Component Events
nur elektronisch
1.1.4.10.5
Focus Events
nur elektronisch
1.1.5
1.1.5.1
Layoutmanager
Beispiel Layout
/* Layout.java */
import java.awt.*;
import java.awt.event.*;
public class Layout
extends Frame
{
public static void main(String[] args)
{
Layout wnd = new Layout();
wnd.setVisible(true);
}
public Layout()
{
super("Test Layout");
addWindowListener(new WindowClosingAdapter(true));
setLayout(new GridLayout(5,1));
// Panel fuer FlowLayout
Panel fp = new Panel();
//Layout setzen und Komponenten hinzufügen
fp.setLayout(new FlowLayout(FlowLayout.LEFT,20,20));
fp.add(new Button("Button 1"));
fp.add(new Button("Button 2"));
fp.add(new Button("Button 3"));
fp.add(new Button("Button 4"));
fp.add(new Button("Button 5"));
add(fp);
// Panel fuer GridLayout
Panel gp = new Panel();
//Layout setzen und Komponenten hinzufügen
gp.setLayout(new GridLayout(4,2));
gp.add(new Button("Button 1"));
gp.add(new Button("Button 2"));
gp.add(new Button("Button 3"));
gp.add(new Button("Button 4"));
gp.add(new Button("Button 5"));
gp.add(new Button("Button 6"));
gp.add(new Button("Button 7"));
add(gp);
// Panel fuer BorderLayout
29
30
Panel bp = new Panel();
//Layout setzen und Komponenten hinzufügen
bp.setLayout(new BorderLayout());
bp.add("North", new Button("Button 1"));
bp.add("South", new Button("Button 2"));
bp.add("West", new Button("Button 3"));
bp.add("East", new Button("Button 4"));
bp.add("Center", new Button("Button 5"));
add(bp);
// Geschachtelte Layoutmanager
//Panel 1
Panel panel1 = new Panel();
panel1.setLayout(new GridLayout(3,2));
panel1.add(new Button("Button1"));
panel1.add(new Button("Button2"));
panel1.add(new Button("Button3"));
panel1.add(new Button("Button4"));
panel1.add(new Button("Button5"));
//Panel 2
Panel panel2 = new Panel();
panel2.setLayout(new BorderLayout());
panel2.add("North", new Button("Button4"));
panel2.add("South", new Button("Button5"));
panel2.add("West",
new Button("Button6"));
panel2.add("East",
new Button("Button7"));
panel2.add("Center", panel1);
add(panel2);
pack();
}
}
1.1.5.2
Konzept
In vielen grafischen Oberflächen wird die Anordnung der Elemente eines Dialoges durch
Angabe absoluter Koordinaten vorgenommen. Dabei wird für jede Komponente manuell
oder mit Hilfe eines Ressourcen-Editors pixelgenau festgelegt, an welcher Stelle im Dialog
sie zu erscheinen hat.
Da Java-Programme auf vielen unterschiedlichen Plattformen mit unterschiedlichen
Ausgabegeräten laufen sollen, war eine solche Vorgehensweise für die Designer des AWT
nicht akzeptabel. Sie wählten statt dessen den Umweg über einen Layoutmanager, der für
die Anordnung der Dialogelemente verantwortlich ist. Um einen Layoutmanager verwenden
zu können, wird dieser dem Fenster vor der Übergabe der Dialogelemente mit der Methode
setLayout zugeordnet. Er ordnet dann die per add übergebenen Elemente auf dem Fenster
an.
Jeder Layoutmanager implementiert seine eigene Logik bezüglich der optimalen Anordnung
der Komponenten:
Neben den gestalterischen Fähigkeiten eines Layoutmanagers bestimmt in der Regel die
Reihenfolge der Aufrufe der add-Methode des Fensters die tatsächliche Anordnung der
Komponenten auf dem Bildschirm. Wenn nicht - wie es z.B. beim BorderLayout möglich ist zusätzliche Positionierungsinformationen an das Fenster übergeben werden, ordnet der
jeweilige Layoutmanager die Komponenten in der Reihenfolge ihres Eintreffens an.
Eines der Schlüsselkonzepte zur Realisierung komplexer, portabler Dialoge ist die
Fähigkeit, Layoutmanager schachteln zu können. Dazu wird an der Stelle, die ein Sublayout
erhalten soll, einfach ein Objekt der Klasse Panel eingefügt, das einen eigenen
Layoutmanager erhält. Dieses Panel kann mit Dialogelementen bestückt werden, die
entsprechend dem zugeordneten Unterlayout formatiert werden.
1.1.5.3
FlowLayout
Die Klasse FlowLayout stellt den einfachsten Layoutmanager dar. Alle Elemente werden so
lange nacheinander in einer Zeile angeordnet, bis kein Platz mehr vorhanden ist und in der
nächsten Zeile fortgefahren wird.
30
31
1.1.5.4
GridLayout
Ein GridLayout bietet eine größere Kontrolle über die Anordnung der Elemente als ein
FlowLayout. Hier werden die Komponenten nicht einfach nacheinander auf dem Bildschirm
positioniert, sondern innerhalb eines rechteckigen Gitters angeordnet, dessen Elemente
eine feste Größe haben.
Das Programm übergibt dazu beim Aufruf des Konstruktors zwei Parameter, rows und
columns, mit denen die vertikale und horizontale Anzahl an Elementen festgelegt wird:
Beim Aufruf von add werden die Komponenten dann nacheinander in die einzelnen Zellen
der Gittermatrix gelegt. Analog zum FlowLayout wird dabei zunächst die erste Zeile von
links nach rechts gefüllt, dann die zweite usw.
Die größere Kontrolle des Programms über das Layout besteht nun darin, dass der
Umbruch in die nächste Zeile genau vorhergesagt werden kann. Anders als beim
FlowLayout erfolgt dieser nicht erst dann, wenn keine weiteren Elemente in die Zeile
passen, sondern wenn dort so viele Elemente plaziert wurden, wie das Programm
vorgegeben hat.
Die Größe der Komponenten
Wenn wir die Größe des Fensters nicht durch einen Aufruf von pack, sondern manuell
festgelegen, wird ein wichtiger Unterschied zwischen den beiden bisher vorgestellten
Layoutmanagern deutlich.
Der Unterschied besteht darin, dass ein FlowLayout die gewünschte Größe eines
Dialogelements verwendet, um seine Ausmaße zu bestimmen. Das GridLayout dagegen
ignoriert die gewünschte Größe und dimensioniert die Dialogelemente fest in der Größe
eines Gitterelements. Ein LayoutManager hat also offensichtlich die Freiheit zu entscheiden,
ob und in welcher Weise er die Größen von Fenster und Dialogelementen den aktuellen
Erfordernissen anpasst.
1.1.5.5
BorderLayout
Das BorderLayout verfolgt einen anderen Ansatz als die beiden vorigen Layoutmanager,
denn die Positionierung der Komponenten wird nicht mehr primär durch die Reihenfolge der
Aufrufe von add bestimmt. Statt dessen teilt das BorderLayout den Bildschirm in fünf
Bereiche auf, und zwar in die vier Ränder und das Zentrum. Durch Angabe eines
Himmelsrichtungs-Strings wird beim Aufruf von add angegeben, auf welchem dieser
Bereiche die Komponente plaziert werden soll:
"South": unterer Rand
"North": oberer Rand
"East": rechter Rand
"West": linker Rand
"Center": Mitte
Bezüglich der Skalierung der Komponenten verfolgt BorderLayout einen Mittelweg zwischen
FlowLayout und GridLayout. Während FlowLayout die Komponenten immer in ihrer
gewünschten Größe belässt und GridLayout sie immer skaliert, ist dies bei BorderLayout
von verschiedenen Faktoren abhängig:
Nord- und Südelement behalten ihre gewünschte Höhe, werden aber auf die volle
Fensterbreite skaliert.
Ost- und Westelement behalten ihre gewünschte Breite, werden aber in der Höhe so
skaliert, dass sie genau zwischen Nord- und Südelement passen.
Das Mittelelement wird in der Höhe und Breite so angepasst, dass es den verbleibenden
freien Raum einnimmt.
Auch beim BorderLayout kann die Größe der Lücken zwischen den Elementen an den
Konstruktor übergeben werden.
31
32
1.1.5.6
CardLayout
Das CardLayout ist in der Lage, mehrere Unterdialoge in einem Fenster unterzubringen und
jeweils einen davon auf Anforderung des Programms anzuzeigen.
1.1.5.7
GridBagLayout
Das GridBagLayout ist ein komplexer Layoutmanager, der die Fähigkeiten von GridLayout
erweitert und es ermöglicht, mit Hilfe von Bedingungsobjekten sehr komplexe Layouts zu
erzeugen.
1.1.5.8
1.1.6
Null-Layout
Sollte auch die GridBagLayout Variante nicht genau genug sein, so bietet sich durch
Verwendung eines Null-Layouts die Möglichkeit an, Komponenten durch Vorgabe fester
Koordinaten zu plazieren.
Modale Dialoge
1.1.6.1
Beispiel Dialoge
/* Dialoge.java */
import java.awt.*;
import java.awt.event.*;
class YesNoDialog
extends Dialog
implements ActionListener
{
boolean result;
public YesNoDialog(Frame owner, String msg)
{
super(owner, "Ja-/Nein-Auswahl", true); //mit true wartet auf antwort
//Fenster
setBackground(Color.lightGray);
setLayout(new BorderLayout());
setResizable(false); //Hinweis im Text beachten
Point fatherLocation = owner.getLocation();
setLocation(fatherLocation.x + 30, fatherLocation.y + 30);
//Message
add("Center", new Label(msg));
//Buttons
Panel panel = new Panel();
panel.setLayout(new FlowLayout(FlowLayout.CENTER));
Button button = new Button("Ja");
button.addActionListener(this);
panel.add(button);
button = new Button("Nein");
button.addActionListener(this);
panel.add(button);
add("South", panel);
pack();
}
public void actionPerformed(ActionEvent event)
{
result = event.getActionCommand().equals("Ja");
setVisible(false);
dispose();
}
public boolean getResult()
{
return result;
}
}
public class Dialoge
extends Frame
implements ActionListener
{
32
33
public static void main(String[] args)
{
Dialoge wnd = new Dialoge();
wnd.setVisible(true);
}
public Dialoge()
{
super("Modale Dialoge");
setLayout(new FlowLayout());
setBackground(Color.lightGray);
Button button = new Button("Ende");
button.addActionListener(this);
add(button);
setLocation(100,100);
setSize(300,200);
setVisible(true);
}
public void actionPerformed(ActionEvent event)
{
String cmd = event.getActionCommand();
if (cmd.equals("Ende")) {
YesNoDialog dlg;
dlg = new YesNoDialog(this, "Wollen Sie das Programm wirklich beenden?");
dlg.setVisible(true);
//Auf das Schließen des Dialogs warten...
if (dlg.getResult()) {
setVisible(false);
dispose();
System.exit(0);
}
}
}
}
1.1.6.2
Warten auf die Eingabe
Modale Dialoge sind solche, die alle Benutzereingaben des Programmes beanspruchen und
andere Fenster erst dann wieder zum Zuge kommen lassen, wenn das Dialogfenster
geschlossen wird. Eine wichtige Eigenschaft modaler Dialoge ist es, dass im Programm der
Aufruf zur Anzeige des Dialogs so lange blockiert, bis der Dialog beendet ist. Auf diese
Weise kann an einer bestimmten Stelle im Programm auf eine Eingabe gewartet werden
und erst dann mit der Bearbeitung fortgefahren werden, wenn die Eingabe erfolgt ist.
Ein modaler Dialog muss immer aus der Klasse Dialog abgeleitet werden. Nur sie bietet die
Möglichkeit, an den Konstruktor einen booleschen Wert zu übergeben, der festlegt, dass die
übrigen Fenster der Anwendung während der Anzeige des Dialogs suspendiert werden.
Als erstes Argument des Konstruktors muss in jedem Fall ein Frame- oder Dialog-Objekt als
Vaterfenster übergeben werden. Mit title kann der Inhalt der Titelzeile vorgegeben werden,
und der Parameter modal entscheidet, ob der Dialog modal dargestellt wird oder nicht.
1.1.6.3
Beispiel VariableDialoge
nur elektronisch
1.2
Swing
1.2.1 Beispiel Geburtsmonat
Fenster
Swing
Geburtsmonat
/* Geburtsmonat.java */
import java.awt.event.*;
//import javax.swing.*; // Funktioniert nicht mit VisualCafe
33
34
import com.sun.java.swing.*;
public class Geburtsmonat
extends JFrame
implements ActionListener
{
private static final String[] MONTHS = {
"Januar",
"Februar", "März",
"April",
"Mai",
"Juni",
"Juli",
"August",
"September", "Oktober", "November", "Dezember"
};
public Geburtsmonat()
{
super("Swing-Programm");
//Panel zur Namenseingabe hinzufügen
JPanel namePanel = new JPanel();
JLabel label = new JLabel(
"Name:",
new ImageIcon("testicon.gif"),
SwingConstants.LEFT
);
namePanel.add(label);
JTextField tf = new JTextField(30);
tf.setToolTipText("Geben Sie ihren Namen ein");
namePanel.add(tf);
namePanel.setBorder(BorderFactory.createEtchedBorder());
getContentPane().add("North", namePanel);
//Monatsliste hinzufügen
JList list = new JList(MONTHS);
list.setToolTipText("Wählen Sie ihren Geburtsmonat aus");
getContentPane().add("Center", new JScrollPane(list));
//Panel mit den Buttons hinzufügen
JPanel buttonPanel = new JPanel();
JButton button1 = new JButton("Metal");
button1.addActionListener(this);
button1.setToolTipText("Metal-Look-and-Feel aktivieren");
buttonPanel.add(button1);
JButton button2 = new JButton("Motif");
button2.addActionListener(this);
button2.setToolTipText("Motif-Look-and-Feel aktivieren");
buttonPanel.add(button2);
JButton button3 = new JButton("Windows");
button3.addActionListener(this);
button3.setToolTipText("Windows-Look-and-Feel aktivieren");
buttonPanel.add(button3);
buttonPanel.setBorder(BorderFactory.createEtchedBorder());
getContentPane().add("South", buttonPanel);
//Windows-Listener
addWindowListener(new WindowClosingAdapter(true));
}
public void actionPerformed(ActionEvent event)
{
String cmd = event.getActionCommand();
try {
//PLAF-Klasse auswählen
String plaf = "unknown";
if (cmd.equals("Metal")) {
plaf = "javax.swing.plaf.metal.MetalLookAndFeel";
} else if (cmd.equals("Motif")) {
plaf = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
} else if (cmd.equals("Windows")) {
plaf = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
}
//LAF umschalten
UIManager.setLookAndFeel(plaf);
SwingUtilities.updateComponetTreeUI(this);
} catch (UnsupportedLookAndFeelException e) {
System.err.println(e.toString());
} catch (ClassNotFoundException e) {
System.err.println(e.toString());
} catch (InstantiationException e) {
System.err.println(e.toString());
} catch (IllegalAccessException e) {
System.err.println(e.toString());
}
}
public static void main(String[] args)
{
Geburtsmonat frame = new Geburtsmonat();
34
35
frame.setLocation(100, 100);
frame.pack();
frame.setVisible(true);
}
}
Einige Besonderheiten von Swing
Unten befindet sich ein Panel mit drei Buttons, mit denen zwischen den drei vordefinierten
Look-and-Feels umgeschaltet werden kann
Die beiden Panels wurden mit einer Umrandung versehen, und alle aktiven Elemente
zeigen Tooltips an, wenn man mit dem Mauszeiger darauf zeigt.
Das Label besitzt neben seiner Beschriftung ein Icon.
1.2.2
Probleme des AWT
Da alle Fenster- und Dialogelemente von dem darunterliegenden Betriebssystem zur
Verfügung gestellt wurden, ist es sehr schwierig, plattformübergreifend ein einheitliches
Look-and-Feel zu realisieren.
Im AWT gibt es nur eine Grundmenge an Dialogelementen, mit denen sich aufwendige
grafische Benutzeroberflächen nicht oder nur mit sehr viel Zusatzaufwand realisieren
lassen.
1.2.3
Leichtgewichtige Komponenten
Im Gegensatz zum AWT benutzen Swing-Komponenten nur noch in sehr eingeschränkter
Weise plattformspezifische GUI-Ressourcen.
Ein Swing-Button unter Windows wird nicht mehr vom Windows-UI-Manager dargestellt,
sondern von Swing selbst gezeichnet
Der Grundstein für Komponenten, die von Java selbst erzeugt und dargestellt werden
können, wurde im JDK 1.1 mit der Einführung der Lightweight Components
(»leichtgewichtige« Komponenten) gelegt. Dabei wird die paint-Methode eines ComponentObjects nicht mehr an die betriebssystemspezifische Klasse weitergeleitet, sondern in den
Komponentenklassen überlagert und mit Hilfe grafischer Primitivoperationen selbst
implementiert.
Die im AWT vorhandenen Dialogelemente werden im Gegensatz dazu als Heavyweight
Components bezeichnet (»schwergewichtige« Komponenten).
Dass alle Komponenten selbst gezeichnet werden müssen, erfordert viel CPU-Leistung und
eine Menge Hauptspeicher.
1.2.4
Pluggable Look-and-Feel
Eine der auf den ersten Blick spektakulärsten Eigenschaften von Swing ist die Möglichkeit,
das Look-and-Feel (also das Aussehen und die Bedienung einer Anwendung) zur Laufzeit
umschalten zu können, z.B. zwischen Swing, Motif und Windows (Pluggable Look-and-Feel
).
1.2.5
Das Model-View-Controller-Prinzip
Neben den äußerlichen Qualitäten wurde auch die Architektur des Gesamtsystems
verbessert. Wichtigste "Errungenschaft" ist dabei das Model-View-Controller-Prinzip (kurz
MVC genannt). Anstatt den gesamten Code in eine einzelne Klasse zu packen, werden
beim MVC-Konzept drei unterschiedliche Bestandteile eines grafischen Elements sorgsam
unterschieden:
Das Modell enthält die Daten des Dialogelements und speichert seinen Zustand.
Der View ist für die grafische Darstellung der Komponente verantwortlich.
Der Controller wirkt als Verbindungsglied zwischen beiden. Er empfängt Tastatur- und
Mausereignisse und stößt die erforderlichen Maßnahmen zur Änderung von Model und
View an.
35
36
Das Modell enthält praktisch die gesamte Verarbeitungslogik der Komponente. Ein wichtiger
Aspekt ist dabei, dass ein Model mehrere Views gleichzeitig haben kann. Damit
Veränderungen des Modells in allen Views sichtbar werden, wird ein
Benachrichtigungsmechanismus implementiert, mit dem das Modell die zugeordneten
Views über Änderungen informiert.
Bei den Swing-Dialogelementen wird eine vereinfachte Variante von MVC verwendet, die
auch als Model-Delegate-Prinzip bezeichnet wird. Hierbei wird die Funktionalität von View
und Controller in einem UI Delegate zusammengefasst.
1.2.6
Container und Menüs
1.2.6.1
Hauptfenster
1.2.6.1.1
Beispiel MDI
/* Mdi.java */
class DesktopFrame extends JFrame
{
private JDesktopPane desk;
public DesktopFrame()
{
super("DesktopFrame");
this.desk = new JDesktopPane();
desk.setDesktopManager(new DefaultDesktopManager());
setContentPane(desk);
addWindowListener(new WindowClosingAdapter());
}
public void addChild(JInternalFrame child, int x, int y)
{
child.setLocation(x, y);
child.setSize(200, 150);
child.setDefaultCloseOperation(
JInternalFrame.DISPOSE_ON_CLOSE
);
desk.add(child);
child.setVisible(true);
}
}
class ChildFrame
extends JInternalFrame
{
public ChildFrame(String title)
{
super("Child " + title, true, true);
setIconifiable(true);
setMaximizable(true);
setBackground(Color.green);
}
}
public class Mdi
{
public static void main(String[] args)
{
//Desktop erzeugen
DesktopFrame desktop = new DesktopFrame();
desktop.setLocation(100, 100);
desktop.setSize(400, 300);
desktop.setVisible(true);
//Zwei ChildFrames hinzufügen
desktop.addChild(new ChildFrame("1"), 10, 10);
desktop.addChild(new ChildFrame("2"), 20, 20);
}
}
36
37
1.2.6.1.2
JFrame
1.2.6.1.2.1
Struktur
Ein bedeutender Unterschied zwischen den AWT- und Swing-Hauptfenstern besteht in ihrer
Komponentenstruktur und den sich daraus ergebenden Unterschieden in der Bedienung.
Während die Komponenten eines AWT-Fensters direkt auf dem Fenster plaziert werden,
besitzt ein Swing-Hauptfenster eine einzige Hauptkomponente, die alle anderen
Komponenten aufnimmt.
Diese Hauptkomponente wird als RootPane bezeichnet und ist vom Typ JRootPane. Sie
übernimmt die Rolle einer Verwaltungsinstanz für alle anderen Komponenten des
Hauptfensters. Eine RootPane enthält folgende Komponenten:
 Eine aus Container abgeleitete GlassPane und
 eine aus JLayeredPane abgeleitete LayeredPane.
Die LayeredPane enthält ihrerseits zwei Unterkomponenten:
 Eine aus Container abgeleitete ContentPane und
 Eine aus JMenuBar abgeleitete Menüleiste.
Dabei ergibt sich folgende Struktur:
Die GlassPane ist normalerweise durchsichtig und wird meist nicht zur Grafikausgabe
benutzt. Sie könnte dann verwendet werden, wenn Effekte erzielt werden sollen, die das
Fenster als Ganzes betreffen (und nicht seine einzelnen Dialogelemente). Eine
(beispielsweise von JInternalFrame genutzte) Funktion besteht darin, Mausereignisse
abzufangen, bevor sie an andere Komponenten weitergegeben werden.
Die LayeredPane enthält das Menü und die Dialogelemente der Anwendung.
1.2.6.1.2.2
Erzeugung
Die RootPane, und mit ihr die darin enthaltene GlassPane, LayeredPane und ContentPane,
werden beim Anlegen des Fensters automatisch erzeugt (einzig die Menüleiste bleibt
standardmäßig leer).
1.2.6.1.2.3
Zugriff
Alle Hauptfenster implementieren das Interface RootPaneContainer, das den Zugriff auf die
RootPane vereinfacht. Einige seiner Methoden sind:
public JRootPane getRootPane()
37
38
public Container getContentPane()
public JLayeredPane getLayeredPane()
public Component getGlassPane()
javax.swing.RootPaneContainer
Um auf einem Hauptfenster Komponenten zu plazieren, ist es also nicht nötig, zunächst mit
getRootPane die RootPane, dann mit getLayeredPane die LayeredPane und schließlich mit
getContentPane die ContentPane zu beschaffen, sondern es kann direkt getContentPane
aufgerufen werden.
Das Einfügen und Anordnen von Dialogelementen auf einem Hauptfenster erfolgt über
dessen ContentPane. Die Aufrufe von add und setLayout werden damit nicht direkt auf dem
Fenster ausgeführt, sondern auf dessen ContentPane, die über einen Aufruf von
getContentPane beschafft werden kann.
1.2.6.1.3
JInternalFrame
1.2.6.1.3.1
Konzept
Bei vielen Programmen ist es üblich, dass sie ein einziges Hauptfenster besitzen und ihre
zahlreichen, gleichzeitig geöffneten Kindfenster innerhalb dieses Hauptfensters anordnen.
Diese unter Windows als MDI (Multiple Document Interface) bezeichnete Technik ist bei
bestimmten Typen von Anwendungen mittlerweile weitverbreitet (z.B. bei
Textverarbeitungen, Grafikprogrammen oder Entwicklungsumgebungen).
1.2.6.1.3.2
Der Desktop
Um die Kindfenster zu verwalten, wird die vordefinierte ContentPane durch eine Instanz der
Klasse JDesktopPane ersetzt. Diese Klasse besitzt einen DesktopManager, der für die
Verwaltung der Kindfenster zuständig ist. Der DesktopManager wird beispielsweise
benachrichtigt (und führt alle dazu erforderlichen Aktionen aus), wenn ein Kindfenster
verkleinert, vergrößert oder verschoben werden soll.
Der DesktopManager ist ein Interface, das eine Vielzahl von Methoden enthält. Mit der
Klasse DefaultDesktopManager gibt es eine Standardimplementierung, die für viele Zwecke
ausreichend ist.
1.2.6.1.3.3
Die Kindfenster
Die Kindfenster werden aus JInternalFrame abgeleitet.
JInternalFrame implementiert das Interface RootPaneContainer.
Mit setDefaultCloseOperation wird angegeben, wie das Fenster sich beim Schließen
verhalten soll. Hier kann eine der Konstanten DO_NOTHING_ON_CLOSE,
HIDE_ON_CLOSE oder DISPOSE_ON_CLOSE angegeben werden.
1.2.6.1.4
Beispiel SplashScreen
swing
SplashScreen
nur elektronisch
1.2.6.1.5
JWindow
Sie ist aus Window abgeleitet und dient wie diese dazu, ein rahmenloses Fenster zu
erzeugen, das an beliebiger Stelle und in beliebiger Größe auf dem Bildschirm plaziert
werden kann.
38
39
Ebenso wie JFrame besitzt auch JWindow eine RootPane mit der im vorigen Abschnitt
beschriebenen Struktur.
1.2.6.1.6
JDialog
Die aus Dialog abgeleitete Klasse JDialog besitzt denselben strukturellen Aufbau wie
JFrame und JWindow.
Als owner sollte der Aufrufer dem Konstruktor das Fenster übergeben, zu dem der Dialog
logisch gehört.
1.2.6.1.7
Beispiel Quartal
Swing
Quartal
Zeigt die Verwendung von JOptionPane
nur elektronisch
1.2.6.1.8
JOptionPane
Eine weitere Möglichkeit, Swing-Dialoge zu erzeugen, steht mit der Klasse JOptionPane zur
Verfügung. Diese ist in der Lage, einfache Dialoge, die lediglich ein Icon und einen Text
oder ein Eingabefeld und eine Auswahl der Buttons "Yes", "No" und "Cancel" enthalten, mit
einem einzigen Aufruf einer statischen Methode zu erzeugen.
1.2.6.1.9
JApplet
Die Klasse JApplet ist eine einfache Erweiterung von java.applet.Applet. Sie dient zur
Entwicklung von Applets, die Swing-Dialogelemente zur Gestaltung der Oberfläche
verwenden.
Wie bei den anderen Hauptfenstern ist auch hier der wichtigste Unterschied, dass JApplet
die beschriebene RootPane-Struktur realisiert.
39
40
1.2.6.2
Menüs
1.2.6.2.1
Einfache Menüs
1.2.6.2.1.1
Beispiel SimpleMenue
Swing
SimpleMenue
/* SimpleMenue.java */
public class SimpleMenue
extends JFrame
implements ActionListener
{
public SimpleMenue()
{
super("Swing-Menütest");
addWindowListener(new WindowClosingAdapter());
JMenuBar menubar = new JmenuBar();
menubar.add(createFileMenu());
setJMenuBar(menuBar);
}
public void actionPerformed(ActionEvent event)
{
System.out.println(event.getActionCommand());
}
//---Private Methoden--------------private JMenu createFileMenu()
{
JMenu menu = new JMenu("Datei");
menu.setMnemonic('D');
JMenuItem mi;
//Öffnen
mi = new JMenuItem("Oeffnen", 'f');
setCtrlAccelerator(mi, 'O');
mi.addActionListener(this);
menu.add(mi);
//Speichern
mi = new JMenuItem("Speichern", 'p');
setCtrlAccelerator(mi, 'S');
mi.addActionListener(this);
menu.add(mi);
//Separator
menu.addSeparator();
//Beenden
mi = new JMenuItem("Beenden", 'e');
mi.addActionListener(this);
menu.add(mi);
return menu;
}
private void setCtrlAccelerator(JMenuItem mi, char acc)
{
KeyStroke ks = KeyStroke.getKeyStroke(
acc, Event.CTRL_MASK
);
mi.setAccelerator(ks);
}
public static void main(String[] args)
{
SimpleMenue frame = new SimpleMenue();
frame.setLocation(100, 100);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
Alt D erzeugt DropDownMenue
f öffnet File
Ctrl O öffnet File auch ohne DropDownMenue
40
41
1.2.6.2.1.2
Grundlagen von Swing-Menüs
JMenuBar
In Swing können alle Hauptfenster mit Ausnahme von JWindow eine Menüleiste haben.
Dabei handelt es sich um eine Instanz der Klasse JMenuBar, die dem Hauptfenster durch
Aufruf von addJMenuBar hinzugefügt wird.
JMenu
Die einzelnen Menüs einer Menüleiste sind Instanzen der Klasse JMenu, die aus
JMenuItem abgeleitet ist (Composite-Pattern).
Mit addSeparator wird eine Trennlinie hinter dem letzten Menüpunkt angefügt.
JMenuItem
Die Klasse JMenuItem repräsentiert Menüeinträge, also Elemente, die sich in einem Menü
befinden. Dabei handelt es sich um Texte, die wahlweise mit einem Icon oder einem
Häkchen versehen werden können.
Der an den Konstruktor übergebene String text legt den Menütext fest. Wahlweise kann
zusätzlich ein Icon übergeben werden, das neben dem Menütext angezeigt wird.
Mit setMnemonic wird das mnemonische Kürzel des Menüeintrags festgelegt. Das ist ein
(unterstrichen dargestellter) Buchstabe innerhalb des Menütexts, der bei geöffnetem Menü
gedrückt werden kann, um den Menüeintrag per Tastatur aufzurufen.
Die als Acceleratoren oder Beschleunigertasten bezeichneten Tasten können auch dann
verwendet werden, wenn das entsprechende Menü nicht geöffnet ist. Beschleuniger werden
mit setAccelerator zugewiesen.
Mit den Methoden setEnabled und getEnabled kann auf den Aktivierungszustand des
Menüeintrags zugegriffen werden. Wird false an setEnabled übergeben, wird der Eintrag
deaktiviert, andernfalls aktiviert.
1.2.6.2.2
Weitere Möglichkeiten
1.2.6.2.2.1
Beispiel Extended Menue ( funktioniert nicht mit VisualCafe)
Achtung Fehler bei Verwendung von Visual Cafe :
Im JDK 1.3 arbeitet das Programm unter Windows fehlerhaft. Nach einmaligem Anklicken
des Menüeintrags »Sicherheit« kann das Untermenü »Tools« nicht mehr per Maus
aufgerufen werden, weil im Event-Thread eine NullPointerException ausgelöst wird. Die
Tastaturbedienung ist dagegen weiterhin möglich. Dieser seit dem RC2 bekannte Fehler
wurde leider bis zur endgültigen Version des JDK 1.3 nicht mehr behoben.
JDK1.1-1.3
/* ExtendedMenue.java */
public class ExtendedMenue
extends JFrame
{
public ExtendedMenue()
{
super("Extended Menue");
addWindowListener(new WindowClosingAdapter());
JMenuBar menubar = new JMenuBar();
menubar.add(createExtrasMenu());
setJMenuBar(menubar);
}
//---Private Methoden--------------private JMenu createExtrasMenu()
{
JMenu ret = new JMenu("Extras");
41
42
ret.setMnemonic('X');
JMenuItem mi;
//Tools-Untermenü
ret.add(createToolsSubMenu());
//Separator
ret.addSeparator();
//Statuszeile und Buttonleiste
mi = new JCheckBoxMenuItem("Statuszeile");
mi.setMnemonic('z');
((JCheckBoxMenuItem)mi).setState(true);
ret.add(mi);
mi = new JCheckBoxMenuItem("Buttonleiste");
mi.setMnemonic('B');
ret.add(mi);
//Separator
ret.addSeparator();
//Offline, Verbinden, Anmelden
ButtonGroup bg = new ButtonGroup();
mi = new JRadioButtonMenuItem("Offline", true);
mi.setMnemonic('O');
ret.add(mi);
bg.add(mi);
mi = new JRadioButtonMenuItem("Verbinden");
mi.setMnemonic('V');
ret.add(mi);
bg.add(mi);
mi = new JRadioButtonMenuItem("Anmelden");
mi.setMnemonic('A');
ret.add(mi);
bg.add(mi);
//Separator
ret.addSeparator();
//Sicherheit
mi = new JMenuItem(
"Sicherheit",
new ImageIcon("lock.gif")
);
mi.setMnemonic('S');
mi.setHorizontalTextPosition(JMenuItem.LEFT);
ret.add(mi);
return ret;
}
private JMenu createToolsSubMenu()
{
JMenu ret = new JMenu("Tools");
ret.setMnemonic('T');
ret.add(new JMenuItem("Rechner",
ret.add(new JMenuItem("Editor",
ret.add(new JMenuItem("Browser",
ret.add(new JMenuItem("Zipper",
ret.add(new JMenuItem("Snapper",
ret.add(new JMenuItem("Viewer",
return ret;
}
'R'));
'E'));
'B'));
'Z'));
'S'));
'V'));
public static void main(String[] args)
{
ExtendedMenue frame = new ExtendedMenue();
frame.setLocation(100, 100);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
1.2.6.2.2.2
Untermenüs
Da Menu aus MenuItem abgeleitet ist, kann an die Methode add der Klasse Menu auch eine
Instanz der Klasse Menu übergeben werden. Auf diese Weise lassen sich Menüs
schachteln.
Der Name des Untermenüs erscheint dann an der Einfügestelle, und mit einem kleinen Pfeil
wird angezeigt, dass es sich um ein Untermenü handelt.
1.2.6.2.2.3
Icons in Menüeinträgen
42
43
Einem Menüeintrag kann auch ein Icon zugeordnet werden. Icon ist ein Interface, das die
abstrakten Eigenschaften eines Icons definiert. Es besitzt eine Implementierung ImageIcon,
mit der sehr einfach aus einer gif- oder jpeg-Datei ein Icon erzeugt werden kann:
1.2.6.2.2.4
Checkboxes und Radiobuttons in Menüeinträgen
Die Kontrolle des Zustands der Buttons erfolgt mit einem ButtonGroup-Objekt. Es wird vor
dem Erzeugen der Menüeinträge angelegt, und jeder JRadioButtonMenuItem wird mit add
hinzugefügt:
Das ButtonGroup-Objekt sorgt automatisch dafür, dass zu jedem Zeitpunkt genau ein
Eintrag selektiert ist.
1.2.6.2.3
Kontextmenue
1.2.6.2.3.1
Beispiel Kontextmenue
nur elektronisch
1.2.6.2.3.2
Kontextmenüs
In Swing-Programmen werden Kontextmenüs mit Hilfe der Klasse JPopupMenu erzeugt.
Um das Menü anzuzeigen, ist show aufzurufen. Die dazu erforderlichen Koordinaten
gewinnt man am besten aus der aktuellen Position des Mauszeigers, die im Mausereignis
mitgeliefert wird.
Die zum Aktivieren eines Kontextmenüs erforderliche Mausaktion kann von Plattform zu
Plattform unterschiedlich sein. Die portabelste Lösung besteht darin, einen MouseListener
auf der Komponente zu registrieren, und bei jedem Mausereignis mit isPopupTrigger
abzufragen, ob es sich um eine Aktion zum Aufrufen eines Kontextmenüs handelte.
1.2.7
Swing Komponenten
1.2.7.1
Einfache Swing Komponenten
1.2.7.1.1
Beispiel Swing Components
Swing
SwingComponents
/* SwingComponents.java */
public class SwingComponents
extends JFrame
implements ActionListener, CaretListener, AdjustmentListener, ChangeListener
{
// Fuer Liste
static final String[] DATA = {
"Hund", "Katze", "Meerschweinchen", "Tiger", "Maus",
"Fisch", "Leopard", "Schimpanse", "Kuh", "Pferd",
"Reh", "Huhn", "Marder", "Adler", "Nilpferd"
};
private JList list;
// Fuer Combo Box
private static final String[] COLORS = {
"rot", "grün", "blau", "gelb"
};
// Fuer Radio Buttons
private ButtonGroup group = new ButtonGroup();
// Fuer Scrollbar
43
44
private
private
private
private
private
private
private
JPanel coloredPanel;
JScrollBar sbEast;
JScrollBar sbSouth;
JSlider slWest;
JSlider slNorth;
int
blue = 100;
int
red = 200;
public SwingComponents()
{
super("Swing Components");
addWindowListener(new WindowClosingAdapter());
Container cp = getContentPane();
cp.setLayout(new GridLayout(4, 2));
// Text
JPanel tp = new JPanel();
tp.setBorder(BorderFactory.createLineBorder(Color.blue));
// Textfields
JTextField tf;
//Linksbündiges Textfeld mit "Hello, world"
tf = new JTextField("Hello, world");
tp.add(tf);
//Leeres Textfeld mit 20 Spalten
tf = new JTextField(20);
tp.add(tf);
//Textfeld mit "Hello, world" und 20 Spalten
tf = new JTextField("Hello, world", 20);
tf.addActionListener(this);
tf.addCaretListener(this);
tp.add(tf);
// Textarea
JTextArea ta = new JTextArea("Hello world \nHallo Welt \nHuhu \ng \nh \nEnde", 5,
30);
ta.setTabSize(4);
ta.setLineWrap(true);
ta.setWrapStyleWord(true);
tp.add(new JScrollPane(ta));
cp.add(tp);
// Labels
JPanel lp = new JPanel();
lp.setBorder(BorderFactory.createLineBorder(Color.blue));
lp.setLayout(new GridLayout(5, 1));
JLabel label;
//Standardlabel
label = new JLabel("Standard-Label");
lp.add(label);
//Label mit Icon
label = new JLabel(
"Label mit Icon",
new ImageIcon("lock.gif"),
JLabel.CENTER
);
lp.add(label);
//Nur-Icon
label = new JLabel(new ImageIcon("lock.gif"));
lp.add(label);
//Icon auf der rechten Seite
label = new JLabel(
"Label mit Icon rechts",
new ImageIcon("lock.gif"),
JLabel.CENTER
);
label.setHorizontalTextPosition(JLabel.LEFT);
lp.add(label);
//Label rechts unten
label = new JLabel("Label rechts unten");
label.setHorizontalAlignment(JLabel.RIGHT);
label.setVerticalAlignment(JLabel.BOTTOM);
lp.add(label);
cp.add(lp);
// Buttons
JPanel bp = new JPanel();
bp.setBorder(BorderFactory.createLineBorder(Color.blue));
//OK-Button
JButton okButton = new DefaultButton("OK", getRootPane());
okButton.addActionListener(this);
44
45
bp.add(okButton);
//Abbrechen-Button
JButton cancelButton = new CancelButton("Abbrechen");
cancelButton.addActionListener(this);
bp.add(cancelButton);
//Hilfe-Button
JButton helpButton = new JButton("Hilfe");
helpButton.setMnemonic('H');
helpButton.addActionListener(this);
bp.add(helpButton);
//Test-Button
JButton testButton = new JButton("Test", new ImageIcon("testicon.gif"));
testButton.setMnemonic('T');
testButton.addActionListener(this);
testButton.setHorizontalTextPosition(JButton.LEFT);
testButton.setForeground(Color.blue);
testButton.setBackground(Color.green);
bp.add(testButton);
//Panel hinzufügen
cp.add(bp);
// CheckBox
JPanel cbp = new JPanel();
cbp.setBorder(BorderFactory.createLineBorder(Color.blue));
cbp.setLayout(new GridLayout(3, 1));
for (int i = 1; i <= 3; ++i) {
JCheckBox cb = new JCheckBox("Checkbox" + i, i == 2);
cb.addItemListener(new CheckBoxItemListener());
cbp.add(cb);
}
cp.add(cbp);
// Radio Buttons
//RadioButton-Panel
JPanel rbp = new JPanel();
rbp.setBorder(BorderFactory.createLineBorder(Color.blue));
rbp.setLayout(new GridLayout(4, 1));
for (int i = 1; i <= 3; ++i) {
JRadioButton rb = new JRadioButton("RadioButton" + i, i == 2);
rb.setActionCommand(rb.getText());
rbp.add(rb);
group.add(rb);
}
//Selektion-Button
JButton button = new JButton("Selektion");
button.addActionListener(this);
rbp.add(button);
cp.add(rbp);
// Liste
//List-Panel
JPanel lip = new JPanel();
lip.setBorder(BorderFactory.createLineBorder(Color.blue));
lip.setLayout(new BorderLayout());
list = new JList(new DefaultListModel());
((DefaultListModel)list.getModel()).addElement("Hans");
((DefaultListModel)list.getModel()).addElement("Franz");
((DefaultListModel)list.getModel()).addElement("Heinz");
list.setSelectionMode(
ListSelectionModel.MULTIPLE_INTERVAL_SELECTION
);
list.setSelectedIndex(2);
lip.add(new JScrollPane(list), "Center");
//Ausgabe-Button
JButton ab = new JButton("Ausgabe");
ab.addActionListener(this);
lip.add(ab, "South");
cp.add(lip);
// Combo Box
//Combo Box-Panel
JPanel cop = new JPanel();
cop.setBorder(BorderFactory.createLineBorder(Color.blue));
cop.setLayout(new BorderLayout());
for (int i = 1; i <= 2; ++i) {
JPanel p = new JPanel();
p.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 2));
p.add(new JLabel("Farbe " + i + ":"));
JComboBox combo = new JComboBox(COLORS);
45
46
combo.setEditable(i == 1);
combo.addItemListener(new ComboBoxItemListener());
p.add(combo);
cop.add(p, i == 1 ? "North" : "Center");
}
cp.add(cop);
// Scrollbar und Slider
//Scrollbar-Panel
JPanel sbp = new JPanel();
sbp.setBorder(BorderFactory.createLineBorder(Color.blue));
sbp.setLayout(new BorderLayout());
//Vertikaler Schieberegler
sbEast = new JScrollBar(JScrollBar.VERTICAL, 0, 10, 0, 255);
sbEast.addAdjustmentListener(this);
sbp.add(sbEast, "East");
//Horizontaler Schieberegler
sbSouth = new JScrollBar(JScrollBar.HORIZONTAL, 0, 10, 0, 255);
sbSouth.addAdjustmentListener(this);
sbp.add(sbSouth, "South");
//Vertikaler Slider
slWest = new JSlider(JSlider.VERTICAL, 0, 255, 0);
slWest.setMajorTickSpacing(50);
slWest.setMinorTickSpacing(10);
slWest.setPaintTicks(true);
slWest.setPaintLabels(true);
slWest.addChangeListener(this);
sbp.add(slWest, "West");
//Horizontaler Schieberegler
slNorth = new JSlider(JSlider.HORIZONTAL, 0, 255, 0);
slNorth.setMajorTickSpacing(100);
slNorth.setMinorTickSpacing(25);
slNorth.setPaintTicks(true);
slNorth.setPaintLabels(true);
slNorth.setSnapToTicks(true);
slNorth.addChangeListener(this);
sbp.add(slNorth, "North");
//Farbiges Panel
coloredPanel = new JPanel();
coloredPanel.setBackground(new Color(red, 0, blue));
sbp.add(coloredPanel, "Center");
cp.add(sbp);
}
public void actionPerformed(ActionEvent event)
{
String ec = event.getActionCommand();
if ((ec.equals("OK"))||
(ec.equals("Abbrechen"))||
(ec.equals("Test"))||
(ec.equals("Hilfe")))
{
System.out.println(ec);
}
else
if (ec.equals("Ausgabe")) {
System.out.println("---");
ListModel lm = list.getModel();
int[] sel = list.getSelectedIndices();
for (int i = 0; i < sel.length; ++i) {
String value = (String)lm.getElementAt(sel[i]);
System.out.println(" " + value);
}
}
else
if (ec.equals("Selektion")) {
ButtonModel selected = group.getSelection();
System.out.print("Selektiert: ");
if (selected != null) {
System.out.println(selected.getActionCommand());
}
}
else
{
JTextField tf = (JTextField)event.getSource();
System.out.println("---ActionEvent---");
System.out.println(tf.getText());
System.out.println(tf.getSelectedText());
System.out.println(tf.getSelectionStart());
System.out.println(tf.getSelectionEnd());
System.out.println(tf.getCaretPosition());
46
47
}
}
public void caretUpdate(CaretEvent event)
{
System.out.println("---CaretEvent---");
System.out.println(event.getDot());
System.out.println(event.getMark());
}
public void adjustmentValueChanged(AdjustmentEvent event)
{
JScrollBar sb = (JScrollBar)event.getSource();
if (sb == sbEast) {
blue = event.getValue();
} else {
red = event.getValue();
}
coloredPanel.setBackground(new Color(red, 0, blue));
coloredPanel.repaint();
if (!sb.getValueIsAdjusting()) {
System.out.println("Color : (" + red + ",0," + blue + ")");
}
}
public void stateChanged(ChangeEvent event)
{
JSlider sl = (JSlider)event.getSource();
if (sl == slWest) {
blue = sl.getValue();
} else {
red = sl.getValue();
}
coloredPanel.setBackground(new Color(red, 0, blue));
coloredPanel.repaint();
if (!sl.getValueIsAdjusting()) {
System.out.println("(" + red + ",0," + blue + ")");
}
}
public static void main(String[] args)
{
SwingComponents frame = new SwingComponents();
frame.setLocation(100, 100);
frame.setSize(800, 600);
frame.setVisible(true);
}
class CheckBoxItemListener
implements ItemListener
{
public void itemStateChanged(ItemEvent e)
{
JCheckBox cb = (JCheckBox)e.getSource();
int change = e.getStateChange();
if (change == ItemEvent.SELECTED) {
System.out.println(cb.getText() + ": SELECTED");
} else if (change == ItemEvent.DESELECTED) {
System.out.println(cb.getText() + ": DESELECTED");
}
}
}
class ComboBoxItemListener
implements ItemListener
{
public void itemStateChanged(ItemEvent e)
{
JComboBox cb = (JComboBox)e.getSource();
System.out.println("ComboBox : " + cb.getSelectedItem());
}
}
}
/* CancelButton.java */
import java.awt.event.*;
//import javax.swing.*;
import com.sun.java.swing.*;
public class CancelButton
extends JButton
47
48
{
public CancelButton(String title)
{
super(title);
ActionListener al = new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
String cmd = event.getActionCommand();
if (cmd.equals("PressedESCAPE")) {
doClick(1000);
}
}
};
registerKeyboardAction(
al,
"PressedESCAPE",
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
JButton.WHEN_IN_FOCUSED_WINDOW
);
}
}
/* DefaultButton.java */
//import javax.swing.*;
import com.sun.java.swing.*;
public class DefaultButton
extends JButton
{
public DefaultButton(String title, JRootPane rootpane)
{
super(title);
rootpane.setDefaultButton(this);
}
}
1.2.7.1.2
Label und Textfelder
1.2.7.1.2.1
JLabel
Ein JLabel ist ein Dialogelement zur Anzeige einer Beschriftung innerhalb eines GUIContainers. Es besitzt einen Text und ein Icon, die in beliebiger Anordnung dargestellt
werden können.. Auf Benutzereingaben reagiert ein JLabel nicht.
1.2.7.1.2.2
JTextField
Die Klasse JTextField ist das Swing-Pendant zur AWT-Klasse TextField und stellt ein
einzeiliges Textfeld zur Eingabe von Daten dar.
Die wichtigsten registrierbaren Listener sind ActionListener und CaretListener:
Ein ActionListener wird immer dann aufgerufen, wenn im Eingabefeld [ENTER] gedrückt
wird, ein CaretListener, wenn sich die Position der Einfügemarke geändert hat.
(Einen TextListener wie bei der Klasse TextField gibt es bei den aus JTextComponent
abgeleiteten Klassen nicht. Will die Anwendung über jede Änderung in einem Textfeld
unterrichtet werden, muss sie einen DocumentListener auf dessen Modell registrieren. Das
Modell eines Textfeldes ist eine Instanz der Klasse Document und kann durch Aufruf von
getDocument beschafft werden.)
1.2.7.1.2.3
JPasswordField
JPasswordField ist eine Spezialisierung von JTextField zur Eingabe von Passworten. Der
Hauptunterschied zu JTextField besteht darin, dass der eingegebene Text nicht angezeigt,
sondern statt dessen für jedes Zeichen ein Sternchen ausgegeben wird. Durch Aufruf von
48
49
setEchoChar kann auch ein anderes Ausgabezeichen ausgewählt werden. Des weiteren
sind die beiden Zwischenablagefunktionen Kopieren und Ausschneiden deaktiviert, um zu
verhindern, dass der Text aus einem Passwortfeld in ein anderes Feld kopiert oder an ein
anderes Programm übergeben werden kann.
1.2.7.1.2.4
JTextArea
JTextArea ist eine Komponente zur Anzeige und Eingabe von mehrzeiligen Texten. Wie die
AWT-Klasse TextArea dient sie dazu, unformatierte Texte zu bearbeiten. Diese können
zwar Zeilenumbrüche und Tabulatoren, nicht aber unterschiedliche Schriften, Farben oder
grafische Elemente enthalten (für diesen Zweck gibt es die Klassen JEditorPane und
JTextPane).
Anders als TextArea ist JTextArea nicht in der Lage, Text automatisch zu scrollen, wenn er
zu lang oder zu breit für den verfügbaren Bildschirmausschnitt ist. Wird das gewünscht,
muss das Textfeld in eine Komponente des Typs JScrollPane eingebettet werden. An Stelle
der JTextArea ist dem Dialog dann die JScrollPane hinzuzufügen.
1.2.7.1.3
Buttons
1.2.7.1.3.1
JButton
Swing-Buttons sind Instanzen der Klasse JButton und dienen dazu, Schaltflächen zu
erzeugen.
Wird ein JButton per Mausklick betätigt, sendet er ein ActionEvent an alle registrierten
Listener. Durch Aufruf von doClick kann ein Buttonklick auch programmgesteuert ausgelöst
werden.
1.2.7.1.3.2
JCheckBox
Die Klasse JCheckBox stellt einen Button dar, der vom Anwender wahlweise an- oder
ausgeschaltet werden kann. Er wird meist verwendet, um boolesche Werte auf einer GUIOberfläche darzustellen. JCheckBox ist von der Klasse JToggleButton abgeleitet, die als
Abstraktion von Buttons, die ihren Zustand ändern können, auch Basisklasse von
JRadioButton ist.
Das Ankreuzfeld einer JCheckBox ist also ihr (automatisch angezeigtes) Icon. Durch Aufruf
von setHorizontalTextPosition und Übergabe der Konstante LEFT können Ankreuzfeld und
Text vertauscht werden.
Wie ein JButton sendet eine JCheckBox bei jeder Betätigung ein ActionEvent an registrierte
Listener. Zudem wird bei Zustandsänderungen ein ItemEvent versendet, auf das ein
ItemListener reagieren kann.
1.2.7.1.3.3
JRadioButton
Die Klasse JRadioButton stellt einen Button dar, der wahlweise an- oder ausgeschaltet
werden kann. Anders als bei JCheckBox ist in einer Gruppe von Radiobuttons allerdings
immer nur ein Button zur Zeit aktiviert, alle anderen sind deaktiviert.
Um die Buttons zu gruppieren, ist eine ButtonGroup zu instanzieren, und die Buttons sind
durch Aufruf von add hinzuzufügen. Mit getSelection kann auf das ButtonModel des
selektierten Elements zugegriffen werden, getElements liefert alle Buttons der Gruppe.
1.2.7.1.4
Listen und Comboboxen
49
50
1.2.7.1.4.1
JList
Die Klasse JList dient dazu, Listen von Werten darzustellen, aus denen der Anwender einen
oder mehrere Einträge auswählen kann. Im Gegensatz zur AWT-Klasse List kann sie nicht
nur Strings, sondern beliebige Objekte enthalten. Auch die Darstellung der Listenelemente
auf dem Bildschirm kann weitgehend frei gestaltet werden.
Wird die Selektion geändert, versendet eine JList ein ListSelectionEvent an alle registrierten
Listener. Jede Selektionsänderung führt zum Aufruf der Methode valueChanged.
Den Listeninhalt dynamisch verändern
Etwas mehr Aufwand als beim AWT-Pendant muss getrieben werden, wenn der Inhalt einer
JList nach der Instanzierung modifiziert werden soll. In diesem Fall kann nicht mehr mit dem
automatisch erzeugten Listenmodel gearbeitet werden, sondern es muss selbst eines
erzeugt werden. Das Modell einer JList muss stets das Interface ListModel implementieren
und der Liste durch Versenden eines ListDataEvent jede Datenänderung mitteilen. Eine für
viele Zwecke ausreichende Implementierung steht mit der Klasse DefaultListModel zur
Verfügung. Ihre Schnittstelle entspricht der Klasse Vector und alle erforderlichen
Änderungsbenachrichtigungen werden automatisch verschickt.
Soll eine Liste mit einem benutzerdefinierten Modell arbeiten, wird dieses einfach manuell
erzeugt und an den Konstruktor übergeben. Alle Einfügungen, Löschungen und Änderungen
von Daten werden dann an diesem Modell vorgenommen. Durch Aufruf von getModel kann
auf einfache Weise auf das Modell einer JList zugegriffen werden.
1.2.7.1.4.2
JComboBox
Eine JComboBox ist das Swing-Pendant zur AWT-Klasse Choice. Es stellt eine
Kombination aus Textfeld und Liste dar. Die Liste ist normalerweise unsichtbar und wird
vom Anwender nur dann geöffnet, wenn er einen Wert daraus auswählen will. Sie erlaubt
grundsätzlich nur einfache Selektion.
Ein wichtiges Merkmal einer JComboBox ist die Möglichkeit, das Textfeld editieren zu
können oder nicht. Ist das der Fall, kann der Anwender auch Werte eingeben, die nicht in
der Liste stehen; andernfalls ist er auf Listenwerte beschränkt. Mit den Methoden
setEditable und isEditable kann auf diese Eigenschaft zugegriffen werden:
Im Gegensatz zu JList stellt JComboBox auch einige Methoden zur Verfügung, mit denen
die Elemente der Liste dynamisch verändert werden können.
Mit addItem wird ein neues Element an das Ende der Liste angehängt, mit insertItemAt wird
es einer beliebigen Position eingefügt. removeItem entfernt das angegebene Element,
removeItemAt das Element mit dem angegebenen Index. removeAllItems entfernt alle
Elemente aus der Liste.
Jedesmal, wenn ein anderes Element selektiert wird, sendet eine JComboBox ein
ItemEvent an registrierte ItemListener (und zwar sowohl für das deselektierte als auch
für das selektierte Element). Nach Abschluß der Selektion oder wenn der Anwender in einer
editierbaren JComboBox einen Wert per Hand eingegeben hat, wird zusätzlich ein
ActionEvent an registrierte ActionListener versendet.
1.2.7.1.5
Quasi-analoge Komponenten
1.2.7.1.5.1
JScrollBar
JScrollBar ist die leichtgewichtige Swing-Variante der AWT-Klasse Scrollbar. Sie dient dazu,
mit Hilfe eines Schiebereglers einen Wert kontinuierlich innerhalb vorgegebener Grenzen
einzustellen.
50
51
JScrollBar stellt einige Methoden zur Verfügung, mit denen nach der Instanzierung auf die
numerischen Eigenschaften des Schiebereglers zugegriffen werden kann:
Mit getMinimum, getMaximum, setMinimum und setMaximum kann auf das Minimum und
Maximum des definierten Wertebereichs zugegriffen werden. getVisibleAmount liefert die
Ausdehnung des Schiebers, und mit setVisibleAmount kann diese gesetzt werden. Mit
getValue und setValue kann auf den aktuellen Wert des Schiebereglers zugegriffen werden.
getUnitIncrement gibt an, um welchen Betrag der Wert des Schiebereglers verändert wird,
wenn der Anwender einen der Pfeilbuttons betätigt. getBlockIncrement ermittelt analog dazu
den Betrag der Änderung, wenn zwischen Schieber und Pfeilbuttons geklickt wird. Mit
setUnitIncrement und setBlockIncrement können beide Werte auch verändert werden.
Wird der Wert einer JScrollBar verändert, sendet sie ein AdjustmentEvent an registrierte
Listener.
Mit der Methode getValueIsAdjusting() kann festgestellt werden, auf welche Weise der Wert
verändert wird. Sie gibt genau dann true zurück, wenn die Änderung Bestandteil einer Kette
von Änderungen ist, wenn also der Anwender den Schieber betätigt. Wurde die Änderung
dagegen durch einen Mausklick auf einen der Buttons oder auf die Fläche zwischen Buttons
und Schieber ausgelöst, liefert getValueIsAdjusting() den Wert false.
1.2.7.1.5.2
JSlider
Mit der Klasse JSlider werden ebenso wie mit JScrollBar Schieberegler erzeugt. Abgesehen
von den unterschiedlichen Oberflächen gibt es zwischen beiden Klassen zwei wichtige
konzeptionelle Unterschiede:
Ein JSlider kann eine Anzeigeskala mit grober und feiner Einteilung und Beschriftung
haben.
Ein JSlider kennt keine unterschiedlichen Schiebergrößen. Ihre Ausdehnung ist immer 1.
Mit setMajorTickSpacing wird der Abstand der großen Markierungen vorgegeben, mit
setMinorTickSpacing der Abstand der kleinen.
Damit die Anzeigeskala tatsächlich angezeigt wird, muss setPaintTicks aufgerufen und true
übergeben werden. Soll auch die Beschriftung angezeigt werden, muss zusätzlich
setPaintLabels mit true als Argument aufgerufen werden.
Ein Aufruf von setSnapToTicks (mit Übergabe von true als Argument) sorgt dafür, dass der
Schieber stets auf den Skalenmarkierungen einrastet. Zwischenpositionen können dann
nicht mehr angewählt werden.
Im Gegensatz zu JScrollPane sendet ein JSlider kein AdjustmentEvent, wenn sein Wert
verändert wird, sondern ein ChangeEvent (diese Klasse liegt im Paket javax.swing.event).
Wie bei JScrollPane kann mit getValueIsAdjusting festgestellt werden, ob die Änderung
Bestandteil einer Kette von Wertänderungen ist oder ob sie einzeln aufgetreten ist.
51
52
1.2.7.2
Jtree
1.2.7.2.1
Beispiel SelectionTree
/* SelectionTree.java */
import
import
import
import
import
java.awt.*;
java.awt.event.*;
javax.swing.*;
javax.swing.event.*;
javax.swing.tree.*;
public class SelectionTree
extends JFrame
{
TreeSelectionModel tsm = null;
public SelectionTree()
{
super("JTree 2");
addWindowListener(new WindowClosingAdapter());
//Einfaches TreeModel bauen
DefaultMutableTreeNode root, child, subchild;
root = new DefaultMutableTreeNode("Root");
for (int i = 1; i <= 5; ++i) {
String name = "Child-" + i;
child = new DefaultMutableTreeNode(name);
root.add(child);
for (int j = 1; j <= 3; ++j) {
subchild = new DefaultMutableTreeNode(name + "-" + j);
child.add(subchild);
}
}
//JTree erzeugen
JTree tree = new JTree(root)
tree.setRootVisible(true);
//Einfachselektion aktivieren
tsm = new DefaultTreeSelectionModel();
tsm.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.setSelectionModel(tsm);
//TreeSelectionListener hinzufügen
tree.addTreeSelectionListener(
new TreeSelectionListener()
{
52
53
public void valueChanged(TreeSelectionEvent event)
{
Treepath tp = event.getNewLeadSelectionPath();
if (tp != null) {
System.out.println(" Selektiert: " + tp.toString());
int[]rows = tsm.getSelectionRows();
for (int i = 0; i < rows.length; i++)
{
System.out.println("row " + rows[i]);
}
} else {
System.out.println(" Kein Element selektiert");
}
}
}
);
//JTree einfügen
Container cp = getContentPane();
cp.add(new JScrollPane(tree), "Center");
}
public static void main(String[] args)
{
try {
SelectionTree frame = new SelectionTree();
frame.setLocation(100, 100);
frame.setSize(250, 200);
frame.setVisible(true);
} catch (Exception e) {
}
}
}
1.2.7.2.2
Bäume
JTree dient zur Darstellung, Navigation und Bearbeitung baumartiger, hierarchischer
Datenstrukturen.
Ein einfaches Beispiel für derartige Baumstrukturen stellt etwa das Dateisystem unter UNIX
oder Windows dar. Es besteht aus einer Wurzel (dem
Root-Verzeichnis) und darin enthaltenen Unterverzeichnissen. Die Unterverzeichnisse
können ihrerseits weitere Unterverzeichnisse enthalten usw. Dadurch entsteht eine beliebig
tief geschachtelte Struktur von Verzeichnissen, die baumartig durchlaufen und bearbeitet
werden kann.
Im Umgang mit Bäumen haben sich folgende Begriffe eingebürgert:
Ein Element des Baums wird als Knoten bezeichnet.
Die in einem Knoten enthaltenen Elemente bezeichnet man als Unterknoten, manchmal
auch als Kindknoten.
Das übergeordnete Element eines Knotens bezeichnet man als Vaterknoten, manchmal
auch als Elternknoten.
Das Startelement des Baums wird Wurzel genannt.
Knoten, die keine Unterknoten haben, werden als Blätter bezeichnet.
Knoten, die sowohl Vater- als auch Unterknoten enthalten, bezeichnet man als innere
Knoten.
1.2.7.2.3
Interfaces für das Model-View Konzept
Auch der JTree basiert auf dem Model-View Konzept.
Der JTree erfragt die zur Darstellung und Navigation erforderlichen Daten immer bei seinem
TreeModel.
Ein TreeModel kapselt alle relevanten Informationen über die Struktur des Baums. Es liefert
auf Anfrage dessen Wurzel, stellt Informationen über einen bestimmten Knoten zur
Verfügung oder liefert dessen Unterknoten.
Damit der JTree mit seinem TreeModel bzw. den TreeNodes kommunizieren kann, muss
die Schnittstelle für TreeModel und TreeNode spezifiziert sein.
53
54
interface TreeNode
Defines the requirements for an object that can be used as a tree node in a JTree.
children()
Returns the children of the reciever as an Enumeration.
getAllowsChildren()
Returns true if the receiver allows children.
getChildAt(int childIndex)
Returns the child TreeNode at index childIndex.
getChildCount()
Returns the number of children TreeNodes the receiver contains.
getIndex(TreeNode node)
Returns the index of node in the receivers children.
getParent()
Returns the parent TreeNode of the receiver.
isLeaf()
Returns true if the receiver is a leaf.
interface MutableTreeNode
extends TreeNode
Defines the requirements for a tree node object that can change -- by adding or removing
child nodes, or by changing the contents of a user object stored in the node.
insert(MutableTreeNode child, int index)
Adds child to the receiver at index.
remove(MutableTreeNode node)
Removes node from the receiver.
remove(int index)
Removes the child at index from the receiver.
removeFromParent()
Removes the receiver from its parent.
setParent(MutableTreeNode newParent)
Sets the parent of the receiver to newParent.
setUserObject(Object object)
Resets the user object of the receiver to object.
interface TreeModel
The interface that defines a suitable data model for a JTree.
addTreeModelListener(TreeModelListener l)
Adds a listener for the TreeModelEvent posted after the tree changes.
getChild(Object parent, int index)
Returns the child of parent at index index in the parent's child array.
getChildCount(Object parent)
Returns the number of children of parent.
getIndexOfChild(Object parent, Object child)
Returns the index of child in parent.
getRoot()
Returns the root of the tree.
isLeaf(Object node)
Returns true if node is a leaf.
removeTreeModelListener(TreeModelListener l)
Removes a listener previously added with addTreeModelListener().
valueForPathChanged(TreePath path, Object newValue)
Messaged when the user has altered the value for the item identified by path to newValue.
1.2.7.2.4
Default-Implementationen der Interfaces
Mit DefaultMutableTreeNode steht eine recht flexible Implementierung von TreeNode zur
Verfügung, die auch Methoden zum Einfügen und Löschen von Knoten bietet.
Mit add wird ein neuer Kindknoten an das Ende der Liste der Unterknoten angefügt, mit
insert kann dies an einer beliebigen Stelle erfolgen. remove entfernt einen beliebigen und
removeAllChildren alle Kindknoten. Anwendungsbezogene Informationen werden in einem
54
55
UserObject gehalten, das direkt an den Konstruktor übergeben werden kann. Mit
setUserObject und getUserObject kann auch nach der Konstruktion noch darauf zugegriffen
werden. Das UserObject ist auch der Lieferant für die Knotenbeschriftung: jeder Aufruf von
toString wird an das UserObject weitergeleitet.
DefaultTreeModel ist eine einfache Implementierung des TreeModels.
1.2.7.2.5
Erzeugen eines Baumes
Die beiden wichtigsten Konstruktoren der Klasse JTree sind:
public JTree(TreeModel newModel)
public JTree(TreeNode root)
Der erste von beiden erwartet ein vordefiniertes TreeModel zur Darstellung der Elemente
des Baums.
An den zweiten Konstruktor wird lediglich die Wurzel des Baums übergeben. Sie wird
jedoch vom Konstruktor in das automatisch instanzierte DefaultTreeModel eingebettet.
1.2.7.2.6
Selektieren von Knoten
Konfiguration der Selektionsmöglichkeit
Das Selektieren von Knoten wird durch das TreeSelectionModel gesteuert.
Standardmäßig erlaubt ein JTree das Selektieren mehrerer Knoten. Soll die
Selektionsmöglichkeit auf einen einzelnen Knoten beschränkt werden, muss ein eigenes
TreeSelectionModel an setSelectionModel übergeben werden. Dazu kann eine Instanz der
Klasse DefaultTreeSelectionModel erzeugt und durch Aufruf von setSelectionMode und
Übergabe einer der Konstanten SINGLE_TREE_SELECTION,
CONTIGUOUS_TREE_SELECTION oder DISCONTIGUOUS_TREE_SELECTION
konfiguriert werden:
Abfragen der Selektion
Mit getSelectionPath wird das selektierte Element ermittelt. Bei aktivierter Mehrfachselektion
liefert die Methode das erste aller selektierten Elemente. Ist kein Knoten selektiert, wird null
zurückgegeben. getSelectionPaths gibt ein Array mit allen selektierten Knoten zurück.
getLeadSelectionPath liefert das zuletzt markierte Element.
(public TreePath getLeadSelectionPath()
Returns the path of the last node added to the selection.)
Alle beschriebenen Methoden liefern Objekte des Typs TreePath. Diese Klasse beschreibt
einen Knoten im Baum über den Pfad, der von der Wurzel aus
beschritten werden muss, um zu dem Knoten zu gelangen.
Mit getLastPathComponent kann das letzte Element dieses Pfads bestimmt werden.
Mit getPath kann der komplette Pfad ermittelt werden. An erster Stelle liegt dabei die Wurzel
des Baums, an letzter Stelle das selektierte Element:
Soll ermittelt werden, ob und welche Elemente im Baum selektiert sind, können die
Methoden isSelectionEmpty und isPathSelected aufgerufen werden:
(Alternativ zum TreePath kann auf die selektierten Elemente auch mit Hilfe ihrer internen
Zeilennummer zugriffen werden. Dazu besitzt jedes angezeigte Element im Baum eine
fortlaufende Nummer, die mit 0 bei der Wurzel beginnt und sich dann zeilenweise bis zum
letzten Element fortsetzt. Die zugehörigen Methoden heißen getSelectionRows und
getLeadSelectionRow. Abhängig davon, wie viele Knoten oberhalb eines bestimmten
55
56
Knotens sichtbar oder verdeckt sind, kann sich die Zeilennummer während der
Lebensdauer des Baums durchaus verändern.)
Dient der JTree zur Steuerung anderer Komponenten (etwa in explorerartigen Oberflächen),
muss das Programm meist unmittelbar auf Änderungen der Selektion durch den Anwender
reagieren. Dazu kann es einen TreeSelectionListener instanzieren und ihn mit
addTreeSelectionListener beim JTree registrieren.
Bei jeder Selektionsänderung wird dann die Methode valueChanged aufgerufen und
bekommt ein TreeSelectionEvent als Argument übergeben:
Dieses stellt unter anderem die Methoden getOldLeadSelectionPath und
getNewLeadSelectionPath zur Verfügung, um auf den vorherigen oder aktuellen
Selektionspfad zuzugreifen:
Verändern der Selektion
Die Selektion kann auch programmgesteuert verändert werden:
Mit clearSelection wird die Selektion vollständig gelöscht. Mit addSelectionPath und
addSelectionPaths kann die Selektion um ein einzelnes oder eine Menge von Knoten
erweitert werden. Mit setSelectionPath und setSelectionPaths werden - unabhängig von der
bisherigen Selektion - die als Argument
übergebenen Knoten selektiert.
Öffnen und Schließen der Knoten
Der Anwender kann die Knoten mit Maus- oder Tastaturkommandos öffnen oder schließen.
Dadurch werden die Unterknoten entweder sichtbar oder versteckt.
Das Programm kann diesen Zustand mit den Methoden isCollapsed und isExpanded
abfragen:
isExpanded liefert true, wenn der Knoten geöffnet ist, isCollapsed, wenn er geschlossen ist.
hasBeenExpanded gibt an, ob der Knoten überhaupt schon einmal geöffnet wurde. isVisible
gibt genau dann true zurück, wenn der Knoten sichtbar ist, d.h. wenn alle seine Elternknoten
geöffnet sind. Mit makeVisible kann ein Knoten sichtbar gemacht werden. Mit expandPath
kann er geöffnet und mit collapsePath geschlossen werden.
1.2.7.2.7
Beispiel MutableTree
Das folgende Programm zeigt einen Baum, der zunächst nur den Wurzelknoten enthält.
Dieser ist vom Typ DefaultMutableTreeNode und wird in ein explizit erzeugtes
DefaultTreeModel eingebettet, dass an den Konstruktor des JTree übergeben wird. Neben
dem JTree enthält das Programm drei Buttons, mit denen
ein neuer Knoten eingefügt sowie ein vorhandener gelöscht oder seine Beschriftung
geändert werden kann.
/* MutableTree.java */
import
import
import
import
import
java.awt.*;
java.awt.event.*;
javax.swing.*;
javax.swing.event.*;
javax.swing.tree.*;
public class MutableTree
extends JFrame
implements ActionListener
{
protected DefaultMutableTreeNode root;
protected DefaultTreeModel
treeModel;
protected JTree
tree;
public MutableTree()
{
super("MutableTree");
addWindowListener(new WindowClosingAdapter());
56
57
//JTree erzeugen und Einfachselektion aktivieren
root = new DefaultMutableTreeNode("Root");
treeModel = new DefaultTreeModel(root);
tree = new JTree(treeModel);
TreeSelectionModel tsm = new DefaultTreeSelectionModel();
tsm.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.setSelectionModel(tsm);
tree.setRootVisible(true);
//JTree einfügen
Container cp = getContentPane();
cp.add(new JScrollPane(tree), BorderLayout.CENTER);
//ButtonPanel
JPanel panel = new JPanel(new FlowLayout());
String[] buttons = new String[]{"AddChild", "Delete", "Change"};
for (int i = 0; i < buttons.length; ++i) {
JButton button = new JButton(buttons[i]);
button.addActionListener(this);
panel.add(button);
}
cp.add(panel, BorderLayout.SOUTH);
}
public void actionPerformed(ActionEvent event)
{
String cmd = event.getActionCommand();
TreePath tp = tree.getLeadSelectionPath();
if (tp != null) {
DefaultMutableTreeNode node;
node = (DefaultMutableTreeNode)tp.getLastPathComponent();
if (cmd.equals("AddChild")) {
DefaultMutableTreeNode child;
child = new DefaultMutableTreeNode("child");
treeModel.insertNodeInto(child, node, node.getChildCount());
TreeNode[] path = treeModel.getPathToRoot(node);
tree.expandPath(new TreePath(path));
} else if (cmd.equals("Delete")) {
if (node != root) {
TreeNode parent = node.getParent();
treeModel.removeNodefromParent(node);
TreeNode[] path = treeModel.getPathToRoot(parent);
tree.setSelectionPath(new TreePath(path));
}
} else if (cmd.equals("Change")) {
String name = node.toString();
node.setUserObject(name + "C");
treeModel.nodeChanged(node);
}
}
}
public static void main(String[] args)
{
try {
String plaf = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
UIManager.setLookAndFeel(plaf);
MutableTree frame = new MutableTree();
frame.setLocation(100, 100);
frame.setSize(300, 300);
frame.setVisible(true);
} catch (Exception e) {
}
}
}
1.2.7.2.8
Verändern der Baumstruktur
Es ist ohne weiteres möglich, den Inhalt und die Struktur des Baums nach dem Anlegen des
JTree zu ändern. Es können neue Knoten eingefügt, bestehende entfernt
oder vorhandene modifiziert werden. Wird die Klasse DefaultMutableTreeNode als
Knotenklasse verwendet, reicht es allerdings nicht aus, einfach die
entsprechenden Methoden zum Ändern, Einfügen oder Löschen auf den betroffenen Knoten
aufzurufen. In diesem Fall würde zwar die Änderung im Datenmodell
durchgeführt werden, aber die Bildschirmdarstellung würde sich nicht verändern.
Änderungen im Baum müssen immer über das Modell ausgeführt werden, denn nur dort ist
der JTree standardmäßig als TreeModelListener registriert und wird
57
58
über Änderungen unterrichtet. Werden diese dagegen direkt auf den Knoten ausgeführt,
bleiben sie dem Modell verborgen und die Anzeige wird inkonsistent.
Für einfache Änderungen reicht es aus, eine Instanz der Klasse DefaultTreeModel als
TreeModel zu verwenden. Sie wird durch Übergabe des Wurzelknotens instanziert und stellt
eine Vielzahl von Methoden zum Einfügen, Löschen und Ändern der Knoten zur Verfügung.
Alle Änderungen werden durch Versenden eines TreeModelEvent automatisch an alle
registrierten TreeModelListener weitergegeben und führen dort zu entsprechenden
Aktualisierungen der Bildschirmdarstellung.
Mit insertNodeInto wird ein neuer Kindknoten an einer beliebigen Position zu einem
Elternknoten hinzugefügt. Mit removeNodeFromParent wird ein beliebiger Knoten aus dem
Baum entfernt (er darf auch Unterknoten enthalten), und nodeChanged sollte aufgerufen
werden, wenn der Inhalt eines Knotens sich so geändert hat, dass seine
Bildschirmdarstellung erneuert werden muss. getPathToRoot schließlich ist eine nützliche
Hilfsmethode, mit der das zur Konstruktion eines TreePath-Objekts erforderliche KnotenArray auf einfache Weise erstellt werden kann.
1.2.7.3
JTabbedPane
1.2.7.3.1
Beispiel TabbedPane
Das folgende Programm zeigt ein einfaches Beispiel eines Registerdialogs. Es erstellt ein
JTabbedPane mit fünf Registerkarten, die jeweils ein JPanel mit einem
Label und einem Button enthalten. Das Label zeigt den Namen der Karte an, der Button
dient dazu, die jeweils nächste Karte auszuwählen. Dessen Aktivität ist in
der Klasse NextTabActionListener gekapselt. Als Besonderheit wird nach jedem
Seitenwechsel requestDefaultFocus auf der neuen Seite aufgerufen, um automatisch dem
ersten Dialogelement den Fokus zu geben.
/* TabbedPane.java */
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TabbedPane
extends JFrame
{
JTabbedPane tp;
public TabbedPane()
{
super("JTabbedPane");
addWindowListener(new WindowClosingAdapter());
tp = new JTabbedPane();
for (int i = 0; i < 5; ++i) {
JPanel panel = new JPanel();
panel.add(new JLabel("Karte " + i));
JButton next = new JButton("Weiter");
next.addActionListener(new NextTabActionListener());
panel.add(next);
tp.addTab("Tab" + i, panel);
}
getContentPane().add(tp, "Center");
}
class NextTabActionListener
implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
int tab = tp.getSelectedIndex();
tab = (tab >= tp.getTabCount() - 1 ? 0 : tab + 1);
tp.setSelectedIndex(tab);
((JPanel)tp.getSelectedComponent()).requestDefaultFocus();
}
}
public static void main(String[] args)
{
58
59
TabbedPane frame = new TabbedPane();
frame.setLocation(100, 100);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
1.2.7.3.2
JTabbedPane
Mit JTabbedPane ist es möglich, Dialoge zu erstellen, die eine Reihe von Registerkarten
enthalten. Das sind Unterdialoge, die über ein am Rand befindliches Register einzeln
ausgewählt werden können. Derartige Registerkarten werden beispielsweise in
Konfigurationsdialogen verwendet, wenn die zu bearbeitenden Optionen nicht alle auf eine
Seite passen.
Nach der Instanzierung ist der Registerdialog zunächst leer. Um Registerkarten
hinzuzufügen, kann eine der Methoden addTab oder insertTab aufgerufen werden:
removeTabAt entfernt die Karte mit dem angegebenen Index, removeAll entfernt alle
Karten.
Mit getTabCount kann die Anzahl der Registerkarten ermittelt werden, und mit
getComponentAt kann die Komponente einer beliebigen Registerkarte ermittelt werden. Mit
setComponentAt kann der Inhalt einer Registerkarte sogar nachträglich ausgetauscht
werden:
In einem Registerdialog ist immer genau eine der Karten selektiert. Mit getSelectedIndex
kann deren Index ermittelt werden, mit getSelectedComponent sogar direkt auf ihren Inhalt
zugegriffen werden. Die Methode setSelectedIndex erlaubt es, programmgesteuert eine
beliebige Registerkarte auszuwählen:
Registerkarten besitzen eine Reihe von Eigenschaften, die einzeln verändert werden
können. Die wichtigste von ihnen ist die Möglichkeit, sie zu aktivieren oder zu deaktivieren.
Nur aktivierte Karten können ausgewählt werden, deaktivierte dagegen nicht. Der Zugriff auf
den Aktivierungsstatus erfolgt mit den Methoden setEnabledAt und isEnabledAt:
Bei jeder Änderung der Selektion versendet ein JTabbedPane ein ChangeEvent an
registrierte ChangeListener. Auf diese Weise ist es möglich, Programmcode zu schreiben,
der beim Anwählen oder Verlassen einzelner Registerkarten ausgeführt wird. Eine der
Anwendungen hierfür besteht darin, die Dialoge auf den Registerkarten erst dann zu
erzeugen, wenn sie wirklich gebraucht werden. Dazu wird an alle Registerkarten zunächst
ein leeres Panel übergeben und erst in der Methode stateChanged durch den eigentlichen
Dialog ersetzt. Auf diese Weise spart das Programm beim Initialisieren des Registerdialogs
Rechenzeit und Speicher, was sich vor allem bei komplexen Dialogen positiv bemerkbar
macht.
1.3
Grafiken und Bilder
1.3.1 Grundlagen der Grafikausgabe
1.3.1.1
Beispiel PrimitiveRoutines
/* PrimitiveRoutines.java */
import java.awt.*;
import java.awt.event.*;
public class PrimitiveRoutines
extends Frame
{
int[] arx = {50,50,120,120,80,80,100,100,80,80};
int[] ary = {170,40,40,70,70,100,100,130,130,170};
public static void main(String[] args)
{
PrimitiveRoutines wnd = new PrimitiveRoutines();
59
60
wnd.drawShapes();
}
public PrimitiveRoutines()
{
super("PrimitiveRoutines");
addWindowListener(new WindowClosingAdapter(true));
setBackground(Color.lightGray);
setSize(400,400);
setVisible(true);
}
public void drawShapes()
{
Graphics g = getGraphics();
g.setClip(20,20,350,150);
g.drawLine(20,40,20,300);
g.drawOval(70,90,80,100);
g.drawRect(170,90,80,100);
g.drawPolygon(arx,ary,arx.length);
g.fillRect(200,40,20,80);
}
}
1.3.1.2
Die Methode paint
Die Ausgabe in ein Fenster erfolgt durch Überlagern der Methode paint, die immer dann
aufgerufen wird, wenn das Fenster ganz oder teilweise neu gezeichnet werden muss.
Dies ist beispielsweise dann der Fall, wenn das Fenster zum ersten Mal angezeigt wird oder
durch Benutzeraktionen ein Teil des Fensters sichtbar wird, der bisher verdeckt war. paint
bekommt beim Aufruf eine Instanz der Klasse Graphics übergeben.
1.3.1.3
Die Klasse Graphics
Graphics ist Javas Implementierung eines Device-Kontexts (auch Grafikkontext genannt)
und stellt somit die Abstraktion eines universellen Ausgabegeräts für Grafik und Schrift dar.
Die Klasse bietet Methoden zur Erzeugung von Linien-, Füll- und Textelementen. Darüber
hinaus verwaltet Graphics die Zeichenfarbe, in der alle Ausgaben erfolgen, und einen Font,
der zur Ausgabe von Schrift verwendet wird.
1.3.1.4
Das grafische Koordinatensystem
Die Ausgabe von Grafik basiert auf einem zweidimensionalen Koordinatensystem, dessen
Ursprungspunkt (0,0) in der linken oberen Ecke liegt. Positive x-Werte erstrecken sich nach
rechts, positive y-Werte nach unten. Die Maßeinheit entspricht einem Bildschirmpixel und ist
damit geräteabhängig.
In den meisten Fällen steht dem Programm nicht das gesamte Fenster zur Ausgabe zur
Verfügung, sondern es gibt Randelemente wie Titelzeilen, Menüs oder Rahmen, die nicht
überschrieben werden können. Mit der Methode getInsets() kann die Größe dieser
Randelemente ermittelt werden.
60
61
1.3.1.5
Elementare Grafikroutinen
Die Klasse Graphics stellt neben vielen anderen Funktionen auch eine Sammlung von
linienbasierten Zeichenoperationen zur Verfügung. Diese sind zur Darstellung von einfachen
Linien, Rechtecken oder Polygonen sowie von Kreisen, Ellipsen und Kreisabschnitten
geeignet.
1.3.1.5.1
Linien
public void drawLine(int x1, int y1, int x2, int y2)
Zieht eine Linie von der Position (x1,y1) zur Position (x2,y2). Teile der Ausgabe, die
außerhalb des darstellbaren Bereichs liegen, werden unterdrückt.
Die Methode drawLine zeichnet Linien grundsätzlich mit einer Dicke von einem Pixel.
1.3.1.5.2
Rechteck
public void drawRect(int x, int y, int width, int height)
Zeichnet ein Rechteck der Breite width und der Höhe height, dessen linke obere Ecke an
der Position (x,y) liegt. Eine größere Breite dehnt das Rechteck nach rechts aus, eine
größere Höhe nach unten.
Eine Variante von drawRect ist die Methode drawRoundRect:
public void drawRoundRect(
int x, int y,
int width, int height,
int arcWidth, int arcHeight
)
Gegenüber drawRect sind hier die Parameter arcWidth und arcHeight dazugekommen. Sie
bestimmen den horizontalen und vertikalen Radius des Ellipsenabschnitts, der zur
Darstellung der runden »Ecke« verwendet wird.
1.3.1.5.3
Polygon
Mit Hilfe der Methode drawPolygon() ist es möglich, Linienzüge zu zeichnen, bei denen das
Ende eines Elements mit dem Anfang des jeweils nächsten verbunden ist:
public void drawPolygon(int[] arx, int[] ary, int cnt)
drawPolygon erwartet drei Parameter. Der erste ist ein Array mit einer Liste der xKoordinaten und der zweite ein Array mit einer Liste der y-Koordinaten. Beide Arrays
müssen so synchronisiert sein, dass ein Paar von Werten an derselben Indexposition immer
61
62
auch ein Koordinatenpaar ergibt. Die Anzahl der gültigen Koordinatenpaare wird durch den
dritten Parameter festgelegt.
Mit Hilfe der Methode addPoint() kann ein Polygon um weitere Punkte erweitert werden.
Schließlich kann ein fertiges Polygon an die Methode drawPolygon() übergeben werden :
public void drawPolygon(Polygon p)
1.3.1.5.4
Kreis
Die Graphics-Klasse von Java erlaubt sowohl das Zeichnen von Kreisen als auch von
Ellipsen und Kreisabschnitten. Ein Kreis wird dabei als Verallgemeinerung einer Ellipse
angesehen, und beide Objekte werden mit der Methode drawOval gezeichnet:
public void drawOval(int x, int y, int width, int height)
Anders als in anderen Grafiksystemen werden bei dieser Methode nicht der Mittelpunkt und
der Radius des Kreises angegeben, sondern die übergebenen Parameter spezifizieren ein
Rechteck der Größe width und heigth, dessen linke obere Ecke an der Position (x,y) liegt.
Gezeichnet wird dann der größte Kreis, der vollständig in das Rechteck hineinpasst.
1.3.1.5.5
Kreisbogen
Ein Kreisbogen ist ein zusammenhängender Abschnitt der Umfangslinie eines Kreises. Er
kann mit der Methode drawArc() gezeichnet werden:
public void drawArc(
int x, int y, int width, int height,
int startAngle, int arcAngle
)
Die ersten vier Parameter bezeichnen dabei den Kreis bzw. die Ellipse so, wie dies auch bei
drawOval der Fall war. Mit startAngle wird der Winkel angegeben, an dem mit dem
Kreisabschnitt begonnen werden soll, und arcAngle gibt den zu überdeckenden Bereich an.
Dabei bezeichnet ein Winkel von 0 Grad die 3-Uhr-Position, und positive Winkel werden
entgegen dem Uhrzeigersinn gemessen. Als Einheit wird Grad verwendet und nicht das
sonst übliche Bogenmaß.
1.3.1.6
Linien und Füllmodus
Mit Ausnahme von drawLine() können alle vorgestellten Routinen entweder im Linien- oder
im Füllmodus verwendet werden. Beginnt der Methodenname mit draw, erfolgt die Ausgabe
im Linienmodus. Es wird lediglich der Umriss der jeweiligen Figur gezeichnet. Beginnt der
Methodenname mit fill, erfolgt die Ausgabe im Füllmodus, und das Objekt wird mit
ausgefüllter Fläche gezeichnet.
1.3.1.7
Kopieren und Löschen von Flächen
Die Klasse Graphics stellt auch einige Methoden zum Bearbeiten bereits gezeichneter
Flächen zur Verfügung. Diese erlauben es beispielsweise, einen rechteckigen Ausschnitt
des Ausgabefensters zu löschen oder zu kopieren:
Die Methode clearRect() löscht das angegebene Rechteck, indem sie den Bereich mit der
aktuellen Hintergrundfarbe ausfüllt.
Soll ein Teil des Fensters kopiert werden, kann dazu die Methode copyArea() verwendet
werden. Die ersten vier Parameter bezeichnen in der üblichen Weise den zu kopierenden
Ausschnitt. (dx,dy) gibt die Entfernung des Zielrechtecks in x- und y-Richtung an. copyArea
kopiert das ausgewählte Rechteck also immer an die Position (x+dx, y+dy).
62
63
1.3.1.8
Die Clipping-Region
Jeder Grafikkontext hat eine zugeordnete Clipping-Region, die dazu dient, die Ausgabe auf
einen bestimmten Bereich einzugrenzen.
So wird beispielsweise verhindert, dass wichtige Teile eines Fensters, wie z.B. der Rahmen
oder die Menüzeile, von den Ausgabeoperationen des Programms überschrieben werden.
Ein Fenster besitzt eine Clipping-Region, die an das übergebene Graphics-Objekt gebunden
ist. Beim Aufruf der paint-Methode entspricht ihre Größe der zur Ausgabe zur Verfügung
stehenden Fläche. Mit Hilfe der Methode clipRect kann die Clipping-Region nun sukzessive
verkleinert werden:
public void clipRect(int x, int y, int widh, int height)
Ein Aufruf dieser Methode begrenzt die Clipping-Region auf die Schnittmenge zwischen
dem angegebenen Rechteck und ihrer vorherigen Größe. Da eine Schnittmenge niemals
größer werden kann als eine der Mengen, aus denen sie gebildet wurde, kann clipRect also
lediglich dazu verwendet werden, die Clipping-Region zu verkleinern.
Soll die Clipping-Region auf einen beliebigen Bereich innerhalb des aktuellen Fensters
ausgedehnt werden (der dabei auch größer sein kann als die bisherige Clipping-Region), so
kann dazu die Methode setClip() verwendet werden.
Soll die Ausdehnung der aktuellen Clipping-Region ermittelt werden, so kann dazu eine der
Methoden getClip oder getClipBounds verwendet werden.
1.3.2
Bitmaps und Animation
1.3.2.1
Bitmaps
1.3.2.1.1
Beispiel BitmapOhneRepaintPaintMechanismus
public class Bitmap
extends Frame
{
private Image img;
public static void main(String[] args)
{
Bitmap wnd = new Bitmap();
}
public Bitmap()
{
super("Bitmap");
setBackground(Color.lightGray);
setSize(250,150);
setVisible(true);
//WindowListener
addWindowListener(new WindowClosingAdapter(true));
img = getToolkit.getImage("…");
MediaTracker mt = new MediaTracker(this);
Mt.addImage(img, 0);
try{
mt.waitForAll();
}
catch(InterruptedException e) {}
Graphics g = getGraphics();
g.drawImage(img, 40, 40, this);
}
}
1.3.2.1.2
Laden einer Bitmap
Das Laden erfolgt mit der Methode getImage(), die eine Instanz der Klasse Image
zurückgibt.
63
64
getImage gibt es in verschiedenen Varianten, die sich dadurch unterscheiden, aus welcher
Quelle sie die Bitmap-Daten laden. In einer Java-Applikation wird in der Regel die Methode
getImage aus der Klasse Toolkit verwendet. Sie erwartet den Namen einer lokalen Datei als
Parameter:
1.3.2.1.3
Die Klasse Toolkit
Es gibt im AWT eine Klasse Toolkit, die als Hilfsklasse bei der Abbildung der portablen
AWT-Eigenschaften dient.
Toolkit ist standardmäßig abstrakt, wird aber von jeder AWT-Implementierung konkretisiert
und den Anwendungen über die Klassenmethode getDefaultToolkit zur Verfügung gestellt.
Das Toolkit für die aktuelle Umgebung kann mit der Methode getToolkit der Klasse
Component beschafft werden.
1.3.2.1.4
Ausgeben einer Bitmap
drawImage erwartet das anzuzeigende Image-Objekt und die Position (x,y), an der die linke
obere Ecke der Bitmap plaziert werden soll.
Das zusätzlich angegebene Objekt observer dient zur Übergabe eines ImageObserverObjektes, mit dem der Ladezustand der Bitmaps überwacht werden kann. Hier kann der
this-Zeiger, also eine Referenz auf das Fensterobjekt, übergeben werden.
1.3.2.1.5
Die Klasse MediaTracker
1.3.2.1.5.1
Problem
Beispiel BitmapOhneRepaintPaintMechanismus
ohne MediaTracker ausführen !
Der Aufruf von getImage() lädt die Bitmap noch nicht, sondern bereitet das Laden nur vor.
Der eigentliche Ladevorgang beginnt erst, wenn die Bitmap beim Aufruf von drawImage()
tatsächlich benötigt wird.
Die Methode drawImage() kehrt auch dann sofort zurück, wenn das Laden eines Bildes
noch nicht abgeschlossen ist.
Unfertige Bilder möchte man jedoch in der Regel nicht anzeigen sondern auf das
vollständige Laden warten.
Wird zum Beispiel die Größe der Bitmap benötigt, um sie korrekt auf dem Bildschirm
anordnen oder skalieren zu können, muss das Programm warten, bis das Image vollständig
erzeugt ist.
1.3.2.1.5.2
Lösung durch einen MediaTracker
Die Klasse MediaTracker ermöglicht das Warten auf das Laden einer ganzen Serie von
Bilder.
Dazu wird zunächst eine Instanz der Klasse angelegt.
Als Komponente wird der this-Zeiger des aktuellen Fensters übergeben. Anschließend
werden durch Aufruf von addImage() alle Bilder, deren Ladevorgang überwacht werden soll,
an den MediaTracker übergeben.
Der zusätzlich übergebene Parameter id kann dazu verwendet werden, einen Namen zu
vergeben, unter dem auf das Image zugegriffen werden kann. Zusätzlich bestimmt er die
Reihenfolge, in der die Images geladen werden. Bitmaps mit kleineren Werten werden
zuerst geladen.
Der MediaTracker bietet Methoden, um den Ladezustand der Bilder zu überwachen :
64
65
waitForAll() wartet, bis alle Images vollständig geladen sind.
1.3.2.1.5.3
Beschreibungen aus der Online Hilfe
track = verfolgen
The MediaTracker class is a utility class to track the status of a number of media objects.
Media objects could include audio clips as well as images, though currently only images are
supported.
To use a media tracker, create an instance of MediaTracker and call its addImage method
for each image to be tracked. In addition, each image can be assigned a unique identifier.
This identifier controls the priority order in which the images are fetched. It can also be used
to identify unique subsets of the images that can be waited on independently. Images with a
lower ID are loaded in preference to those with a higher ID number.
waitForId(int id) throws InterruptedException
Starts loading all images tracked by this media tracker with the specified identifier. This
method waits until all the images with the specified identifier have finished loading.
If there is an error while loading or scaling an image, then that image is considered to have
finished loading. Use the isErrorAny and isErrorID methods to check for errors.
id - the identifier of the images to check.
die weitere Abarbeitung stoppt also, bis die gekennzeichneten Media-Objekte vollständig
geladen sind
public int statusID(int id, boolean load)
Calculates and returns the bitwise inclusive OR of the status of all media with the specified
identifier that are tracked by this media tracker.
Possible flags defined by the MediaTracker class are LOADING, ABORTED, ERRORED,
and COMPLETE. An image that hasn't started loading has zero as its status.
If the value of load is true, then this method starts loading any images that are not yet being
loaded.
diese Methode liefert also den aktuellen Ladezustand, bei erfolgtem Laden ist das
MediaTracker.COMPLETE
public int statusAll(boolean load)
Calculates and returns the bitwise inclusive OR of the status of all media that are tracked by
this media tracker.
diese Methode berechnet also eine Zusammenfassung der Zustände aller zu
überwachenden Objekte, im Fehlerfall MediaTracker.ERRORED.
drawImage
public abstract boolean drawImage(Image img,
int x,
int y,
Color bgcolor,
ImageObserver observer)
Draws as much of the specified image as is currently available. The image is drawn with its
top-left corner at (x, y) in this graphics context's coordinate space. Transparent pixels are
drawn in the specified background color.
This operation is equivalent to filling a rectangle of the width and height of the specified
image with the given color and then drawing the image on top of it, but possibly more
efficient.
This method returns immediately in all cases, even if the complete image has not yet been
loaded, and it has not been dithered and converted for the current output device.
If the image has not yet been completely loaded, then drawImage returns false. As more of
the image becomes available, the process that draws the image notifies the specified image
observer.
65
66
interface ImageObserver
An asynchronous update interface for receiving notifications about Image information as the
Image is constructed.
1.3.2.1.6
Verbleibendes Problem
Fährt man im Beispiel BitmapOhneRepaintPaintMechanismus z.B. mit dem DOS Fenster
über das Bitmap, so werden die verdeckten Bitmap Teile nicht wieder neu dargestellt. Das
Bitmap wir zerstört.
Schiebt man das Bitmap aus dem Bildschirm oder bringt man die Applikation in den
Hintergrund, ist das das Bitmap nicht mehr sichtbar.
1.3.2.1.7
Beispiel Bitmap (Mit repaint-paint Mechanismus)
Abhilfe bietet der repaint-paint Mechanismus
public class Bitmap
extends Frame
{
private Image img;
public static void main(String[] args)
{
Bitmap wnd = new Bitmap();
}
public Bitmap()
{
super("Bitmap");
setBackground(Color.lightGray);
setSize(250,150);
setVisible(true);
//WindowListener
addWindowListener(new WindowClosingAdapter(true));
//Bild laden
img = getToolkit().getImage("duke.gif");
MediaTracker mt = new MediaTracker(this);
mt.addImage(img, 0);
try {
//Warten, bis das Image vollständig geladen ist,
mt.waitForAll();
} catch (InterruptedException e) {
//nothing
}
repaint();
}
public void paint(Graphics){
if (img != Null) {
g.drawImage(img, 40, 40, this);
}
}
}
1.3.2.1.8
Der repaint() – paint() Mechanismus
1.3.2.1.8.1
Die Methode paint()
Die Ausgabe in ein Fenster erfolgt durch die Methode paint(), die immer dann aufgerufen
wird, wenn das Fenster ganz oder teilweise neu gezeichnet werden muss.
Dies ist beispielsweise dann der Fall, wenn das Fenster zum ersten Mal angezeigt wird oder
durch Benutzeraktionen ein Teil des Fensters sichtbar wird, der bisher verdeckt war. paint
bekommt beim Aufruf eine Instanz der Klasse Graphics übergeben.
1.3.2.1.8.2
Die Methode repaint()
66
67
repaint() bewirkt den asynchronen Aufruf der update() Methode.
Die Ausführung von update()ist also die Folge des Aufrufs der Methode repaint().
Mit der argumentlosen Variante überlässt der Programmierer die Reaktionszeit dem
Laufzeitsystem. Nach einer gewissen Zeit, unter Umständen nach der Zusammenfassung
mehrerer repaint()-Anforderungen, löst es update() aus. Dabei ist im argumentlosen
Fall die gesamte Fläche zur Gestaltung freigegeben.
1.3.2.1.8.3
Die Methode update()
Die Methode update() löscht die wirksame Fläche und ruft dann paint() mit demselben
Graphics-Objekt auf. Dort soll der Programmierer die Fläche neu beschreiben.
1.3.2.1.8.4
Anmerkungen
Die Methoden update() und paint() erhalten als Übergabeparameter ein Objekt der Klasse
java.awt.Graphics.
Die Methoden paint() und repaint() erbt die Klasse Frame von Component.
Der Programmierer hat durch die anderen repaint()-Varianten zwei Mittel, die Wirkung
von update() zu steuern.
Er kann die Reaktionszeit einstellen, d.h. den Zeitraum, nach dem update() spätestens
aufzurufen ist, und die wirksame Fläche einschränken.
Achtung :
Bei Einschränkung der wirksamen Fläche führt die Zusammenfassung mehrerer
repaint()-Anforderungen durch das Laufzeitsystem zu einem möglicherweise
ungewollten Effekt: Die wirksame Fläche des Graphics-Objekts in update() ist dann als
das kleinste Rechteck definiert, das alle einzelnen wirksamen Flächen enthält (union rect).
Die so definierte Fläche umfasst im Allgemeinen Gebiete, für die ursprünglich keine
Aktualisierung vorgesehen war.
1.3.2.1.8.5
1.3.2.1.8.5.1
Unterschiede zwischen AWT und Swing
AWT
java.awt.Component
public void repaint(long tm,
int x,
int y,
int width,
int height)
Repaints the specified rectangle of this component within tm milliseconds.
This method causes a call to this component's update method.
public void update(Graphics g)
Updates this component.
The AWT calls the update method in response to a call to repaint. The appearance of the
component on the screen has not changed since the last call to update or paint. You can
assume that the background is not cleared.
The updatemethod of Component does the following:
·
Clears this component by filling it with the background color.
67
68
·
·
Sets the color of the graphics context to be the foreground color of this component.
Calls this component's paint method to completely redraw this component.
public void paint(Graphics g)
Paints this component. This method is called when the contents of the component should
be painted in response to the component first being shown or damage needing repair. The
clip rectangle in the Graphics parameter will be set to the area which needs to be painted.
1.3.2.1.8.5.2
Swing
com.sun.java.swing.JComponent
public void repaint(long tm,
int x,
int y,
int width,
int height)
Adds the specified region to the dirty region list if the component is showing. The
component will be repainted after all of the currently pending events have been dispatched.
public void update(Graphics g)
Calls paint(g). Doesn't clear the background.
Code in JFrame
/**
* Just calls <code>paint(g)</code>. This method was overridden to
* prevent an unneccessary call to clear the background.
*
* @param g the Graphics context in which to paint
*/
public void update(Graphics g) {
paint(g);
}
Achtung !
Die update() Funktion von Swing löscht keinen Hintergrund.
Achtung !
Der Hilfe Text enthält keinen Hinweis darauf, dass die update() Funktion im Zusammenhang
mit repaint() überhaupt aufgerufen wird.
public void paint(Graphics g)
This method is invoked by Swing to draw components. Applications should not invoke paint
directly, but should instead use the repaint method to schedule the component for
redrawing.
This method actually delegates the work of painting to three protected methods:
paintComponent, paintBorder, and paintChildren. They're called in the order listed to ensure
that children appear on top of component itself. Generally speaking, the component and its
children should not paint in the insets area allocated to the border. Subclasses can just
override this method, as always. A subclass that just wants to specialize the UI (look and
feel) delegates paint method should just override paintComponent.
Beispiel vgl.
Grafik/Animation/RepaintPaintMechanismusFuerJPanel
1.3.2.2
1.3.2.2.1
Animation
Beispiel BitmapAnimation
68
69
/* BitmapAnimation.java */
import java.awt.*;
import java.awt.event.*;
public class BitmapAnimation
extends Frame
{
Image[] arImg;
int actimage = -1;
public static void main(String[] args)
{
BitmapAnimation wnd = new BitmapAnimation();
wnd.setSize(200,150);
wnd.setVisible(true);
wnd.startAnimation();
}
public BitmapAnimation()
{
super("Bitmap-Folge");
addWindowListener(new WindowClosingAdapter(true));
}
public void startAnimation()
{
//Bilder laden
arImg = new Image[5];
MediaTracker mt = new MediaTracker(this);
Toolkit tk = getToolkit();
for (int i = 0; i <= 4; ++i) {
arImg[i] = tk.getImage("images/juggler"+i+".gif");
mt.addImage(arImg[i], 0);
}
try {
mt.waitForAll();
} catch (InterruptedException e) {
//nothing
}
//Animation beginnen
actimage = 0;
while (true) {
repaint();
actimage = (actimage + 1) % 4;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
//nichts
}
}
}
public void update(Graphics g)
{
paint(g);
}
public void paint(Graphics g)
{
if (actimage < 0) {
g.drawString("Lade Bitmap !",10,50);
} else {
g.drawImage(arImg[actimage],10,30,this);
}
}
}
1.3.2.2.2
Konzept
Das Darstellen einer Animation auf dem Bildschirm ist im Prinzip nichts Anderes als die
schnell aufeinanderfolgende Anzeige einer Sequenz von Einzelbildern. Die Bildfolge
erscheint dem menschlichen Auge aufgrund seiner Trägheit als zusammenhängende
Bewegung.
69
70
1.3.2.2.3
Beispiel FlackerndesImageApplet
public class PartialRemovingImageApplet extends Applet
implements MouseMotionListener
{
private Image image = null;
private MediaTracker tracker = null;
private int x = 0, y = 0, oldx = 0, oldy = 0, w = 100, h = 100;
public void init() {
tracker = new MediaTracker( this );
try {
image = getImage( new URL( getDocumentBase(), "Willi.gif" ));
tracker.addImage( image, 0 );
tracker.waitForID( 0 );
h = image.getHeight( this );
w = image.getWidth( this );
addMouseMotionListener( this );
}
catch ( MalformedURLException me ) { System.err.println( "nicht
gefunden" ); }
catch ( InterruptedException ie ) { System.err.println( "Fehler beim
Laden" ); }
}
public void mouseMoved( MouseEvent mouseEvent ) {}
public void mouseDragged( MouseEvent mouseEvent ) {
this.x = mouseEvent.getX(); this.y = mouseEvent.getY();
repaint();
}
public void paint( Graphics g ) {
if (( tracker.statusID( 0, false ) & MediaTracker.COMPLETE ) != 0 )
{
g.clearRect(0, 0, getWidth(), getHeight());
g.drawImage( image, x, y, this );
}
g.drawRect( x, y, w, h );
}
//
public void update( Graphics g ) {
super.update(g);
paint( g );
}
/* Code der Basisklassen Funktion
public void update(Graphics g) {
if ((! (peer instanceof java.awt.peer.LightweightPeer)) &&
(! (this instanceof Label)) && (! (this instanceof TextField))) {
g.setColor(getBackground());
g.fillRect(0, 0, width, height);
g.setColor(getForeground());
}
paint(g);
}
*/
}
1.3.2.2.4
Reduktion des Bildschirmflackerns
Die entwickelte Animation zeigt ein ausgeprägtes Flackern. Der Grund für dieses Flackern
liegt darin, dass vor jedem Aufruf von paint() zunächst das Fenster gelöscht wird und
dadurch unmittelbar vor der Ausgabe des nächsten Bildes ganz kurz ein vollständig leerer
Hintergrund erscheint.
Leider besteht die Lösung für dieses Problem nicht einfach darin, das Löschen zu
unterdrücken. Bei einer animierten Bewegung beispielsweise ist es erforderlich, all die
Bestandteile des vorigen Bildes zu löschen, die im aktuellen Bild nicht mehr oder an einer
anderen Stelle angezeigt werden.
Auch wenn paint deshalb aufgerufen wird, weil ein bisher verdeckter Bildausschnitt wieder
sichtbar wird, muss natürlich der entsprechende Bildausschnitt zunächst gelöscht werden,
70
71
um die Bestandteile des anderen Fensters zu entfernen. Im Grunde ist es also eine ganz
vernünftige Vorgehensweise, das Fenster vor jedem Aufruf von paint zu löschen.
Das Flackern kann nun auf unterschiedliche Weise unterdrückt werden. Die drei
gebräuchlichsten Methoden sind folgende:
den Bildschirm gar nicht zu löschen (Dies ist nur selten möglich !)
nur den wirklich benötigten Teil des Bildschirms zu löschen
das Verfahren der Doppelpufferung anzuwenden
Jedes dieser Verfahren hat Vor- und Nachteile und kann in verschiedenen Situationen
unterschiedlich gut angewendet werden.
1.3.2.2.5
Beispiel PartialRemovingImageApplet
PartialRemovingImageApplet
// PartialRemovingImageApplet.java
public class PartialRemovingImageApplet extends Applet
implements MouseMotionListener {
private Image image = null;
private MediaTracker tracker = null;
private int x = 0, y = 0, oldx = 0, oldy = 0, w = 100, h = 100;
public void init() {
tracker = new MediaTracker( this );
try {
image = getImage( new URL( getDocumentBase(), "Willi.gif" ));
tracker.addImage( image, 0 );
tracker.waitForID( 0 );
h = image.getHeight( this );
w = image.getWidth( this );
addMouseMotionListener( this );
}
catch ( MalformedURLException me ) { System.err.println( "nicht
gefunden" ); }
catch ( InterruptedException ie ) { System.err.println( "Fehler beim
Laden" ); }
}
public void mouseMoved( MouseEvent mouseEvent ) {}
public void mouseDragged( MouseEvent mouseEvent ) {
this.x = mouseEvent.getX(); this.y = mouseEvent.getY();
repaint();
}
public void update( Graphics g ) {
paint( g );
}
public void paint( Graphics g ) {
int
int
int
int
dx
dw
dy
dh
=
=
=
=
(( x < oldx
( Math.abs(
(( y < oldy
( Math.abs(
)
x
)
y
&& ( x
- oldx
&& ( y
- oldy
if (( tracker.statusID( 0,
g.drawImage( image,
g.drawRect( x, y, w, h );
g.clearRect( dx, oldy, dw,
g.clearRect( oldx, dy, w +
oldx = x; oldy = y;
+
)
+
)
w
<
h
<
>
w
>
h
oldx )) ? x +
) ? Math.abs(
oldy )) ? y +
) ? Math.abs(
w
x
h
y
+
+
-
1 : oldx;
oldx ) : w + 1;
1 : oldy;
oldy ) : h + 1;
false ) & MediaTracker.COMPLETE ) != 0 )
x, y, this );
h + 1 );
1, dh );
}
}
update ruft nur paint() auf, ohne die Fläche vorher zu löschen.
71
72
Die Methode paint()löscht alle Flächen, die beim letzten Aufruf vom Bild aber jetzt nicht
mehr eingenommen wurden.
URL getDocumentBase()
Gets the document URL. This is the URL of the document in which the applet is embedded.
Class URL represents a Uniform Resource Locator, a pointer to a "resource" on the World
Wide Web.
1.3.2.2.6
Selektives Löschen
Selektives Löschen"Der Programmierer löscht dabei statt der gesamten Fläche nur Gebiete,
bei denen durch das Bewegen eines Bildobjekts der Hintergrund wieder sichtbar wird. In der
Regel wird update() hier einfach paint() aufrufen. paint() zeichnet das zu
bewegende Bildobjekt an der neuen Position und füllt die Differenzfläche aus alter und
neuer Position mit der Hintergrundfarbe bzw. dem Hintergrundbild.
1.3.2.2.7
Beispiel DoubleBufferingImageApplet
DoubleBufferingImageApplet
DoubleBufferingImageApplet.java
public class DoubleBufferingImageApplet extends Applet
implements MouseMotionListener {
private
private
private
private
private
private
Image image = null;
Image offscreenImage = null;
Graphics offscreenGraphics = null;
Dimension appletDimension = null;
MediaTracker tracker = null;
int x = 0, y = 0, w = 100, h = 100;
public void init() {
tracker = new MediaTracker( this );
try {
image = getImage( new URL( getDocumentBase(), "Willi.gif" ));
tracker.addImage( image, 0 );
tracker.waitForID( 0 );
h = image.getHeight( this );
w = image.getWidth( this );
addMouseMotionListener( this );
appletDimension = getSize();
offscreenImage = createImage( appletDimension.width,
appletDimension.height );
offscreenGraphics = offscreenImage.getGraphics();
offscreenGraphics.drawImage( image, x, y, this );
offscreenGraphics.drawRect( x, y, w, h );
}
catch ( MalformedURLException me ) { System.err.println( "nicht
gefunden" ); }
catch ( InterruptedException ie ) { System.err.println( "Fehler beim
Laden" ); }
}
public void mouseMoved( MouseEvent mouseEvent ) {}
public void mouseDragged( MouseEvent mouseEvent ) {
this.x = mouseEvent.getX(); this.y = mouseEvent.getY();
repaint();
}
public void update(Graphics g) {
paint(g);
paint(offscreenGraphics);
g.drawImage(offscreenImage, 0, 0, this);
}
public void paint( Graphics g ) {
g.clearRect( 0, 0,
appletDimension.width, appletDimension.height );
g.drawImage( image, x, y, this );
g.drawRect( x, y, w, h );
}
}
72
73
1.3.2.2.8
Doppelpufferung
Beim Double Buffering benutzt der Programmierer ein zweites Graphics-Objekt zur
Anordnung der Bildobjekte.
Das Fensterobjekt beschafft sich durch Aufruf von createImage() ein Offscreen-Image und
speichert es in einer Instanzvariablen.
Durch Aufruf von getGraphics() wird ein Grafikkontext zu diesem Image beschafft.
Alle Bildschirmausgaben (inklusive Löschen des Bildschirms) gehen zunächst auf den
Offscreen-Grafikkontext.
Wenn alle Ausgabeoperationen abgeschlossen sind, wird das Offscreen-Image mit
drawImage in das Ausgabefenster kopiert.
Durch diese Vorgehensweise wird erreicht, dass das Bild komplett aufgebaut ist, bevor es
angezeigt wird. Da beim anschließenden Kopieren die neuen Pixel direkt über die alten
kopiert werden, erscheinen dem Betrachter nur die Teile des Bildes verändert, die auch
tatsächlich geändert wurden.
Ein Flackern, das entsteht, weil Flächen für einen kurzen Zeitraum gelöscht und dann
wieder gefüllt werden, kann nicht mehr auftreten.
Das DoubleBuffeering ist für Swing JComponents bereits vorgesehen :
JComponent.setDoubleBuffered(boolean aFlag)
Set whether the receiving component should use a buffer to paint. If set to true, all the
drawing from this component will be done in an offscreen painting buffer. The offscreen
painting buffer will the be copied onto the screen. Swing's painting system always use a
maximum of one double buffer. If a Component is buffered and one of its ancestor is also
buffered, the ancestor buffer will be used.
Achtung : JFrame ist nicht von JComponent abgeleitet !
1.3.2.2.9
Beispiel ExclusiveOrImageApplet
ExclusiveOrImageApplet.java
public class ExclusiveOrImageApplet extends PartialRemovingImageApplet {
private int xLineStart = 0, yLineStart = 0,
xLineEnd = 0, xLineEndOld = 0, yLineEnd = 0, yLineEndOld = 0;
private boolean line = false;
public void mouseMoved( MouseEvent mouseEvent ) {
xLineEnd = mouseEvent.getX(); yLineEnd = mouseEvent.getY();
line = true;
repaint();
}
public void mouseDragged( MouseEvent mouseEvent ) {
xLineStart = mouseEvent.getX(); yLineStart = mouseEvent.getY();
xLineEnd = xLineStart; yLineEnd = yLineStart;
line = false;
super.mouseDragged( mouseEvent );
repaint();
}
public void paint( Graphics g ) {
if ( line ) {
g.setXORMode(color.white);
g.drawLine( xLineStart, yLineStart, xLineEndOld, yLineEndOld );
g.drawLine( xLineStart, yLineStart, xLineEnd, yLineEnd );
}
else {
g.setPaintMode();
super.paint( g );
}
xLineEndOld = xLineEnd; yLineEndOld = yLineEnd;
73
74
}
}
1.3.2.2.10
Löschen mit Exclusiv-Oder
Wie löscht man eine gezeichnete Linie wieder? Natürlich indem man sie mit der
Hintergrundfarbe übermalt. Wenn aber die Linie über ein Bild gelegt war, dann sieht man
nun statt der ursprünglichen Linie eine Linie in der Hintergrundfarbe über dem Bild.
Dieses Problem löst das Malen im Exclusiv-Oder-Modus. Das folgende Beispiel zeigt
dessen Anwendung.
Beispiel für XOR
weiß = 0
schwarz = 1
schwarz auf schwarz = 1 XOR 1 = 0 = weiß
schwarz auf weiß = 1 XOR 0 = 1 = schwarz
Beim erstenmal erscheint eine weiße Linie auf schwarzem Hintergrund, beim nächstenmal
erscheint die schwarze Linie, d.h. der Ausgangszustand ist wieder erreicht.
1.4
Objektserialiserung
1.4.1 Serialisierung von einfachen Objekten
1.4.1.1
Beispiel Einfache Serialisierung
SimpleSerializing
/* SimpleSerializing.java */
import java.io.*;
import java.util.*;
public class SimpleSerializing
{
public static void main(String[] args)throws java.io.IOException
{
try {
FileOutputStream fs = new FileOutputStream("Test2ser");
ObjectOutputStream os = new ObjectOutputStream(fs);
os.write(123);
os.writeObject("Hallo");
os.writeObject(new Time(10, 30));
os.writeObject(new Time(11, 25));
os.close();
} catch (IOException e) {
System.err.println(e.toString());
}
System.out.println("Fertig mit Serialiseren ! Bitte Taste druecken !");
try {
FileInputStream fs = new FileInputStream("test2.ser");
ObjectInputStream is = new ObjectInputStream(fs);
System.out.println("" + is.readInt());
System.out.println((String)is.readObject());
Time time = (Time)is.readObject();
System.out.println(time.toString());
time = (Time)is.readObject();
System.out.println(time.toString());
is.close();
} catch (ClassNotFoundException e) {
System.err.println(e.toString());
} catch (IOException e) {
System.err.println(e.toString());
}
System.out.println("Fertig mit Deserialiseren ! Bitte Taste druecken !");
System.in.read();
74
75
}
}
1.4.1.2
Begriffsbestimmung
Die Objektserialiserung ist die Konvertierung von Java-Objekten in einen Strom von Bytes.
Dazu werden die Instanzvariablen in einer definierten Reihenfolge z.B. in eine Datei oder
eine Netzwerkverbindung geschrieben.
Serialisierung wird häufig mit dem Begriff Persistenz gleichgesetzt, vor allem in
objektorientierten Programmiersprachen. Das ist nur bedingt richtig, denn Persistenz
bezeichnet genaugenommen das dauerhafte Speichern von Daten auf einem externen
Datenträger, so dass sie auch nach dem Beenden des Programms erhalten bleiben.
Obwohl die persistente Speicherung von Objekten sicherlich eine der Hauptanwendungen
der Serialisierung ist, ist sie nicht ihre einzige.
1.4.1.3
Schreiben von Objekten
1.4.1.3.1
Die Klasse ObjectOutputStream
Eine zentrale Rolle im Serialiserungsprozess spielt die Klasse ObjectOutputStream.
ObjectOutputStream besitzt einen Konstruktor, der einen OutputStream als Argument
erwartet.
Der an den Konstruktor übergebene OutputStream dient als Ziel der Ausgabe.
Hier kann ein beliebiges Objekt der Klasse OutputStream oder einer daraus abgeleiteten
Klasse übergeben werden. Typischerweise wird ein FileOutputStream verwendet, um die
serialisierten Daten in eine Datei zu schreiben.
1.4.1.3.2
Methoden von ObjectOutputStream
ObjectOutputStream besitzt sowohl Methoden, um primitive Typen zu serialisieren, als auch
die wichtige Methode writeObject, mit der ein komplettes Objekt serialisiert werden kann:
public final void writeObject(Object obj)
public void writeBoolean(boolean data)
public void writeByte(int data)
public void writeShort(int data)
public void writeChar(int data)
public void writeInt(int data)
public void writeLong(long data)
public void writeFloat(float data)
public void writeDouble(double data)
public void writeBytes(String data)
public void writeChars(String data)
public void writeUTF(String data)
1.4.1.3.3
Konzept
Aufwendig ist das Serialisieren von Objekten vor allem aus zwei Gründen:
Erstens muss zur Laufzeit ermittelt werden, welche Membervariablen das zu serialisierende
Objekt besitzt (bzw. geerbt hat) und welche von ihnen serialisiert werden sollen. Dazu wird
das Reflection-API verwendet.
Zweitens kann eine Membervariable natürlich selbst ein Objekt sein, das seinerseits
serialisiert werden muss. Insbesondere muss writeObject dabei korrekt mit zyklischen
Verweisen umgehen und dafür sorgen, dass Objektreferenzen erhalten bleiben.
WriteObject schreibt folgende Daten in den OutputStream:
75
76



Die Klasse des als Argument übergebenen Objekts.
Die Signatur der Klasse
Alle nicht-statischen, nicht-transienten Membervariablen des übergebenen Objekts
inkl. der aus allen Vaterklassen geerbten Membervariablen.
Die Methode writeObject durchsucht das übergebene Objekt nach Membervariablen und
überprüft deren Attribute. Ist eine Membervariable vom Typ static, wird es nicht serialisiert,
denn es gehört nicht zum Objekt, sondern zur Klasse des Objekts. Weiterhin werden alle
Membervariablen ignoriert, die mit dem Schlüsselwort transient deklariert wurden. Auf diese
Weise kann das Objekt Membervariablen definieren, die aufgrund ihrer Natur nicht
serialisiert werden sollen oder dürfen.
Die Sichtbarkeit einer Membervariable hat keinen Einfluss darauf, ob sie von writeObject
serialisiert wird oder nicht.
1.4.1.3.4
Das Interface Serializable
Ein Objekt kann nur dann mit writeObject serialisiert werden, wenn es das Interface
Serializable implementiert.
Zwar definiert Serializable keine Methoden, writeObject testet jedoch, ob das zu
serialisierende Objekt dieses Interface implementiert. Ist das nicht der Fall, wird eine
Ausnahme des Typs NotSerializableException ausgelöst.
1.4.1.4
Lesen von Objekten
Nachdem ein Objekt serialisiert wurde, kann es mit Hilfe der Klasse ObjectInputStream
wieder rekonstruiert werden. Analog zu ObjectOutputStream gibt es Methoden zum
Wiedereinlesen von primitiven Typen und eine Methode readObject, mit der ein
serialisiertes Objekt wieder hergestellt werden kann.
Das Deserialisieren eines Objektes kann man sich stark vereinfacht aus den folgenden
beiden Schritten bestehend vorstellen:
Zunächst wird ein neues Objekt des zu deserialisierenden Typs angelegt, und die
Membervariablen werden mit Default-Werten vorbelegt.
Zudem wird der Default-Konstruktor der ersten nicht-serialisierbaren Superklasse
aufgerufen.
Anschließend werden die serialisierten Daten gelesen und den entprechenden
Membervariablen des angelegten Objekts zugewiesen.
Das erzeugte Objekt hat anschließend dieselbe Struktur und denselben Zustand, den das
serialisierte Objekt zuvor hatte (abgesehen von den nicht serialisierten Membervariablen
des Typs static oder transient). Da der Rückgabewert von readObject vom Typ Object ist,
muss das erzeugte Objekt in den tatsächlichen Typ (oder eine seiner Oberklassen)
umgewandelt werden.
1.4.1.5
Anmerkungen
Es ist wichtig zu verstehen, dass beim Deserialisieren nicht der Konstruktor des erzeugten
Objekts aufgerufen wird. Lediglich bei einer serialisierbaren Klasse, die in ihrer
Vererbungshierarchie Superklassen hat, die nicht das Interface Serializable implementieren,
wird der parameterlose Konstruktor der nächsthöheren nicht-serialisierbaren Vaterklasse
aufgerufen. Da die aus der nicht-serialisierbaren Vaterklasse geerbten Membervariablen
nicht serialisiert werden, soll auf diese Weise sichergestellt sein, dass sie wenigstens
sinnvoll initialisiert werden.
Auch eventuell vorhandene Initialisierungen einzelner Membervariablen werden nicht
ausgeführt.
76
77
Wir könnten beispielsweise die Time-Klasse um eine Membervariable seconds erweitern:
private transient int seconds = 11;
Dann wäre zwar bei allen mit new konstruierten Objekten der Sekundenwert mit 11
vorbelegt. Bei Objekten, die durch Deserialisieren erzeugt wurden, bleibt er aber 0 (das ist
der Standardwert eines int), denn der Initialisierungscode wird in diesem Fall nicht
ausgeführt.
Beim Deserialisieren von Objekten können einige Fehler passieren. Damit ein Aufruf von
readObject erfolgreich ist, müssen mehrere Kriterien erfüllt sein:
Das nächste Element des Eingabestreams ist tatsächlich ein Objekt (kein primitiver Typ).
Das Objekt muss sich vollständig und fehlerfrei aus der Eingabedatei lesen lassen.
Es muss eine Konvertierung auf den gewünschten Typ erlauben, also entweder zu
derselben oder einer daraus abgeleiteten Klasse gehören.
Der Bytecode für die Klasse des zu deserialisierenden Objekts muss vorhanden sein. Er
wird beim Serialisieren nicht mitgespeichert, sondern muss dem Empfängerprogramm wie
üblich als kompilierter Bytecode zur Verfügung stehen.
1.4.1.6
Auszug aus der Online Hilfe
interface Serializable
Serializability of a class is enabled by the class implementing the java.io.Serializable
interface. Classes that do not implement this interface will not have any of their state
serialized or deserialized. All subtypes of a serializable class are themselves serializable.
The serialization interface has no methods or fields and serves only to identify the semantics
of being serializable.
To allow subtypes of non-serializable classes to be serialized, the subtype may assume
responsibility for saving and restoring the state of the supertype's public, protected, and (if
accessible) package fields. The subtype may assume this responsibility only if the class it
extends has an accessible no-arg constructor to initialize the class's state.
During deserialization, the fields of non-serializable classes will be initialized using the public
or protected no-arg constructor of the class. A no-arg constructor must be accessible to the
subclass that is serializable. The fields of serializable subclasses will be restored from the
stream.
When traversing a graph, an object may be encountered that does not support the
Serializable interface. In this case the NotSerializableException will be thrown and will
identify the class of the non-serializable object.
Classes that require special handling during the serialization and deserialization process
must implement special methods with these exact signatures:
private void writeObject(java.io.ObjectOutputStream out)
private void readObject(java.io.ObjectInputStream in)
The writeObject method is responsible for writing the state of the object for its particular
class so that the corresponding readObject method can restore it.
The readObject method is responsible for reading from the stream and restoring the classes
fields.
1.4.1.7
Nicht-serialisierte Membervariablen
Mitunter besitzt eine Klasse Membervariablen, die nicht serialisiert werden sollen. Typische
Beispiele sind Variablen, deren Wert sich während des Programmlaufs dynamisch ergibt,
oder solche, die nur der temporären Kommunikation zwischen zwei oder mehr Methoden
dienen. Auch Daten, die nur im Kontext der laufenden Anwendung Sinn machen, wie
77
78
beispielsweise Filehandles, Sockets oder GUI-Ressourcen, sollten nicht serialisiert werden;
sie verfallen mit dem Ende des Programms.
Membervariablen, die nicht serialisiert werden sollen, können mit dem Attribut transient
versehen werden. Dadurch werden sie beim Schreiben des Objekts mit writeObject ignoriert
und gelangen nicht in die Ausgabedatei. Beim Deserialisieren werden die transienten
Objekte lediglich mit dem typspezifischen Standardwert belegt.
1.4.2
1.4.2.1
Serialiserung von Graphen
Beispiel Graph Serialiserung Familie
GraphSerializing:
Heinrich
Lina
Peter
Jens
Renate
Christian
Bild Familienvererbungsbaum
/* GraphSerializing.java */
import java.io.*;
import java.util.*;
public class GraphSerializing
{
public static void main(String[] args)throws java.io.IOException
{
//Erzeugen der Familie
Person opa = new Person("Heinrich");
Person oma = new Person("Lina");
Person vater = new Person("Peter");
Person mutter = new Person("Renate");
Person kind1 = new Person("Jens");
Person kind2 = new Person("Christian");
vater.father = opa;
vater.mother = oma;
kind1.father = kind2.father = vater;
kind1.mother = kind2.mother = mutter;
//Serialisieren der Familie
try {
FileOutputStream fs = new FileOutputStream("test3.ser");
ObjectOutputStream os = new ObjectOutputStream(fs);
os.writeObject(kind1);
os.writeObject(kind2);
os.close();
} catch (IOException e) {
System.err.println(e.toString());
}
//Rekonstruieren der Familie
kind1 = kind2 = null;
try {
FileInputStream fs = new FileInputStream("test3.ser");
ObjectInputStream is = new ObjectInputStream(fs);
kind1 = (Person)is.readObject();
kind2 = (Person)is.readObject();
//Überprüfen der Objekte
System.out.println(kind1.name);
System.out.println(kind2.name);
System.out.println(kind1.father.name);
System.out.println(kind1.mother.name);
78
79
System.out.println(kind2.father.name);
System.out.println(kind2.mother.name);
System.out.println(kind1.father.father.name);
System.out.println(kind1.father.mother.name);
//Name des Vaters ändern
kind1.father.name = "Friedrich";
//Erneutes Überprüfen der Objekte
System.out.println("---");
System.out.println(kind1.name);
System.out.println(kind2.name);
System.out.println(kind1.father.name);
System.out.println(kind1.mother.name);
System.out.println(kind2.father.name);
System.out.println(kind2.mother.name);
System.out.println(kind1.father.father.name);
System.out.println(kind1.father.mother.name);
is.close();
} catch (ClassNotFoundException e) {
System.err.println(e.toString());
} catch (IOException e) {
System.err.println(e.toString());
}
System.in.read();
}
}
1.4.2.2
Objektreferenzen
1.4.2.2.1
Problem
Referenzen werden automatisch gesichert und rekonstruiert. Besitzt ein Objekt selbst
Strings, Arrays oder andere Objekte als Membervariablen, so werden diese ebenso wie die
primitiven Typen serialisiert und deserialisiert.
Da eine Objektvariable lediglich einen Verweis auf das im Hauptspeicher allozierte Objekt
darstellt, ist es wichtig, dass diese Verweise auch nach dem Serialisieren/Deserialisieren
erhalten bleiben. Insbesondere darf ein Objekt auch dann nur einmal angelegt werden,
wenn darauf von mehr als einer Variable verwiesen wird. Auch nach dem Deserialisieren
darf das Objekt nur einmal vorhanden sein, und die verschiedenen Objektvariablen müssen
auf dieses Objekt zeigen.
1.4.2.2.2
Lösung
Der ObjectOutputStream hält eine Hashtabelle, in der alle bereits serialisierten Objekte
verzeichnet werden. Bei jedem Aufruf von writeObject wird zunächst in der Tabelle
nachgesehen, ob das Objekt bereits serialisiert wurde. Ist das der Fall, wird in der
Ausgabedatei lediglich ein Verweis auf das Objekt gespeichert. Andernfalls wird das Objekt
serialisiert und in der Hashtabelle eingetragen. Beim Deserialisieren eines Verweises wird
dieser durch einen Objektverweis auf das zuvor deserialisierte Objekt ersetzt. Auf diese
Weise werden Objekte nur einmal gespeichert, die Objektreferenzen werden konserviert,
und das Problem von Endlosschleifen durch zyklische Referenzen ist ebenfalls gelöst.
1.4.2.2.3
Warnungen
Einerseits kann es passieren, dass mehr Objekte als erwartet serialisiert werden.
Insbesondere bei komplexen Objektbeziehungen kann es sein, dass an dem zu
serialisierenden Objekt indirekt viele weitere Objekte hängen und beim Serialisieren
wesentlich mehr Objekte gespeichert werden, als erwartet wurde. Das kostet unnötig Zeit
und Speicher.
Durch das Zwischenspeichern der bereits serialisierten Objekte in ObjectOutputStream
werden viele Verweise auf Objekte gehalten, die sonst möglicherweise für das Programm
unerreichbar wären. Da der Garbage Collector diese Objekte nicht freigibt, kann es beim
Serialisieren einer großen Anzahl von Objekten zu Speicherproblemen kommen. Mit Hilfe
79
80
der Methode reset kann der ObjectOutputStream in den Anfangszustand versetzt werden;
alle bereits bekannten Objektreferenzen werden »vergessen«. Wird ein bereits serialisiertes
Objekt danach noch einmal gespeichert, wird kein Verweis, sondern das Objekt selbst noch
einmal geschrieben.
Wenn ein bereits serialisiertes Objekt verändert und anschließend erneut serialisiert wird,
bleibt die Veränderung beim Deserialisieren unsichtbar, denn in der Ausgabedatei wird
lediglich ein Verweis auf das Originalobjekt gespeichert.
1.4.3
Serialisierung von Collections
1.4.3.1
Beispiel ObjectStore
Die folgende Klasse ObjectStore stellt ein Beispiel für einen persistenten Objektspeicher
dar.
/* StoreUser.java */
import java.io.*;
import java.util.*;
public class StoreUser
{
public static void main(String[] args)throws java.io.IOException
{
//Erzeugen und Speichern des Objektspeichers
ObjectStore tos = new ObjectStore("OttoVersand");
tos.putObject("name", "Otto Versand Hamburg");
tos.putObject("besitzer", "Otto");
Vector products = new Vector(10);
products.addElement("Hemd");
products.addElement("Hose");
products.addElement("Socken");
products.addElement("Schlips");
products.addElement("Zahnbuerste");
tos.putObject("produkte", products);
try {
tos.save();
} catch (IOException e) {
System.err.println(e.toString());
}
//Einlesen des Objektspeichers
TrivialObjectStore tos2 = new TrivialObjectStore("OttoVersand");
try {
tos2.load();
Enumeration names = tos2.getAllNames();
while (names.hasMoreElements()) {
String name = (String)names.nextElement();
Object obj = tos2.getObject(name);
System.out.print(name + ": ");
System.out.println("Classname : " + obj.getClass().toString());
if (obj instanceof Vector) {
Vector v = (Vector)obj;
for (int i = 0; i < v.size(); i++) {
System.out.println(" " + v.elementAt(i).toString());
}
} else {
System.out.println(" " + obj.toString());
}
}
} catch (IOException e) {
System.err.println(e.toString());
} catch (ClassNotFoundException e) {
System.err.println(e.toString());
}
System.in.read();
}
}
/* ObjectStore.java */
import java.io.*;
import java.util.*;
80
81
public class ObjectStore
{
private String fname;
private Hashtable objects;
public ObjectStore(String fname)
{
this.fname = fname;
if (!fname.endsWith(".tos")) {
this.fname += ".tos";
}
this.objects = new Hashtable(50);
}
public void save()
throws IOException
{
FileOutputStream fs = new FileOutputStream(fname);
ObjectOutputStream os = new ObjectOutputStream(fs);
os.writeObject(objects);
os.close();
}
public void load()
throws ClassNotFoundException, IOException
{
FileInputStream fs = new FileInputStream(fname);
ObjectInputStream is = new ObjectInputStream(fs);
objects = (Hashtable)is.readObject();
is.close();
}
public void putObject(String name, Object object)
{
objects.put(name, object);
}
public Object getObject(String name)
{
return objects.get(name);
}
public Enumeration getAllNames()
{
return objects.keys();
}
}
public class Hashtable
This class implements a hashtable, which maps keys to values. Any non-null object can be
used as a key or as a value.
To successfully store and retrieve objects from a hashtable, the objects used as keys must
implement the hashCode method and the equals method.
get(Object key)
Returns the value to which the specified key is mapped in this hashtable.
keys()
Returns an enumeration of the keys in this hashtable.
put(Object key, Object value)
Maps the specified key to the specified value in this hashtable.
1.4.3.2
Serialisierbare Standardklassen
Neben selbstgeschriebenen Klassen sind auch viele der Standardklassen des JDK
serialisierbar, insbesondere die meisten Collection-Klassen. Um beispielsweise alle Daten
eines Vektors oder einer Hashtable persistent zu speichern, genügt ein einfaches
Serialisieren. Voraussetzung ist allerdings, dass auch die Elemente der Collection
serialisierbar sind, andernfalls gibt es eine NotSerializableException. Auch die
Wrapperklassen zu den Basistypen sind standardmäßig serialisierbar und können damit
problemlos als Objekte in serialisierbaren Collections verwendet werden.
1.4.4
Kopieren durch Serialisieren
81
82
1.4.4.1
Beispiel Kopieren durch Serialisierung
/* SerializeCopy.java */
import java.io.*;
import java.util.*;
public class SerializeCopy
{
public static Object serializedCopyObject(Object o)
throws IOException, ClassNotFoundException
{
//Serialisieren des Objekts
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(out);
os.writeObject(o);
os.flush();
//Deserialisieren des Objekts
ByteArrayInputStream in = new ByteArrayInputStream(
out.toByteArray()
);
ObjectInputStream is = new ObjectInputStream(in);
Object ret = is.readObject();
is.close();
os.close();
return ret;
}
public static void main(String[] args)
{
try {
//Erzeugen des Buchobjekts
Book book = new Book();
book.author = "Peitgen, Heinz-Otto";
String[] s = {"Jürgens, Hartmut", "Saupe, Dietmar"};
book.coAuthors = s;
book.title = "Bausteine des Chaos";
book.publisher = "rororo science";
book.pubyear = 1998;
book.pages = 514;
book.isbn = "3-499-60250-4";
book.reflist = new Vector();
book.reflist.addElement("The World of MC Escher");
book.reflist.addElement(
"Die fraktale Geometrie der Natur"
);
book.reflist.addElement("Gödel, Escher, Bach");
System.out.println(book.toString());
//Erzeugen und Verändern der Kopie
Book copy = (Book)serializedCopyObject(book);
copy.title += " - Fraktale";
copy.reflist.addElement("Fractal Creations");
//Ausgeben von Original und Kopie
System.out.print(book.toString());
System.out.println("---");
System.out.print(copy.toString());
System.in.read();
} catch (IOException e) {
System.err.println(e.toString());
} catch (ClassNotFoundException e) {
System.err.println(e.toString());
}
}
}
class Book
implements Serializable
{
public String author;
public String[] coAuthors;
public String title;
public String publisher;
public int
pubyear;
public int
pages;
public String isbn;
public Vector reflist;
public String toString()
82
83
{
String NL = System.getProperty("line.separator");
StringBuffer ret = new StringBuffer(200);
ret.append(author + NL);
for (int i = 0; i < coAuthors.length; ++i) {
ret.append(coAuthors[i] + NL);
}
ret.append("\"" + title + "\"" + NL);
ret.append(publisher + " " + pubyear + NL);
ret.append(pages + " pages" + NL);
ret.append(isbn + NL);
Enumeration e = reflist.elements();
while (e.hasMoreElements()) {
ret.append(" " + (String)e.nextElement() + NL);
}
return ret.toString();
}
}
1.4.4.2
Serialisieren in Byte Array
Soll ein komplexes Objekt kopiert werden, wird dazu üblicherweise das Interface Cloneable
implementiert und eine Methode clone zur Verfügung gestellt, die den eigentlichen
Kopiervorgang vornimmt. Sollte ein Objekt kopiert werden, das Cloneable nicht
implementiert, blieb bisher nur der umständliche Weg über das manuelle Kopieren aller
Membervariablen. Das ist insbesondere dann mühsam und fehlerträchtig, wenn das zu
kopierende Objekt Unterobjekte enthält, die ihrerseits kopiert werden müssen (deep copy
anstatt shallow copy).
Der Schlüssel zum Kopieren von Objekten mit Hilfe der Serialisierung liegt darin, anstelle
der üblichen dateibasierten Streamklassen solche zu verwenden, die ihre Daten im
Hauptspeicher halten. Am besten sind dazu ByteArrayOutputStream und
ByteArrayInputStream geeignet.
Das Verfahren findet dort seine Grenzen, wo die zu kopierenden Objekte nicht serialisierbar
sind oder nicht-serialisierbare Unterobjekte enthalten.
Der Vorgang des Serialisierens/Deserialisierens ist langsamer als das direkte Kopieren der
Objektattribute.
1.5
Exception Handling
1.5.1 Behandlung von Exceptions
1.5.1.1
Beispiel Simple Exception
Achtung :
Im Debug Modus laufen lassen (DOS Fenster wird sofort wieder geschlossen !).
/* SimpleException.java */
public class SimpleException
{
public static void main(String[] args)
{
int i, base = 0;
try {
for (base = 10; base >= 2; --base) {
i = Integer.parseInt("40",base);
System.out.println("40 base "+base+" = "+i);
}
} catch (NumberFormatException e) {
System.out.println("Fehler aufgetreten !");
System.out.println("Ursache: "+e.getMessage());
e.printStackTrace();
System.out.println("40 ist keine Zahl zur Basis "+base);
}
}
}
83
84
1.5.1.2
Die try-catch-Anweisung
Das Behandeln von Ausnahmen erfolgt mit Hilfe der try-catch-Anweisung:
try {
Anweisung;
...
} catch (Ausnahmetyp x) {
Anweisung;
...
}
Der try-Block enthält eine oder mehrere Anweisungen, bei deren Ausführung ein Fehler des
Typs Ausnahmetyp auftreten kann. In diesem Fall wird die normale Programmausführung
unterbrochen, und der Programmablauf fährt mit der ersten Anweisung nach der catchKlausel fort, die den passenden Ausnahmetyp deklariert hat.
Hier kann nun Code untergebracht werden, der eine angemessene Reaktion auf den Fehler
realisiert.
1.5.1.3
Das Fehlerobjekt
Fehlerobjekte sind dabei Instanzen der Klasse Throwable oder einer ihrer Unterklassen. Sie
werden vom Aufrufer der Ausnahme erzeugt und als Parameter an die catch-Klausel
übergeben. Das Fehlerobjekt enthält Informationen über die Art des aufgetretenen Fehlers.
So liefert beispielsweise die Methode getMessage einen Fehlertext (wenn dieser explizit
gesetzt wurde), und printStackTrace druckt einen Auszug aus dem Laufzeit-Stack. Am
einfachsten kann der Fehlertext mit der Methode toString der Klasse Throwable
ausgegeben werden:
1.5.1.4
Die Fehlerklassen von Java
Alle Laufzeitfehler in Java sind Unterklassen der Klasse Throwable. Throwable ist eine
allgemeine Fehlerklasse, die im Wesentlichen eine Klartext-Fehlermeldung speichern und
einen Auszug des Laufzeit-Stacks ausgeben kann.
Unterhalb von Throwable befinden sich zwei große Vererbungshierarchien:
Die Klasse Error ist Superklasse aller schwerwiegenden Fehler. Diese werden
hauptsächlich durch Probleme in der virtuellen Java-Maschine ausgelöst. Fehler der Klasse
Error sollten in der Regel nicht abgefangen werden, sondern (durch den StandardFehlerhandler) nach einer entsprechenden Meldung zum Abbruch des Programms führen.
Alle Fehler, die möglicherweise für die Anwendung selbst von Interesse sind, befinden sich
in der Klasse Exception oder einer ihrer Unterklassen. Ein Fehler dieser Art signalisiert
einen abnormen Zustand, der vom Programm abgefangen und behandelt werden kann.
Viele Pakete der Java-Klassenbibliothek definieren ihre eigenen Fehlerklassen. So gibt es
spezielle Fehlerklassen für die Dateiein- und -ausgabe, die Netzwerkkommunikation oder
den Zugriff auf Arrays.
Die Klasse Throwable
public class Throwable extends Object
Konstruktoren
public Throwable()
public Throwable( String message )
// Nachricht, die man in der catch-Anweisung
// abfragen kann.
Methoden
public String getMessage()
// Nachricht, mit der das Objekt erzeugt wurde
public String toString()
// Zeichenkettenrepräsentation
public void printStackTrace()
// Ausgabe des Stacks auf die Standardausgabe
84
85
public
//
public
//
void printStackTrace( PrintStream s )
Ausgabe des Stacks nach s
Throwable fillInStackTrace()
Hinzufügen der Ausnahme zum Trace
Die Ausnahmen des Paketes java.lang
public class ArithmeticException extends RuntimeException
// allgemeine Arithmetik-Ausnahme
public class ArrayIndexOutOfBoundsException
extends IndexOutOfBoundsException
// unerlaubter Feldzugriff
public class ArrayStoreException extends RuntimeException
// Ausnahme beim Speichern
public class ClassCastException extends RuntimeException
// unerlaubte Klassenumwandlung
public class ClassNotFoundException extends Exception
// Zu ladende Klasse ist nicht vorhanden
public class CloneNotSupportedException extends Exception
// Clonen ist nicht erlaubt
public class IllegalAccessException extends Exception
// unerlaubter Zugriff
public class IllegalArgumentException extends RuntimeException
// falsches Argument
public class IllegalMonitorStateException extends RuntimeException
// siehe Kapitel über Threads
public class IllegalThreadStateException
extends IllegalArgumentException
// unerlaubter Methodenzugriff in diesem Zustand des Threads
public class IndexOutOfBoundsException extends RuntimeException
// unerlaubter Zugriff
public class InstantiationException extends Exception
// Ausnahme bei der Objekterzeugung
public class InterruptedException extends Exception
// Unterbrochen
public class NegativeArraySizeException extends RuntimeException
// negative Feldgröße
public class NoSuchFieldException extends Exception
// falscher Komponentenzugriff
public class NoSuchMethodException extends Exception
// falscher Methodenzugriff
public class NullPointerException extends RuntimeException
// meist: Variable referenziert noch kein Objekt
public class NumberFormatException extends IllegalArgumentException
// falsches Zahlenformat
public class RuntimeException extends Exception
// allgemeine Ausnahme zur Laufzeit, muß nicht mit try-catch
// abgefangen werden
public class SecurityException extends RuntimeException
// Ausnahme im Sicherheitsmanagement
public class StringIndexOutOfBoundsException
extends IndexOutOfBoundsException
// unerlaubter String-Zugriff
1.5.1.5
Fortfahren nach Fehlern
Wie lautet die Ausgabe des Programmes ?
public static void main(String[] args)
{
int i, base = 0;
for (base = 10; base >= 2; --base) {
try {
i = Integer.parseInt("40",base);
System.out.println("40 base "+base+" = "+i);
} catch (NumberFormatException e) {
System.out.println(
"40 ist keine Zahl zur Basis "+base
);
}
}
}
Die Reaktion auf eine Ausnahme muss keinesfalls zwangsläufig darin bestehen, das
Programm zu beenden. Statt dessen kann auch versucht werden, den Fehler zu beheben
oder zu umgehen, um dann mit dem Programm fortzufahren.
85
86
Wird im obigen Programm die try-catch-Anweisung in die Schleife gesetzt, so fährt das
Programm nach jedem Fehler fort und versucht, die Konvertierung zur nächsten Basis
vorzunehmen:
Die Ausgabe des Programms lautet nun:
40 base 10 = 40
40 base 9 = 36
40 base 8 = 32
40 base 7 = 28
40 base 6 = 24
40 base 5 = 20
40 ist keine Zahl zur Basis 4
40 ist keine Zahl zur Basis 3
40 ist keine Zahl zur Basis 2
1.5.1.6
Beispiel MultipleCatchException
/* MultipleCatchException.java */
public class MultipleCatchException
{
public static void main(String[] args)
{
int i, j, base = 0;
String[] numbers = new String[3];
numbers[0] = "10";
numbers[1] = "20";
numbers[2] = "30";
try {
for (base = 10; base >= 2; --base) {
for (j = 0; j <= 3; ++j) {
i = Integer.parseInt(numbers[j],base);
System.out.println(
numbers[j]+" base "+base+" = "+i
);
}
}
} catch (IndexOutOfBoundsException e1) {
System.out.println(
"IndexOutOfBoundsException: " + e1.toString()
);
} catch (NumberFormatException e2) {
System.out.println(
"NumberFormatException: " + e2.toString()
);
}finally {
System.out.println("Hier wird endgueltig aufgeraeumt !");
};
}
}
1.5.1.7
Mehrere catch-Klauseln
Ein Programm kann auf verschiedene Fehler reagieren, indem es mehr als eine catchKlausel verwendet. Jede catch-Klausel fängt die Fehler ab, die zum Typ des angegebenen
Fehlerobjekts zuweisungskompatibel sind. Dazu gehören alle Fehler der angegebenen
Klasse und all ihrer Unterklassen. Die einzelnen catch-Klauseln werden in der Reihenfolge
ihres Auftretens abgearbeitet.
1.5.1.8
Die finally-Klausel
Die try-catch-Anweisung enthält einen optionalen Bestandteil. Mit Hilfe der finally-Klausel,
die als letzter Bestandteil einer try-catch-Anweisung verwendet werden darf, kann ein
86
87
Programmfragment definiert werden, das immer dann ausgeführt wird, wenn die zugehörige
try-Klausel betreten wurde. Dabei spielt es keine Rolle, welches Ereignis dafür
verantwortlich war, dass die try-Klausel verlassen wurde. Die finally-Klausel wird
insbesondere dann ausgeführt, wenn der try-Block durch eine der folgenden Anweisungen
verlassen wurde:
wenn das normale Ende des try-Blocks erreicht wurde
wenn eine Ausnahme aufgetreten ist, die durch eine catch-Klausel behandelt wurde
wenn eine Ausnahme aufgetreten ist, die nicht durch eine catch-Klausel behandelt wurde
wenn der try-Block durch eine der Sprunganweisungen break, continue oder return
verlassen werden soll
Die finally-Klausel ist also der ideale Ort, um Aufräumarbeiten durchzuführen. Hier können
beispielsweise Dateien geschlossen oder Ressourcen freigegeben werden.
1.5.2
Weitergabe von Exceptions
1.5.2.1
Beispiel Divide
public class Divide {
public static void main( String[] args ) {
try {
int x = divide( 5, 0 );
}
catch ( DivisionByZeroException e ) {
System.err.println(
"Ausnahme
e.getMessage() );
}
}
bei
der
Division:
"
static int divide ( int x, int y ) throws DivisionByZeroException {
if ( y == 0 )
throw( new DivisionByZeroException() );
return x / y;
}
}
class DivisionByZeroException extends ArithmeticException {
public DivisionByZeroException() {
super( "Division durch 0" );
}
}
Die throw-Anweisung hat dabei den Charakter einer Sprunganweisung. Sie unterbricht das
Programm an der aktuellen Stelle und verzweigt unmittelbar zu der umgebenden catchKlausel. Gibt es eine solche nicht, wird der Fehler an den Aufrufer weitergegeben. Tritt der
Fehler innerhalb einer try-catch-Anweisung mit einer finally-Klausel auf, wird diese noch vor
der Weitergabe des Fehlers ausgeführt.
87
+
88
1.5.2.2
Die catch-or-throw-Regel
Bei der Behandlung von Ausnahmen in Java gibt es die Grundregel catch or throw. Sie
besagt, dass jede Ausnahme entweder behandelt oder weitergegeben werden muss.
Soll eine Ausnahme nicht behandelt, sondern weitergegeben werden, so kann dies einfach
dadurch geschehen, dass eine geeignete try-catch-Anweisung nicht verwendet wird.
In der Regel gibt es dann jedoch zunächst einen Fehler beim Übersetzen des Programms,
denn der Compiler erwartet, dass jede mögliche Ausnahme, die nicht behandelt, sondern
weitergegeben wird, mit Hilfe der throws-Klausel zu deklarieren ist. Dazu wird an das Ende
des Methodenkopfes das Schlüsselwort throws mit einer Liste aller Ausnahmen, die nicht
behandelt werden sollen, angehängt:
Tritt eine Ausnahme ein, sucht das Laufzeitsystem zunächst nach einer die Anweisung
unmittelbar umgebenden try-catch-Anweisung, die diesen Fehler behandelt. Findet es eine
solche nicht, wiederholt es die Suche sukzessive in allen umgebenden Blöcken. Ist auch
das erfolglos, wird der Fehler an den Aufrufer der Methode weitergegeben. Dort beginnt die
Suche nach einem Fehler-Handler von neuem, und der Fehler wird an die umgebenden
Blöcke und schließlich an den Aufrufer weitergereicht. Enthält auch die Hauptmethode
keinen Code, um den Fehler zu behandeln, bricht das Programm mit einer Fehlermeldung
ab.
Da alle Fehler, die nicht innerhalb einer Methode behandelt werden, dem Compiler mit Hilfe
der throws-Klausel bekanntgemacht werden, kennt dieser zu jeder Methode die potentiellen
Fehler, die von ihr verursacht werden können. Mit diesen Informationen kann der Compiler
bei jedem Methodenaufruf sicherstellen, dass der Aufrufer seinerseits die catch-or-throwRegel einhält.
1.5.2.3
Auslösen von Ausnahmen
Mit Hilfe der throw-Anweisung kann ein Objekt aus einer Fehlerklasse dazu verwendet
werden, eine Ausnahme zu erzeugen.
Die throw-Anweisung kann nicht nur dazu verwendet werden, neue Fehler auszulösen. Sie
kann ebenfalls eingesetzt werden, um innerhalb der catch-Klausel einer try-catchAnweisung das übergebene Fehlerobjekt erneut zu senden. In diesem Fall wird nicht noch
einmal dieselbe catch-Klausel ausgeführt, sondern der Fehler wird gemäß den oben
genannten Regeln an den umgebenden Block bzw. den Aufrufer weitergegeben.
Auch selbstdefinierte Ausnahmen müssen sich an die catch-or-throw-Regel halten. Wird die
Ausnahme nicht innerhalb derselben Methode behandelt, ist sie mit Hilfe der throws-Klausel
im Methodenkopf zu deklarieren und weiter oben in der Aufrufkette zu behandeln.
1.6
Globalisierung
1.6.1 Problem
Manche Klassen sind dafür gedacht, in Programmen eingesetzt zu werden, die nicht nur im
eigenen Land Anwender finden sollen. Programmierer solcher Klassen sollten darauf
achten, dass die Aus- und Eingaben des Programms den jeweiligen nationalen
Besonderheiten entsprechen.
Die beiden wichtigen Klassen für die Sprachanpassung sind Locale und
ResourceBundle.
1.6.2
Beispiel LocalizedClock
Die folgende Uhr wechselt in jeder Sekunde ihr Aussehen, indem sie alle verfügbaren
besonderen Zeitformate nacheinander anzeigt.
import java.util.Locale;
88
89
import java.util.Date;
import java.text.DateFormat;
public class LocalizedClock extends Thread {
private Locale[] locales = null;
public static void main( String[] args ) {
new LocalizedClock().start();
}
public LocalizedClock() {
locales = DateFormat.getAvailableLocales();
}
public void run() {
int localeIndex = 0;
while ( isAlive() ) {
System.out.print( "\r" +
DateFormat.getDateTimeInstance(
DateFormat.DEFAULT,
DateFormat.DEFAULT,
locales[ localeIndex ]).format( new Date() ) +
" -- Locale: " + locales[ localeIndex ].toString() +
"
" );
if ( ++localeIndex >= locales.length - 1 )
localeIndex = 0;
try {
sleep( 1000 );
}
catch ( InterruptedException e ) {}
}
}
}
Der Konstruktor bestimmt die vorhandenen Datumsformate und speichert sie in der
Instanzvariablen locales ab.
Die Methode run() geht das gespeicherte Formatfeld sekundenweise Schritt für Schritt
durch, zeigt Datum und Uhrzeit in dem entsprechenden Format und den Namen dieses
Formats an.
1.6.3
Die Klasse Locale
Locale ist vergleichbar mit dem Context für die Grafikausgabe.
A Locale object represents a specific geographical, political, or cultural region. An operation
that requires a Locale to perform its task is called locale-sensitive and uses the Locale to
tailor information for the user. For example, displaying a number is a locale-sensitive
operation--the number should be formatted according to the customs/conventions of the
user's native country, region, or culture.
The first argument to constructor is a valid ISO
Language Code. These codes are the lower-case two-letter codes as defined by ISO-639.
You can find a full list of these codes at a number of sites, such as:
http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt
The second argument to constructor is a valid ISO Country
Code. These codes are the upper-case two-letter codes as defined by ISO-3166. You can
find a full list of these codes at a number of sites, such as:
http://www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html
The second constructor requires a third argument--the Variant. The Variant codes are
vendor and browser-specific. For example, use WIN for Windows, MAC for Macintosh, and
POSIX for POSIX.
If you want to see whether particular resources are available for the Locale you construct,
you must query those resources. For example, ask the NumberFormat for the locales it
supports using its getAvailableLocales method.
The JDK provides a number of classes that perform locale-sensitive operations. For
example, the NumberFormat class formats numbers, currency, or percentages in a localesensitive manner. Classes such as NumberFormat have a number of convenience methods
for creating a default object of that type.
89
90
A Locale is the mechanism for identifying the kind of object (NumberFormat) that you would
like to get. The locale is just a mechanism for identifying objects, not a container for the
objects themselves.
1.6.4
Die Klasse DataFormat
DateFormat is an abstract class for date/time formatting subclasses which formats and
parses dates or time in a language-independent manner. The date/time formatting subclass,
such as SimpleDateFormat, allows for formatting (i.e., date -> text), parsing (text -> date),
and normalization. The date is represented as a Date object or as the milliseconds since
January 1, 1970, 00:00:00 GMT.
getAvailableLocales()
Gets the set of locales for which DateFormats are installed.
getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale)
Gets the date/time formatter with the given formatting styles for the given locale.
Statische Funktion, liefert DateFormat Objekt als ein Formatierobjekt
format(Date date)
Formats a Date into a date/time string.
1.6.5
Beispiel SimpleGlobalMenue
public class SimpleMenue
extends JFrame
{
public SimpleMenue()
{
super("Swing-Globalisierungstest");
addWindowListener(new WindowClosingAdapter());
//
Locale locale = Locale.US;
Locale locale = Locale.GERMANY;
ResourceBundle labelBundle = ResourceBundle.getBundle(
"ViewerBundle", locale );
String [] labels
labels[ 0 ] =
labels[ 1 ] =
labels[ 2 ] =
= new String[7];
labelBundle.getString( "searchButton" );
labelBundle.getString( "clearButton" );
labelBundle.getString( "executeButton" );
JPanel bp = new JPanel();
getContentPane().setLayout(new BorderLayout());
getContentPane().add("North", bp);
bp.add(new JButton(labels[0]));
bp.add(new JButton(labels[1]));
bp.add(new JButton(labels[2]));
}
public static void main(String[] args)
{
SimpleMenue frame = new SimpleMenue();
frame.setLocation(100, 100);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
public class ViewerBundle extends ListResourceBundle {
static final Object[][] labels = {
{ "searchButton", "search" },
{ "clearButton", "clear" },
{ "executeButton", "execute" }};
public Object[][] getContents() {
return labels;
}
}
public class ViewerBundle_de extends ViewerBundle {
static final Object[][] contents = {
90
91
{ "searchButton", "Suchen" },
{ "clearButton", "Loeschen" },
{ "executeButton", "Starten" }};
public Object[][] getContents() {
return contents;
}
}
public class ViewerBundle_en extends ViewerBundle {
static final Object[][] labels = {
{ "searchButton", "search" },
{ "clearButton", "clear" },
{ "executeButton", "execute" }};
public Object[][] getContents() {
return labels;
}
}
1.6.6
Die Klasse ResourceBundle
ResourceBundle objects represent a bundle of resources, which belong to a Locale object.
There is no object, which represents the set of bundles.
The set of bundles is modeled by static functions of the ResourceBundle class.
When the program needs a locale-specific resource, a String for example, the program can
load it from the resource bundle that is appropriate for the current user's locale using the
static getBundle method.
Resource bundle objects contain key/value pairs. The keys uniquely identify a locale-specific
object in the bundle.
Each related subclass of ResourceBundle has the same base name plus an additional
component that identifies its locale.
Each related subclass of ResourceBundle contains the same items, but the items have
been translated for the locale represented by that ResourceBundle subclass.
2
Die Unified Modeling Language (UML) und der Rational Unified Software Development
Process (RUP)
2.1
UML im Überblick
2.1.1
2.1.1.1
Dokumentation
Wozu dient die Dokumentation
Die Dokumentation dient:
 Zur Reflexion der eigenen (manchmal wirren) Gedanken,
 Zur Abspeicherung der eigenen Überlegungen im Hinblick auf einen
Personalwechel oder eine spätere Wartung,
 Zur Unterstützung der Kommunikation mit Kollegen während der Analyse und
Entwurfsphasen
 Zur Unterstützung vertraglicher Vereinbarungen
2.1.1.2
Anmerkungen zur Dokumentation
In allen Ingenieurdisziplinen werden üblicherweise Zeichnungen angefertigt. Man denke z.B.
an den Haus- oder Maschinenbau. Diese Zeichnungen werden an Subunternehmer
ausgeliefert, dienen als Grundlage für die Kosten- und Zeitabschätzung, für die Ressourcen
und Arbeitsverteilung.
Die Zeichnungen sind das Modell des entstehenden Produkts.
Eine wohldefinierte und ausdrucksstarke Notation ermöglicht erst die Kommunikation mit
anderen Entwicklern oder Kunden.
91
92
Die Designer müssen sicherstellen, dass das Modell die unterschiedlichen Anforderungen
an das Produkt berücksichtigt.
Das Durchdenken alternativer Modelle führt zu einem tieferen Verständnis des Systems und
damit letztendlich zu einem besseren Produkt.
Ein Problem der heutigen Softwareentwicklung besteht darin, dass zu schnell programmiert
wird. Damit befindet man sich bereits auf einem niedrigen Abstraktionslevel der
Systementwicklung.
Die Gründe hierfür sind vielfältig. Manager werden ängstlich, wenn kein Code produziert
wird. Programmierer fühlen sich beim Programmieren sicherer als beim Bilden abstrakter
Modelle.
Insgesamt wird aber inzwischen die Bildung von Modellen in der Software Engineering
Community genauso akkzeptiert wie in anderen Ingenieurdisziplinen auch.
Die Dokumentation ist also wichtig ! Sie sollte aber nicht den Entwicklungsprozess steuern !
Hier hilft eine angemessene Formalisierung der Vorgehensweise. Bei einem 1-MannWegwerf-Programm für 1 Tag genügt ggf. eine Kritzelei auf einem Briefumschlag. Bei 100Mann-Projekten sind dagegen Bücher mit Entwurfsbeschreibungen erforderlich.
Wenn die Designer u. Programmierer eines Systems hochqualifiziert sind und bereits seit
einiger Zeit eng zusammenarbeiten, kann die Dokumentation etwas sparsamer ausfallen als
bei einer neu zusammengestellten Mannschaft von Hochschulabgängern.
Ein zu hoher Dokumentationsaufwand stabilisiert möglicherweise den Zustand eines
Systems und verhindert damit die für eine Weiterentwicklung und Verbesserung
erforderlichen Iterationen. Daher sollten zur Minimierung des Aufwandes möglichst CASETools verwendet werden.
Die Dokumentation sollte inkrementell mit dem System wachsen. Sie bildet dann die Basis
für die Reviews zur Qualitätssicherung.
2.1.2
Geschichte der UML
Bezeichnungen von einigen der bisher verwendeten Methoden :
Booch Methode
OMT von James Rumbaugh
OOSE/Objectory von Ivar Jacobson
Coad Yourdon oder OOA/OOD
Jede Methode hatte ihre eigenen Notationen, ein eigenes Vorgehensmodell oder Prozess
und ihre eigenen Tools.
Im Laufe der Zeit ähnelten sich die Methoden immer mehr. Dies wurde von den MethodenGurus erkannt und führte zur Entwicklung von UML.
Ein zentrales Ziel bei der Entwicklung von UML war die Beendigung des Methodenkriegs.
Die Unified Modelling Language (UML) ist eine visuelle Modellierungssprache und wurde
von Grady Booch, James Rumbaugh und Ivar Jacobson entworfen.
UML ist offen, d.h. Firmen dürfen die Sprache frei benutzen, Toolhersteller dürfen passende
CASE-Tools erstellen.
1996 entstand ein UML Partner Konsortium aus Rose, DigitalEquipment, HP, IBM,
Microsoft, Oracle, Texas Instruments und weitere Organisationen.
Im Januar 1997 wurde die Version 1.0 ausgeliefert.
Die führende Organisation zur Standardisierung im objektorientierten Bereich, OMG (Object
Managment Group), hat die UML im Nov 97 als Standard akzeptiert und damit die
Verantwortung für die weitere Entwicklung der UML übernommen.
2.1.3
Views
92
93
Eine in irgend einem Sinne vollstandige Darstellung eines Systems ist bei komplexen
Systemen nicht möglich. Z.B. lässt sich ein Automotor niemals in einer einzigen Zeichnung
darstellen.
Die Darstellungen zeigen in der Regel nur ausgewählte Ansichten (Views) des Systems.
Z.B. zeigen Darstellungen eines Softwaresystems das Vererbungsnetz bestimmter
Schlüsselklassen oder alle Klassen, die von einer bestimmten Klasse verwendet werden
oder die Verteilung auf die Hardware oder die Verzeichnisstruktur.
Die Einteilung und die Bezeichnung der Views hängt von der Sicht des Autors ab.
Vgl. RefMan S.24
2.2
RUP im Überblick
Jede Softwareerstellung sollte in einem festgelegten organisatorischen Rahmen erfolgen.
Ein Software Development Process definiert wer, was, wann und wie tun soll, um ein
bestimmtes Ziel zu erreichen. Der Prozess dient damit als Hilfe für die beteiligten
Entwickler.
Der Rational Unified Process beschreibt die durchzuführenden Aktivitäten (Activities)
geordnet nach sogenannten Disciplines und die Reihenfolge des Arbeitsablaufes geordnet
nach sogenannten Phasen.
Der Rational Unified Process basiert auf dem 1987 veröffentlichten Objectory Process
(Ericcson) und der 1997 standardisierten UML.
2.3
Aktivitäten des RUP und die zugehörigen UML Diagramme
Man strukturiert die für ein Softwareprodukt aufzuwendenden Aktivitäten gemäß folgender
Disciplines
Business Modeling
Requirements
Analyse
Design
Implementation
Test
Deployment
Configuration&Change Management
Project Management
Environment
Business Modeling wird in einer eigenen Vorlesung behandelt.
2.3.1
Business Modeling
Der Zweck des Business Modeling besteht darin,
die Struktur und die Dynamik einer Firma zu verstehen,
die aktuellen Probleme kennen zu lernen und Möglichkeiten für Verbesserungen zu
erkunden,
Anforderungen für Softwaresysteme zur Automatisierung abzuleiten.
Das Business Modeling ist Gegenstand einer eigenen Vorlesung.
Wir betrachten im Folgenden ausschließlich Beispiele, bei denen der „Geschäftsprozess“
einfach und aus dem täglichen Leben bekannt ist.
Als Beispiele betrachten wir Getränkeautomat und Bibliotheksystem.
93
94
2.3.2
Requirements
2.3.2.1
Erstellen einer Vision
2.3.2.1.1
Beispiel Getränkeautomat
Die AutomatenApplication steuert die Ausgabe eines vom Kunden selektierten Gerätes.
Die Bezahlung kann in bar (einschließlich Scheinannahme) und per Karte erfolgen.
Die Bedienung erfolgt über Touchscreen.
Der Automat informiert einen zentralen Monitor über besondere Ereignisse.
Der Status des Automaten kann vom zentralen Monitor erfragt werden.
Im Rahmen der Wartung soll eine statistische Auswertung möglic sein.
Das System soll einfach wartbar sein.
2.3.2.1.2
Beispiel Bibliothek
Bibliothek:
Vision
Die Bibliothekssoftware soll die Buchausleihe an der FH unterstützen Bücher und Leser
sollen registriert werden können.
Bücher sollen schnell gesucht werden können.
Bücher sollen reserviert werden können.
Automatische Mahnungen sollen versendet werden können.
Zur Einsparung von Personal soll ein automatisches Transportsystem mit Hochlager und
Scanner zur Verfügung stehen.
Eine Fernausleihe soll möglich sein.
Use Case Buch Ausleihen
Gutfall:
Das System zeigt das Login Formular
Der Kunde editiert Name und Passwort
Das System zeigt die BuchNavigationsAnsicht
Der Kunde selektiert ein Buch und drückt „Ausleihen“
Das System beauftragt das HochregalSystem mit der Beschaffung und den
Ausgabeautomaten mit der Ausgabe
Der Kunde logged sich aus.
Schlechtfall:
2.3.2.1.3
Konzept
Die Vision definiert die Kundensicht des Products. Sie enthält eine knappe Darstellung der
wesentlichsten Bedürfnisse, ausgedrückt in der Terminologie des Kunden.
Die Vision bildet die Basis für die detaillierte Auflistung der Requirements.
2.3.2.2
Aufnehmen der Stakeholder Requests
Stakeholder = customer, end user, marketing person, and so on
Liste mit allen vom Kunden formulierten Anforderungen für das System.
Technisch werden diese Requests als Change Requests verwaltet.
94
95
2.3.2.3
Erstellen eines Use Case Models
2.3.2.3.1
Beispiel Getränkeautomat
Gutfall
Touchscreen zeigt die vorhandenen Flaschentypen
Der Kunde drückt den Button mit dem Cola Icon
(Der Automat prüft, ob Cola vorhanden ist)
Der Automat zeigt den Preis.
Der Kunde wirft zu viel Geld ein.
Der Automat wirft eine Cola Flasche aus.
Der Automat gibt den überzahlten Betrag zurück.
Schlechtfall
…wie oben
Der Kunde wirft einen Hosenknopf ein
Der Automat gibt den Knopf zurück
Der Automat zeigt die Meldung „Unzulässiges Zahlungsmittel“
Schlechtfall:
…wie oben
Der Automat zeigt die Meldung „Cola leer“
Der Automat gibt den eingegebenen Betrag zurück
Grafik
Flaschenfüller
Kunde
Lieferant
Kauf
Geldentnehmer
männlich
weiblich
Rückgabe
Systemstatus
Wartungstechniker
Softwareupdate
Flaschenausgeber Geldausgeber
2.3.2.3.2
zentraler Monitor
Beispiel Bibliothek
95
96
Ausleihe
Fernausleihe
Leser
Hochregallager
Rückgabe
Datenbank
Bibliothekar
Buchpflege
E-Mail Server
Wartungstechniker
Leserpflege
Scanner
Ausgabeautomat
2.3.2.3.3
Überblick
Ein Use Case Diagram zeigt eine Reihe externer Actors und deren Beziehungen zu den
Use Cases.
Der Actor kann ein Mensch oder ein anderes Softwaresystem oder ein Gerät sein.
Use Cases
unterstützen die Entscheidungen über funktionale Anforderungen
beschreiben die funktionalen Anforderungen
unterstützen die Überprüfung der Vereinbarkeit unterschiedlicher Anforderungen
erleichtern die Kommunikation zwischen den Entwicklern
bilden die Basis für den Entwurf des Systems
bilden die Basis für spätere Tests
erleichtern die Verfolgung der Anforderungen zu den zugehörigen Klassen und Funktionen
2.3.2.3.4
System
Das System wird durch ein Rechteck beschrieben.
Das System muss nicht notwendigerweise ein Softwaresystem sein. Es kann auch eine
Maschine oder eine Firma sein.
2.3.2.3.5
Actor
2.3.2.3.5.1
Charakterisierung
Ein Actor interagiert mit dem System indem er Nachrichten sendet und empfängt.
96
97
Der Actor ist ein Typ und keine Instanz. Er repräsentiert damit eine Rolle und nicht einen
individuellen Nutzer des Systems.
Der Actor hat einen Namen, der seine Rolle reflektiert.
Ein Use Case wird immer durch einen Actor initiiert, der eine Nachricht an das System
sendet (Stimulus).
Die Aktoren bilden den Context des Systems. Der Context eines Subsystems oder einer
Klasse besteht in der Regel ausschließlich aus Software.
Ein Actor Model Element hat als Standard Icon ein Strichmännchen.
2.3.2.3.5.2
Beziehungen zwischen Actors
In Use Cases werden z.B. Vererbungsbeziehungen benutzt, um das gemeinsame Verhalten
von Actors zu beschreiben.
2.3.2.3.6
Use Case
2.3.2.3.6.1
Charakterisierung
Ein Use Case ist definiert als eine Menge von Sequenzen von Actions, welche ein
beobachtbares Resultat für einen bestimmten Actor ergeben.
Jeder Use Case beschreibt eine spezifische Funktionalität des Systems.
Ein Use Case wird immer von einem Actor initiiert.
Ein Use Case liefert immer einen Wert an den Anwender. Dieser Wert repräsentiert den
Wunsch des Anwenders.
Im Verlauf eines Use Case können Interaktionen mit dem Actor stattfinden (z.B.
Maskeneingaben). Ein Use Case ist erst dann vollständig , wenn der Endwert produziert
wurde.
Die Verbindung Actor Use Case wird auch als Communication Association bezeichnet.
Ein Use Case ist eine Klasse und keine Instanz.
Er beschreibt die Funktionalität als Ganzes einschließlich Alternativen, Fehler und
Exceptions.
Eine Instanz des Use Case ist ein Scenario. Ein Scenario stellt einen ganz bestimmten
Ausführungspfad dar, z.B. einen Fehlerpfad.
Use Cases können für das gesamte System wie für einzelne Klassen erstellt werden.
2.3.2.3.6.2
Spezifikation
Die Beschreibung des Use Case erfolgt meist in Textform.
Sie sollte folgende Aussagen enthalten :
Was soll mit dem Use Case erreicht werden ?
Welcher Actor intiiert den Use Case in welcher Situation ?
Welche Botschaften tauschen Actor und System aus ?
Welche alternativen Ausführungen können in Abhängigkeit von welchen Bedingungen
auftreten ?
Wann ist der Use Case beendet und welchen Wert liefert er an den Actor ?
97
98
Eine allgemeine textuelle Beschreibung kann durch spezielle Szenarien ergänzt werden.
Diese beschreiben eine spezifische Situation unter Verwendung konkreter Instanzen von
Actor und Use Case.
Meist beschreibt man ein Szenario für den Normalfall (Main Flow of Events) und mehrere
Szenarien für besondere Ausnahmesituationen (Exceptional Flow of Events).
Jede Sequenz stellt einen der möglichen Kontrolflüsse dar.
Szenarios bilden die Instanzen eines Use Case.
Die graphische Darstellung der Szenarien erfolgt typischerweise mit einem Sequenzdiagram
(vgl. weiter unten).
Ein Use Case kann auch durch ein Activity Diagram oder ein Statechart Diagram (vgl. weiter
unten) beschrieben werden.
Use Cases werden in der Sprache und Teminologie des Kunden beschrieben.
Die Funktionalität wird so beschrieben wie sie der Nutzer wahrnimmt. Insbesondere wird
nicht beschrieben, wie das System die Funktionalität erfüllt.
Eine nützliche Methode zum Beschreiben und Durchspielen der Use Cases besteht im
Rollenspiel. Eine Person spielt die Rolle des Actors, eine andere die Rolle des Systems.
2.3.2.3.6.3
Beziehungen zwischen Use Cases
Generalization Relationship
Das Kind kann überall dort verwendet werden, wo der Vater erscheint.
Z.B. kann der Use Case „Identifizierung eines Kunden“ durch den Use Case „Scannen der
Netzhaut“ oder den Use Case „Pruefen des Passwortes“ spezialisiert werden.
Include Relationship
Wenn mehrere Use Cases dieselben Szenarien beinhalten, können diese in einem
gemeinsamen Use Case modelliert werden.
Z.B. beinhalten die Use Cases „GeldWechseln“ und „KontostandAnzeigen“ das Verhalten
des Use Case „ValidiereKunden“.
Grouping
Use Cases können mittels Packages (vgl. weiter unten) gruppiert werden.
???Extends Relationship
Ein Use Case kann einen anderen erweitern. Dies bedeutet, dass er einige Szenarien
beinhaltet, die ggf. optional an bestimmten Stellen des erweiterten Use Cases in ähnlicher
Weise wie Unterprogramme durchlaufen werden.
2.3.2.3.7
Erweiterung der UML
Die Klassifizierung der Model Elemente erfolgt durch sogenannte Stereotypen.
Das Strichmännchen ist das Standard Icon für den Stereotyp <<Actor>>.
Die Stereotypen können in der Regel durch ihr spezifisches Icon oder als Rechteck mit
Label <<Stereotyp>> dargestellt werden.
Das Vokabular der UML kann erweitert werden.
Ein neuer selbst definierter Stereotype definiert eine neue Art von Model Element. Die UML
ermöglicht damit, fehlende Model Elemente zu definieren.
Z.B. könnte man im Rahmen eines Netzwerkdesigns Symbole für Router erfinden.
Einem Stereotype kann ein eigenes Icon zugewiesen werden, z.B. Skizze eines Routers,
eine spezielle Semantik und spezielle Eigenschaften. Gemeint sind hier die Eigenschaften
des Model Elementes, wie Name , Attribute und Operationen im Falle einer Klasse.
2.3.2.3.8
Wie findet man Actors und Use Cases
98
99
Man identifiziert zunächst einen Actor und überdenkt dann dessen Interaktion mit dem
System.
Wie findet man Actors ?
Man beantworte etwa folgende Fragen :
Wer wird die wesentliche Funktionalität des Systems nutzen ?
Wer wird das System warten und administrieren ?
Welche Hardwaregeräte benötigt das System ?
Mit welchen anderen Systemen wird das zu entwickelnde System interagieren ?
Wer hat ein Interesse an den Werten, die das System produziert ?
Wie findet man Use Cases ?
Für jeden Actor beantworte man etwa folgende Fragen :
Welche Funktionen erwartet der Actor von dem System ? Was muss er selbst dazu
beitragen ?
Muss der Actor irgend eine Art von Information lesen, erzeugen, zerstören, modifizieren
oder speichern ?
Muss der Actor über irgendwelche Ereignisse im System unterrichtet werden oder muss er
das System über etwas informieren ? Welche Funktionalität ist hierzu nötig ?
Kann die tägliche Arbeit des Actors vereinfacht oder effizienter gestaltet werden ?
Welchen Input/Output benötigt das System und von wo kommt dieser ?
Was sind die wesentlichen Probleme mit der aktuellen Implementation des Systems ?
2.3.2.4
Erstellen von Prototypen für User Interfaces
Die Anwender sollten verschiedene User Interfaces ausprobieren können.
2.3.2.4.1
Beispiel Bibliothek
99
100
2.3.2.4.2
Getränkeautomat
// Einfügen
2.3.2.5
Festlegen der Supplementary Requirements
2.3.2.5.1
Beispiel Bibliothek
// Einfügen
2.3.2.5.2
Beispiel Getränkeautomat
Supplementary Requirements
100
101
Die Ausgabe der Flasche nach Einwurf des Geldes soll in 90% der Fälle nach 2 Sek
erfolgen.
Die Downtime (Automat aus technischen Gründen nicht funktionsfähig) soll unter 1 Tag pro
Monat liegen.
80% der Personen sollen den Automaten innerhalb von 10 Sekunden bedienen können.
Vorraussetzung für die Auslieferung ist die Bereitstellung des Flaschengebers 2 Monate vor
dem Auslieferungstermin.
2.3.2.6
Festlegen der Supplementary Requirements
2.3.2.6.1
Beispiel Bibliothek
// Einfügen
2.3.2.6.2
Beispiel Getränkeautomat
// Einfügen
2.3.2.6.3
2.3.2.6.3.1
Fragenkatalog für Supplementary Requirements (aus Industrieprojekt für
Bildverarbeitung)
Auslieferungstermine/Zeitresourcen
Vom Kunden geforderter Zeitplan /Deadline
2.3.2.6.3.2
Kostengrenze/Budget/Geldresourcen
…
2.3.2.6.3.3
Nationale oder Internationale Regularien
Sicherheitsstandards USA /China / Germany
2.3.2.6.3.4
Conditions
Beispiel
Die BV Bibliothek steht am ... zur Verfügung
Hardware muß zur Verfügung stehen
Lieferanten müssen Schnittstellen Definitionen liefern
2.3.2.6.3.5
Produktsupport
Schulungen / Einführung beim Kunden – Wie lange? Online Support / Hotline
2.3.2.6.3.6
Juristische Anforderungen
Übereinstimmung mit technischen Standards
ISO9000, CMM-Level 3, ANSI standard
2.3.2.6.3.7
Technologische Randbedingungen für die Lösung (Solution Constrains)
101
102
z.B. Verwendung von NT, MFC für GUI, CanBus usw.
Look and Feel des User Interfaces
Sprache zwischen Deutsch und Englisch umschaltbar (multilingual)
2.3.2.6.3.8
Anforderungen bzgl. Datenvolumen
Objekt-Persistenz mittels Datenbanken oder Dateien,
Datenvolumen
2.3.2.6.3.9
Zeitanforderungen/Performance
Antwortzeit
Durchsatzraten
2.3.2.6.3.10
Anforderungen an die Präzision des Systems
Fehlertoleranz / Präzision
Beispiel: Die Fehlertoleranz für die Tischpositionierung soll unter 10% des Fahrbereichs
liegen.
2.3.2.6.3.11
Lastanforderungen
Belastung des Systems / Zuverlässigkeit
Z.B. Mehrbenutzerbetrieb mit der maximalen Benutzeranzahl 100.
2.3.2.6.3.12
Anforderungen an Fehlertoleranz, Robustheit des Systems
Verfügbarkeit des Systems
2.3.2.6.3.13
Anforderungen an die Benutzbarkeit
Ease of Use /GUI
Ease of Learning (Schulungsaufwand)
2.3.2.6.3.14
Anforderungen an die technische Sicherheit bzgl. Anwender und collaborierende
Hardware
Z.B. Der Roboter darf nie die Grenzen des Moduls überschreiten.
2.3.2.6.3.15
Anforderungen an die Datensicherheit
Zugriffsschutz / Passwörter (verschlüsselt)
2.3.2.6.3.16
Anforderungen an die Interoperabilität mit anderen Systemen/
Installationsumgebung (Implementation Environment)
Zusammenspiel mit Netzwerk, Hardware.
Referenzen zu den Schnittstellen der collaborierenden Systeme
102
103
2.3.2.6.3.17
Anforderungen an die Dokumentation (Benutzer+Wartung)
Technische Dokumentation
Benutzeranleitung
2.3.2.6.3.18
Anforderungen an die Wartbarkeit
…
2.3.2.6.3.19
Anforderungen an die Portabilität
z.B. Software soll leicht von C++ auf Java portierbar sein,
2.3.2.7
Erstellen einer Liste für mögliche Anforderungen
2.3.2.7.1
Beispiel Bibliothek
Alle Ideen werden aufgelistet und ggf. mit Attributen versehen.
2.3.2.7.2
Beispiel Getränkeautomat
List of Requirements
Requirement Identifier
Flasche kaufen
Flasche zurückgeben
Ermittle Status
Geld entnehmen
Flasche auffüllen
System testen
Priority StatusBenefitEffort Risk Stability
Remarks
3
2
1
3
2
1
3
2
1
3
3
2
2
3
1
1
1
2
2
3
Alle Ideen werden aufgelistet und ggf. mit Attributen versehen.
2.3.2.8
Erstellen eines Glossary
Das Glossary enthält wichtige Begriffsdefinitionen.
2.3.2.8.1
Beispiel Bibliothek
Title = identifiziert ein Buch durch dessen ISBN Nummer
item = identifiziert ein Exemplar eines Buches durch die Bibliotheks ID
2.3.2.8.2
Beispiel Getränkeautomat
Getränkemarke = Identifiziert alle Getränke eines Herstellers mit denselben Inhaltsstoffen
Lieferantennummer = 6stellige Nummer zur Identifikation des Lieferanten
103
104
2.3.2.9
Beispiel Bankautomat
ValidateUser
ACustomer
CheckPassword
AIndividualCustomer
RetinalScan
ACorporateCustomer
ChangeMoney
ShowBalance
Textuelle Beschreibung eines Use Case
Bankautomat
ValidateUser
Main flow of events :
Das System bitte den Kunden um seine PIN.
Der Kunde gibt seine PIN über die Tastatur ein.
Der Kunde bestätigt die Eingabe mittels Enter Button.
Das System überprüft die PIN.
Falls die PIN gültig ist, bestätigt das System die Gültigkeit.
Exceptional flow of events :
Der Kunde cancelt die Transaktion über den Cancel Button.
Das System ändert nicht den Account des Kunden.
Exceptional flow of events :
Der Kunde korrigiert seine PIN vor der Betätigung der Enter Taste.
Exceptional flow of events :
Der Kunde gibt eine ungültige PIN ein.
Daraufhin startet die Eingabe erneut. Nach dreimaliger Fehleingabe in Folge wird die
gesamte Transaktion abgebrochen.
2.3.3
Analyse & Design
2.3.3.1
Identifizierung von Klassen und Objekten
2.3.3.1.1
Beispiel Bibliothek
// Einfügen
2.3.3.1.2
Beispiel Getränkeautomat
104
105
Entity Objects:
 Geld
 Getränke
 Lieferanten
Boundary Objects:
 Zentraler Monitor
 Geld-Ein-/Ausgeber
 Temperatursensor
Control Objects
 Geldprüfer
 AusgabeManager
2.3.3.1.3
Grundlegende Probleme
Einige Zitate :
Booch :
„... Der Prozess objektorientierter Analyse und Designs kann nicht in einem Kochbuch
beschrieben werden ...“
Booch :
„Wenn wir klassifizieren, versuchen wir Dinge zu gruppieren, die eine gemeinsame Struktur
oder ein gemeinsames Verhalten aufweisen.
Es gibt keine einfachen Rezepte, keine "perfekte" Klassenstruktur und nicht die "richtige"
Objektmenge. Designentscheidungen stellen immer einen Kompromiss dar, der von vielen
gegeneinander abzuwägenden Faktoren abhängig ist.
Der Designer muss z.B. die Wahrscheinlichkeit für Änderungen einschätzen. Solche
Abschätzungen basieren auf Erfahrung und erfordern Wissen über den Einsatzbereich der
Applikation sowie ein Verständnis für Hardware- u. Softwaretechnologie.
Die besten Software-Designs sehen immer einfach aus.
Intelligente Klassifizierung entsteht am besten in einem inkrementellen und iterativen
Prozess.“
Dijkstra
"Die Herausforderung der Programmierung ist eine umfassende Aufgabe angewandter
Abstraktion und erfordert deshalb die Fähigkeiten eines Mathematikers, kombiniert mit der
Vorgehensweise eines kompetenten Ingenieurs"
Es gibt kein allgemeines Rezept, welches Intelligenz, Erfahrung und guten Geschmack
beim Programmieren ersetzt.
Grundprinzipien zur Bewältigung von Komplexität :
Es gibt nur einen grundlegenden Weg zur Bewältigung von Komplexität : Zerlegung (divide
et impera teile und herrsche) und Abstraktion.
Da wir meist nicht in der Lage sind, ein komplexes Objekt in seiner Ganzheit zu verstehen,
ignorieren wir einfach die unwichtigen Details und beschäftigen uns statt dessen mit dem
idealisierten Modell des Objekts. Wir reduzieren damit die Information auf ein für uns
erträgliches Maß. Abstraktion bedeutet Informationsreduktion.
Die im Folgenden beschriebenen Vorgehensweisen bieten dennoch eine gewisse
Hilfestellung.
2.3.3.1.4
Studieren des Problem Domains
Welche Klassen, Objekte enthält der Problembereich ?
105
106
Die Betrachtung der folgenden Stereotypen ist hilfreich :
2.3.3.1.4.1
Entity Class
Diese Klassen repräsentieren reale Objekte aus dem vom System verwalteten Bereich
(Business Objects)
Die Entity Klassen repräsentieren meist die logische Strukturierung der Informationen, die
das System verwaltet.
Beispiele
Reale Dinge : Bankkunde, Konto, Bücher, Autos, Flugzeuge, Personen, Technische Geräte
Ereignisse : Bestellung, Anfrage, Landung
Orte : Koordinaten, Adressen
Organisationen : Firma, Verein
2.3.3.1.4.2
Boundary Class
Ein Objekt der Boundary Class interagiert mit einem Actor ausserhalb des Systems.
Beispiele
Auswahlfenster, Kartenleser, Sensor für die Geldentnahme, Temperatursensor
Man ordnet jedem externen Gerät eine Boundary Class zu.
Man ordnet jedem externen Softwaresystem eine Boundary Class zu.
Man ordnet jedem menschlichen Actor ein Primary Window (Boundary Class) zu.
Man ordnet jedem Entity Object, mit dem der Actor interagiert, ein untergeordnetes Fenster
(Boundary Class) zu.
2.3.3.1.4.3
Control Class
Die Objekte der Control Class kontrollieren und koordinieren Interaktionen.
Beispiele
BestellungsManager, KreditPrüfer, UserInterfaceDirector
Ein solches Objekt kann z.B. einen einzigen Use Case überwachen und steuern oder eine
Menge untergeordneter Entity Objekte managen.
Eine solche Steuerung beinhaltet oft die Überwachung von Geschäftsregeln (Business
Logic), die keinem Entity Object zugeordnet werden können.
Man kann etwa jedem Use Case eine Control Class zuordnen. Dies ist allerdings unnötig,
wenn der Mensch wesentliche Teile der Steuerung durch Interaktion mit dem System selbst
übernimmt oder ein einziges Steuerungsobjekt für alle Use Cases ausreicht.
2.3.3.1.5
Studium der sprachlichen Beschreibung des Problems
Man studiert die sprachliche Beschreibung der Vision und der Use Cases.
Substantive sind Objektkandidaten.
Verben sind potentielle Elementfunktionen.
Häufig beschreiben Nomen o. Adjektive erforderliche Klassen
(z.B. speicherfähig, gleichzeitig).
106
107
Achtung : Verben können substantiviert werden. Sprache ist meist ungenau.
2.3.3.1.6
Studium existierender Software
Man studiert z.B. das aktuelle Computer System einer Bibliothek oder andere ähnliche
Systeme.
Die Verwendung von vorangegangener Arbeit als Inspiration ist eine akzeptable Technik für
Innovation.
2.3.3.2
Erstellen eines Class Diagrams
2.3.3.2.1
Beispiel Getränkeautomat
// Einfügen
2.3.3.2.2
Beispiel Bibliothek
// Einfügen
2.3.3.2.3
Ziel
// Einfügen
Klassen können in vielfältigen Beziehungen zueinander stehen.
Z.B. kann eine Klasse eine andere nutzen oder eine Spezialisierung einer anderen Klasse
sein.
2.3.3.2.4
UML Darstellung der Klassen
Klassen werden durch Rechtecke dargestellt.
Attribute und Operationen können optional ausgeblendet werden.
Manche Klassen, wie Prozesse, Threads, Hardware Devices, Applets repräsentieren immer
wieder verwendete und wichtige architektonische Abstraktionen. Daher werden sie von UML
mit eigenen Stereotypen und Icons unterstützt.
Im Falle von abstrakten Klassen oder abstrakten Operationen wird der Name kursiv (italic)
geschrieben.
Eine Class ist ein Spezialfall eines Classifiers.
Classifier unfassen Nodes, Use Cases, Components usw.
+ steht bei Attributen und Operationen für public und – für private und # für protected.
Class Scope Attribute (Statische Variable) werden unterstrichen.
Class Scope Operationen (Statische Operationen) werden unterstrichen.
Klassen Icons können auch verwendet werden, um primitive Typen wie int, char, bool usw.
darzustellen.
(UML unterscheidet zwischen Operationen und Methoden.
Eine Operation spezifiziert einen Service, der von jedem Objekt angefordert werden kann.
Eine Methode implementiert den Service. In einem Vererbungsgraph existieren mehrere
Methoden für dieselbe Operation. Die zum aktuellen Objekt gehörende Methode wird zur
Laufzeit ausgewählt.)
107
108
2.3.3.2.5
Identifizieren der Beziehungen zwischen Klassen
2.3.3.2.5.1
Association
2.3.3.2.5.1.1
Allgemein
Ein Link repräsentiert eine Verbindung zwischen zwei Objekten, über die eine Message
gesendet werden kann (Java Referenz oder C++ Pointer).
Eine Association zwischen Klassen beschreibt eine Menge von Links zwischen Objekten
dieser Klassen.
Ein Link ist eine Instanz einer Association.
Eine Association gibt eine semantische Abhängigkeit ansonsten nicht verwandter Klassen
an.
Eine Association ist normalerweise bidirektional. D.h. man kann in beide Richtungen
navigieren.
Ein Pfeil zeigt an, dass die Association nur in einer Richtung verwendet werden kann. Die
Navigierbarkeit wird damit für eine Richtung ausgeschaltet.
Eine Association hat einen oder zwei Namen für jede Richtung. Mit Hilfe dieser Namen kann
die Association „gelesen“ werden.
Ferner kann man die Rolle benennen, welche eine Klasse in der Beziehung zu einer
anderen übernimmt. Die Rollennamen sind Teil der Association und nicht Teil der Klassen.
Die Multiplicity wird am Ziel einer Association angemerkt und gibt die Anzahl der Links
zwischen jeder Instanz der Quellklasse und Instanzen der Zielklasse an.
Recursive Association
Eine Association einer Klasse zu sich selbst wird als Recursive Association bezeichnet.
Association Class
Wenn detailliertere Informationen über einen Link gespeichert werden müssen, bietet sich
die Verwendung einer Klasse an. Zu jedem Link der Association wird dann ein Objekt der
Association Class erzeugt.
2.3.3.2.5.1.2
Aggregation
Eine Aggregation ist ein Spezialfall der Assiciation und zeigt eine Ganz- /BestandteilBeziehung
Nicht immer sind die beiden Klassen an den Enden der Association gleichwertig oder auf
demselben Level. Manchmal möchte man ausdrücken, dass die eine Klasse das „Ganze“
repräsentiert und die andere Klasse einen „Teil“.
Schlüsselworte zur Identifizierung und Kennzeichnung einer Aggregation sind "part of" , "has
a", „consist of“, „contains“
Z.B. enthält jede Instanz der Klasse Auto eine Instanz der Klasse Motor.
Die Teil/Ganzes Hierarchie muss nicht unbedingt ein physikalisches Beinhalten bedeuten.
Z.B. hat ein Verein viele Mitglieder ohne diese physikalisch zu beinhalten.
Auch die Beziehung zwischen dem Vermögen eines Aktionärs u. seinen Anteilen kann als
Aggregation betrachtet werden. Das Vermögen enthält Aktien, Anleihen, usw.
Eine Aggregation beschreibt oft verschiedene Abstraktionslevel, wenn man das „Ganze“
ohne seine „Bestandteile“ betrachtet (Reduktion von Information).
108
109
Die Realisierung in C++ erfolgt durch Elementobjekt, Zeiger o. Referenz. In Java erfolgt die
Realisierung durch Referenz.
Auch das enthaltene Objekt darf einen Zeiger auf das umschließende Objekt enthalten
(zyklische Zeigerbeziehung).
2.3.3.2.5.1.3
Composition Aggregation
Eine Composition Aggregation drückt im strengen Sinne Eigentum aus. Die Teile leben und
sterben mit dem Ganzen.
Die Multiplicity auf der Seite des Ganzen muss 0 oder 1 sein.
Im Falle der Realisierung durch ein C++ Elementobjekt ist die Erzeugung und Vernichtung
automatisch an die umschließende Instanz gekoppelt.
Im Falle eines Zeigers oder einer Referenz sollten die Bestandteile mit dem
umschließenden Aggregat-Objekt erzeugt und vernichtet werden.
Die Verantwortlichkeit für die Existenz eines Bestandteils kann durchaus von einer Klasse
an eine andere übergeben werden (Shared part class).
???Shared Aggregation
Eine Shared Aggrgation ist ein Spezialfall einer Aggregation.
Personen können z.B. Mitglieder von mehreren Vereinen sein. Man erkennt dies an der
Multiplicity auf der Seite des Ganzen.
2.3.3.2.5.2
Realization
Eine Klasse realisiert etwa ein Interface, d.h. sie implementiert das im Interface spezifizierte
Verhalten.
2.3.3.2.5.3
Generalization
Das spezifische Element enthält im Vergleich zum generellen Element zusätzliche
Informationen.
Eine Instanz des spezifischen Elementes kann an Stelle einer Instanz des generellen
Elementes benutzt werden.
Die spezifische Klasse erbt Attribute, Operationen und Assoziationen.
(Vererbungs-Beziehung "is a")
Generalization kann für Classes, Use Cases und Packages genutzt werden.
???Klassen und Funktionen können durch den Property String {abstract} gekennzeichnet
werden.
???Polymorphismus kann nur durch Notes ausgedrückt werden.
???Constraint Generalizations
Overlapping and Disjoint Generalizations
Overlapping Generalization bedeutet, dass Mehrfache Vererbung mit gemeinsamer
Basisklasse erlaubt ist. Disjoint Generalization erlaubt keine gemeinsame Basisklasse.
???Complete Generalization
Mittels Constraint kann spezifiziert werden, dass keine weiteren Klassen abgeleitet werden
dürfen.
Eine Generalisierung drückt immer unterschiedliche Abstraktionslevel aus.
2.3.3.2.5.4
Dependency
109
110
Das Element ist von einem anderen abhängig.
Beispiele für Abhängigkeiten :
Operationen der Client Klasse haben eine Signatur mit Returnwert oder Parameter vom Typ
der Server Klasse.
Eine Klasse greift auf ein globales Objekt vom Typ einer anderen Klasse zu.
Eine Klasse ruft eine Class Scope Operation einer anderen Klasse.
Eine Elementfunktion einer Klasse definiert ein lokales Objekt einer anderen Klasse.
In allen Fällen muss der Code der abhängigen Klasse modifiziert werden, wenn sich das
Interface der verwendeten Klasse ändert.
Diese Beziehung kann auf Classes, Use Cases, Packages angewandt werden.
2.3.3.2.6
Templates
Ein Template ist eine Klasse, die nicht vollständig spezifiziert wurde. Die endgültige
Spezifikation erfolgt über Parameter. Man spricht auch von einer parametrisierten Klasse.
Eine parametrisierte Klasse kann ein Array sein. Die instanzierte Klasse kann dann ein
Array von Autos oder ein Array von Farben sein.
2.3.3.3
Weitere Beispiele
110
111
Buch S. 45 Bild 3.5
Buch S. 47 Bild 3.6 + Text
Text S. 47 unten
Buch S. 185 Bild 8.8
Buch S. 188 Bild 8.12 + Text S. 189
111
112
Unterschied zwischen dem Requirement Domain Model und dem Analyse Model nach Rational
:
Die Klassen des Domain Models repräsentieren reale Objekte
Die Klassen des Analyse Models repräsentieren Objekte des Computersystems
2.3.3.4
Erstellen der Use Case Realisierungen
2.3.3.4.1
Beispiel Getränkeautomat
// Einfügen
2.3.3.4.2
Beispiel Bibliothek
// Einfügen
2.3.3.4.3
Erstellen eines Use Case spezifischen Class Diagrams
Das Use Case spezifische Class Diagram zeigt alle an dem Use Case partizipierende
Klassen.
2.3.3.4.4
Erstellen eines Object Diagrams
2.3.3.4.4.1
Konzept
Ein Object Diagram ist eine Abwandlung des Klassendiagramms.
Im Unterschied zum Klassendiagramm zeigt es die Instanzen der Klassen und Instanzen
der Associations.
In diesem Sinne zeigt das Object Diagram einen Schnappschuss des Systems zu einem
bestimmten Zeitpunkt während der Ausführung.
Das Object Diagram nutzt mit zwei Ausnahmen dieselben Notationen wie das Class
Diagram. Die Namen der Objekte werden unterstrichen und es werden alle Instanzen einer
Beziehung dargestellt.
Durch eine geeignet gewählte Folge von Zeitpunkten kann man daher die
Statusänderungen eines Objektes darstellen. Der Status eines Objektes umfasst alle
Attribute und die aggregierten Teile.
Der Klassenname kann weggelassen werden und es können unbenannte Objekte einer
Klasse dargestellt werden.
2.3.3.4.4.2
Associations
Die Instanzen der Associations werden als Links bezeichnet. Ein Link spezifiziert ein Pfad
entlang dem eine Message gesendet werden kann.
Links können mit Stereotypen versehen werden.
UML erlaubt für fast alle Model Elemente Typen und Instanzen darzustellen.
Die Sichtbarkeit der Objekte kann näher beschrieben werden :
Association
Das korrespondierende Objekt ist sichtbar, weil eine Association bzgl. der Klassen existiert.
(Das sind die wichtigsten Links, da sie die Struktur des Systems widerspiegeln !)
Parameter
112
113
Das korrespondierende Objekt ist sichtbar, weil es ein Parameter ist.
Local
Das korrespondierende Objekt ist sichtbar, weil es eine lokale Variable ist.
Global
Das korrespondierende Objekt ist sichtbar, weil es eine globale Variable ist.
Aktive Objekte, das sind Objekte, welche die Wurzel eines Kontrollflusses (Thread)
repräsentieren, können durch einen fetten Rand markiert werden oder durch den Stereotyp
<<Thread>>.
2.3.3.4.5
Erstellen eines Collaboration Diagram
Das Collaboration Diagram zeigt den Nachrichtenaustausch im Kontext der Objekte und
deren Beziehungen.
Das Zusammenspiel ist eine grundlegende Designentscheidung !
Ein Objekt erhält eine Botschaft und sendet daraufhin eine Botschaft an sich selbst (Aufruf
eigener Elementfunktion) bzw. an ein kooperierendes Objekt (Aufruf Elementfunktion von
kooperierendem Objekt).
Das Wissen über das Zusammenspiel verschiedener Objekte ist in keiner der beteiligten
Klassen vollständig vorhanden. Der Mechanismus des Zusammenspiels ist ein Muster
(Pattern), dessen Wiederverwendbarkeit meist höher ist als die einzelner Klassen.
Innerhalb der Szenarien sollte man daher nach nicht trivialen Mechanismen suchen, die im
Rahmen der Systemarchitektur dokumentiert werden können (Beschreibung der
Mechanismen durch Sequence und Collaboration Diagrams).
Üblicherweise werden unterschiedliche Ablaufmöglichkeiten in verschiedenen Collaboration
Diagrams darsgestellt.
Jedem Link kann eine Message mit einer Nummer zugeordnet werden.
(Die Verschachtelung der Nachrichten kann durch eine Nummerierung analog zur
Kapitelnummerierung ausgedrückt werden. 1.2 ist z.B. die zweite Message innerhalb der
Behandlung der Message 1.)
Die Erzeugung bzw. Vernichtung eines Objektes kann mittels new/create bzw.
destroy/delete Messages oder Konstruktor-/Destruktoraufruf gekennzeichnet werden.
Kontrollflüse können unterschieden werden, indem man dem Funktionaufruf den Namen
des Threads voranstellt.
2.3.3.4.6
Erstellen eines Sequence Diagrams
Ein Sequence Diagram zeigt den Nachrichtenaustausch zwischen den Objekten zeitlich
auseinandergezogen entlang der vertikalen Lebenslinie der Objekte.
Ein Sequence Diagram betont die Zeit (Focus : Time) und die Reihenfolge, ein
Collaboration Diagram betont die Objekte und deren Beziehungen (Focus : Space).
Sie repräsentiert die Existenz des Objektes zu einem bestimmten Zeitpunkt.
Über die Lebenslinie lässt sich die Erzeugung oder Vernichtung eines Objektes darstellen.
Der Empfang einer Nachricht aktiviert das empfangende Objekt. Ein aktiviertes Objekt führt
entweder seinen eigenen Code aus oder wartet auf die Antwort eines anderen Objektes,
dem es eine Nachricht gesendet hat. Die Aktivität eines Objektes wird durch ein vertikales
dünnes Rechteck dargestellt.
Man unterscheidet prinzipiell zwischen der Generic Form und der Instance Form eines
Sequence Diagrams.
113
114
Die Instance Form beschreibt ein spezifisches Scenario im Detail. Sie dokumentiert eine
mögliche Interaktion. Es gibt keine Bedingungen, Verzweigungen oder Schleifen.
Die Generic Form beschreibt alle möglichen Alternativen in einem Scenario. Daher
existieren Verzweigungen, Bedingungen und Schleifen.
Iterationen lassen sich durch eine Bemerkung am Rand ausdrücken.
Man kann auch die Message mit einem Prefix versehen, welches die Iteration [i := 1..n] oder
die Bedingung [x > 0] für eine Verzweigung beschreibt. UML schreibt das Format nicht vor.
Üblicherweise stellt man die verschiedenen Ablaufmöglichkeiten jedoch nicht in einem
einzigen Sequenzdiagramm sondern in unterschiedlichen Sequenzdiagrammen dar.
Messages können eine Signatur mit Parameter haben.
Messages können Nummern haben.
Man unterscheidet folgende Message Types
Simple
For messages with a single thread of control, one object sends a message to a passive
object.
Synchronous
In synchronous messages, the operation proceeds only when the client sends a message to
the supplier and the supplier accepts the message. The client runs until it sends the
message; it then waits for the supplier to accept it. The client continues to wait until the
message is accepted.
Asynchronous
Asynchronous communication occurs when the client sends a message to the supplier for
processing and continues to execute its code without waiting for or relying on the supplier's
receipt of the message.
2.3.3.5
Spezifizierung der Klassen
2.3.3.5.1
Beispiel Getränkeautomat
Ausgabe Manager
Diese Klasse ist verantwortlich für die komplette Steuerung einer Flaschenausgabe. Sie
beinhaltet die für den Verkauf erforderliche Geschäftslogik. Insbesondere prüft sie, ob der
eingegebene Geldbetrag für die selektierte Flasche ausreichend ist. Die Verbuchung von
Geld und Flasche sowie die eigentliche Ausgabe delegiert sie an die entsprechenden
Klassen.
Die Klasse ist passiv.
Es gibt nur eine Instanz dieser Klasse.
Funktionsbeschreibungen:
...
...
...
2.3.3.5.2
Beispiel Bibliothek
// Einfügen
2.3.3.5.3
Identifizieren von Elementfunktionen und Attributen
114
115
Wie findet man Elementfunktionen und Attribute ?
Man betrachtet die Use Cases sowie die identifizierten Klassen und analysiert des
Zusammenspiel der Objekte mittels Sequence- und Collaboration Diagrams.
Aus den an die Instanzen der Klasse gesendeten Botschaften ergibt sich die öffentliche
Schnittstelle der Klasse.
Diese Funktionen werden durch Konstruktoren, Destruktoren und private Hilfsfunktionen
ergänzt.
Man notiert die zur Ausführung einer Funktion erforderlichen Informationen. Auf diese
Weise ergeben sich Klassenattribute und Funktionsparameter.
Man analysiert die möglichen Zustände der Bestandteile durch Betrachtung von State
Diagrams (vgl. weiter unten).
Diese führen zu States in Form von Attributwerten und zu Events und Actions in Form von
Operationen.
Im Rahmen des Designs sollten auch die Parameter der Operationen präzisiert werden.
Dabei ergeben sich wiederum neue Klassen zur „Verpackung“ von Informationen für einen
Transport.
Anmerkungen
Formalisierte Sprachen für den Entwurf
Analyse und Entwurf sind genau so fehleranfällig wie die Implementation. Sie sind ggf. sogar
fehleranfälliger, da sie weniger präzise (meist keine formale Sprache) spezifiziert und nicht wie
die Implementation durch Werkzeuge überprüft werden können (z.B. Compiler).
Durch eine strenge Formalisierung der Notation/Sprache kann hier eine Verbesserung erzielt
werden (z.B. Entwurfsformulierung mit DLCA Defined Language of Cooperating Automata).
Jedoch muß der Formalismus dem praktischen Problem angemessen sein und er darf den
mathematischen Background der beteiligten Designer und Programmierer nicht übersteigen
Manchmal ist es sinnvoll, logisch zusammengehörende und in vielen Objekten benutzte
Hilfsfunktionen in einer eigenen Service-Klasse (ohne Daten !) zusammenzufassen, z.B.
Funktionen zum Sortieren oder zur Umrechnung vom Dezimal- ins Hexadezimalsystem.
Man schreibe grundsätzlich Elementfunktionen für Konstruktion u. Destruktion (auch CopyKonstruktor !) und Zuweisungsoperatoren (operator=()).
Man schreibe ggf. Vergleichsoperatoren (operator==(), operator!=()).
Funktionen zum Auslesen- bzw. Setzen der Zustandsdaten eines Objektes (Selektor bzw.
Modifizierer).
Enthält ein Objekt viele Elementobjekte vom gleichen Typ, benötigt man einen Iterator, der es
erlaubt, auf alle Elementobjekte in einer wohdefinierten Reihenfolge zuzugreifen.
Für den Zugriff auf die Daten sind prinzipiell Funktionen zu benutzen.
Ist die Menge der über die Funktionsschnittstelle zu transportierenden Daten zu groß, sollte
trotzdem kein direkter Zugriff auf die Daten gewährt werden. Stattdessen sollte man Iteratoren
zur Verfügung stellen, die es ermöglichen, sich in der Menge zu bewegen. Ferner werden dann
Funktionen zum Einfügen, Löschen, Editieren benötigt.
Wenn man davon ausgeht, daß die Daten in einem nicht zugreifbaren Speicher (z.B.
Datenbank) verwaltet werden, können keine Zeiger auf die zu editierenden Daten übergeben
werden. Das Editieren ist nur möglich, indem man die Daten als Parameter übergibt oder vor
dem edit() Aufruf eine im eigenen Speicher zugreifbare Kopie editiert (Konzept von
CRecordset).
Die Kopie sollte Teil des Objektes sein, welches die Datenmenge repräsentiert.
Kommunikation zwischen Ansichten und Dokumentenobjekten sollte nicht über den
"Dienstweg" erfolgen (Objektbaum hoch und wieder runter). Man müßte sonst im obersten
Objekt (Dokument) sämtliche Nachrichten an die untergeordneten Objekte (z.B. Testfall)
weitergeben können. D.h. aber, daß alle Schnittstellenelementfunktionen der untern Objekte
auch Schnittstellenlementfunktionen des obersten Objektes sein müssen, da Nachrichten nur
über Elementfunktionen ausgetauscht werden können. Daher baut man besser Assoziationen
115
116
zwischen untergeordneten Objekten auf (z.B. könnte das Testfallansichtsobjekt einen Zeiger
auf das aktuelle Testfalldatenobjekt erhalten) und ermöglicht so den direkten
Nachrichtenaustausch.
Wir ergänzen private nicht in der Schnittstelle enthaltene Hilfsfunktionen, wie etwa Prüfroutinen
(index im erlaubten Bereich, auto in der gewünschten Farbe darstellbar usw.) , Funktionen zur
Vereinfachung der Notation (gebeDatumAus()).
Wir unterscheiden ggf zwischen allgemeinen Anwendern einer Klasse und erbenden
Anwendern durch spezifizieren von protected Anteilen an der öffentlichen Schnittstelle.
Wir legen die Argumenttypen der Schnittstellenfunktionen fest.
Dabei ist darauf zu achten, daß alle Operationen einer Klasse dasselbe Abstraktionsniveau
unterstützen und daß dieses zum Abstraktionsniveau der Klasse paßt.
Beispiele für typische Entwurfsentscheidungen :
Soll z.B. in einer Klasse cZylinder das Volumen gespeichert o. jeweils berechnet werden.
Sollen z.B. Flugplaninformationen bzgl. der Suche oder dem Löschen/Einfügen optimiert
werden.
Implementierung von Statusmaschinen durch Darstellung jedes Status durch ein Objekt einer
den Status repräsentierenden Klasse. Diese Objekte verhalten sich bedingt durch Polymorhie
unterschiedlich und lösen damit unterschiedliche Aktionen aus.
2.3.3.5.4
Beschreibung der Verantwortlichkeiten der Klassen
Die Bedeutung der im Diagramm dargestellten Klassen muss für den Leser deutlich
werden. Daher ist die Verantwortlichkeit jeder Klasse textuell durch einige Sätze zu
beschreiben.
Die grobe Verantwortlichkeit einer Klasse kann bereits durch den Namen deutlich werden,
wenn ein entsprechender Klassenbegriff in der Umgangssprache vorliegt.
Zusätzlich müssen charakteristische Eigenschaften verbal oder durch die Angabe von
einigen Attributen oder Elementfunktionen (Schnittstellenfunktionen, keine privaten
Hilfsfunktionen) beschrieben werden.
Ferner wird im Rahmen der Klassenspezifikation angegeben :
Basisklassen
Zustandsdiagramm (vgl. weiter hinten)
Anzahl der möglichen Instanzen
Multithreading Verhalten
Aktive Klasse
2.3.3.5.5
Erstellen der Beschreibungen der Schnittstellenfunktion
Die Funktionsbeschreibungen kann man z.B. untergliedern nach
Konstruktoren,
Destruktoren
Gruppen von public Functions
Safe&Load Functions
Die Funktionsbeschreibung einer Methode sollte enthalten
Funktionskopf mit Signatur
Return Value
Parameters
Exceptions
Specifier const, virtual, abstract
Multithreading Verhalten
Remarks
116
117
Im Rahmen der Properties für eine Operation können z.B. darüber hinaus Preconditions,
Postconditions oder Zustandsänderungen formuliert werden.
Wenn die Parameter der Elementfunktionen bereits spezifiziert sind, sollte man nach
Gemeinsamkeiten suchen. Ggf. kann man gemeinsame Klassen für gewisse Parameter
einführen.
Im Falle von Klassenkategorien stellen die Operationen die Services dar, die von der Kategorie
exportiert werden und die letztendlich von vielen Klassen der Kategorie realisiert werden.
Der Unterschied zwischen Verhalten (Funktion) und Eigenschaft (Attribut) ist oft nur schwer
erkennbar. Z.B. kann man die Tatsache, daß ein Lkw beladen werden kann durch ein Attribut
"LadungInTonnen" oder eine Elementfunktion laden() ausdrücken.
Beispiel für das Zusammenspiel verschiedener Objekte :
Im Rahmen eines Updates wird jede Ansicht aufgefordert, die Daten darzustellen.
Jede Ansicht erfragt die Daten des Dokumentes, bildet ein grafisches Modell und fordert
dieses auf sich zu zeichnen.
Document und View liegen auf demselben Level. Es handelt sich hier also nicht um Aufträge
an ein untergeordnetes Objekt.
2.3.3.5.6
Updaten der Beziehungen zwischen den Klassen
Einrichten von Vererbungsbeziehungen
Werden z.B. Operationen oder Attribute von mehreren gleichberechtigten Klassen benötigt,
sollten sie in einer ggf. abstrakten Oberklasse untergebracht werden.
An dieser Stelle findet ein Teil der Klassifizierung statt.
Einrichten der Associations
Die für den Botschaftsverkehr notwendigen Assoziationen müssen eingerichtet werden.
Diese Associations werden dann ggf. zu Aggregations oder Composite Aggregations
verfeinert.
Es werden Rollen, eventueller Einschränkungen und Kardinalitäten notiert.
Achtung : Vererbungsbeziehungen sind keine Botschaftswege (Basis- u. abgeleitete Klasse
stehen nicht für verschiedene Objekte).
2.3.3.5.7
Statechart Diagram
2.3.3.5.7.1
Beispiel Getränkeautomat
// Einfügen
2.3.3.5.7.2
Beispiel Bibliothek
// Einfügen
2.3.3.5.7.3
Konzept
Ein Statechart-Diagram zeigt alle möglichen Zustände eines Objektes und welche
Ereignisse zustandsänderungen bewirken.
2.3.3.5.7.4
States
117
118
Alle Objekte haben einen Status. Dieser ist bestimmt durch die Werte der Attribute
(einschließlich der Links zu anderen Objekten). Jede Änderung eines Attributwertes ist im
Prinzip eine Statusänderung.
State Diagrams werden nur für solche Klassen gezeichnet, die eine gewisse Anzahl
wohldefinierter Zustände besitzen, welche das Verhalten der Klasse beeinflussen.
Es gibt einen Initial State und ggf. mehrere Final States.
In UML besitzt jeder State einen Namen.
Optional können Attributwerte, Events und Actions angegeben werden.
Durch Schachtelung können Substates dargestellt werden. Der umfassende State wird als
Composite State bezeichnet.
Mittels History Indicator können Substates gespeichert werden.
2.3.3.5.7.5
Events
Events sind Trigger, die Zustandsübergänge aktivieren.
Der Empfang eines Signals, welches selbst ein Objekt ist oder der Aufruf einer Operation
durch ein anderes Objekt wird durch eine Event-Signature an einer Transition dargestellt.
Events können mit Parameter versehen werden (Event Signature).
Der Ablauf einer Zeitperiode wird durch einen Zeitausdruck an einer Transition
gekennzeichnet.
2.3.3.5.7.6
Transitions
Die Zustandsänderung wird als Transition bezeichnet.
Einer State Transition ist normalerweise ein Event zugeordnet. In diesem Falle wird die
Transition bein Auftreten des Events durchgeführt.
Wenn kein Event zugeordnet ist, wird die Transition ausgeführt, wenn die internen Actions
des Source States beendet sind.
Guard Condition
Eine Guard Condition ist ein Boolscher Ausdruck. Eine Transition, welche mit diesem
Ausdruck versehen ist, „feuert“, wenn der Ausdruck wahr ist.
Falls eine Transition mit Guard Condition und Event gekennzeichnet ist, muss der Ausdruck
wahr und der Event eingetreten sein.
Action Expression
Mittels Action Expressions können Transitions mit Operationen oder Attributzuweisungen
versehen werden.
Für Wertzuweisungen wird := verwendet.
Die Anweisungen sind durch / getrennt.
Eine Action ist eine ausführbare atomare Berechnung. Hierunter fällt z.B. der Aufruf einer
Funktion, die Erzeugung eines Objektes oder das Senden eines Signals an ein anderes
Objekt. Atomar bedeutet, dass die Berechnung nicht durch einen Event unterbrochen
werden kann.
Innerhalb eines States kann mittels der Standardevents Entry, Exit spezifiziert werden,
welche Actions jeweils beim Eintritt in den Zustand und beim Austritt aus dem Zustand
durchzuführen sind.
Die Spezifizierung einer Action mittels Entry erspart es, jede Transition, die in den
betreffenden Zustand führt, mit dieser Action zu kennzeichnen.
2.3.3.5.8
Activity Diagram
118
119
(Eriksson S. 23 Bild 2.8)
2.3.3.5.8.1
Beispiel Getränkeautomat
// Einfügen
2.3.3.5.8.2
Konzept
Eine Activity besteht aus einer Sequenz von Actions und ist durch einen Event
unterbrechbar.
Eine Activity kann z.B. eine längere Berechnung oder das Pollen einer Queue sein.
Ein Statchart Diagram betont die Zustände eines Objektes und zeigt die mit den
Zustandsübergängen und Zuständen verbundenen Aktivitäten nur nebenbei.
Ein Activity Diagram betont hingegen die Aktivitäten.
Man kann ein Activity Diagramm als Spezialfall eines Statchart Diagrams ansehen, in
welchem alle Aktivität gewissen States zugeordnet sind und alle Transitions durch die
Beendigung der Aktivität getriggert werden.
Activity Diagrams zeigen den Kontrollfluss von Aktivität zu Aktivität (Focus : Work).
2.3.3.5.9
Identifizierung aktiver Klassen
Falls z.B. einer der Actors hohe Performance Ansprüche hat, sollte er durch ein eigenes
aktives Objekt bedient werden.
Eine Verteilung auf mehrere Rechner erfordert zumindest ein aktives Objekt pro Rechner
und separate aktive Objekte für die Rechnerkommunikation.
In diesem Zusammenhang muss dann z.B. die Kommunikation der aktiven Objekte, die
Synchronisartion, die Vermeidung von Deadlocks und Starvation sowie die Erzeugung und
Terminierung bedacht werden (vgl. Multithreading Vorlesung SE2).
2.3.3.6
2.3.3.6.1
Erstellen eines Package Diagrams
Beispiel Getränkeautomat
119
120
GUI
Geldmanageme
nt
Automatenman
agement
Persistenzmanagement
Getränkemanag
ement
Utility
Devices
2.3.3.6.2
Beispiel Bibliothek
GUI
Büchermanage
ment
Ausleihmanage
ment
Lesermanagem
ent
Devices
2.3.3.6.3
Packages
Bei allen hinreichend großen Systemen können wir Klassengruppen erkennen, die in sich
selbst stark aber mit anderen Gruppen nur lose gekoppelt sind, die semantisch eng
zusammen gehören und meist nur gemeinsam geändert werden können.
Z.B. könnte man die Java Oberfläche durch eine Windows-Oberfläche ersetzen wollen.
Daher gruppieren wir z.B. alle Oberflächen-Klassen in einem eigenen Package.
Packages dienen dazu, das logische Modell eines Systemes aufzuteilen.
120
121
Ein Package ist ein Mechanismus zum Gruppieren.
Im Hinblick auf den begrenzten Überblick eines Menschen sollte die Anzahl der in einem
Package zusammenarbeitenden Klassen oder Packages nicht deutlich über 7 liegen.
Experimente des Psychologen Miller stützen dies (Begründung duch Leistungsvermögen
Kurzzeitgedächtnis).
2.3.3.6.4
Beziehungen zwischen Packages
Packages können in einer Dependency, Genaralization Beziehung zueinander stehen.
Die Abhängigkeit drückt bei Packages die Importbeziehung aus. Die Klassen der im
"Importbaum" obenliegenden Packages importieren Klassen der darunterliegenden
Packages.
Vererbung hat etwa dieselbe Bedeutung wie bei Klassen. Z.B. spezialisiert das Package
WindowsGUI das Package GUI. Das spezialisierte Package erbt damit die public und
protected Elemente des verallgemeinerten Packages. Wie bei Klassen kann das
spezialisierte Package dabei Elemente ersetzen und neue hinzufügen. Ein spezialisiertes
Package kann überall dort verwendet werden, wo ein allgemeines Package erwartet wird.
Ein Package besitzt seine Model Elemente. Ein Model Element kann nicht in mehr als
einem Package enthalten sein. Packages können jedoch Model Elements von anderen
Packages importieren.
2.3.3.6.5
Zugriffssteuerung
Der Zugriff auf den Inhalt eines Package kann durch private, protected, public und
implementation gekennzeichnet werden.
Im Falle von protected können nur Kinder des Packages das Model Element sehen.
Private Elemente sind außerhalb des Packages nicht sichtbar.
Damit hat man eine klare Trennung zwischen den exportierten public Klassen, die das
Interface des Package stellen, und den private Klassen, die die Services implementieren.
Exportierte Elemente sind nur in solchen Packages sichtbar, die das exportierende
Package explizit importieren.
Die Importbeziehung ist nicht transitiv.
Wenn ein Element in einem Package sichtbar ist, so ist es in allen eingeschachtelten
Packages sichtbar. Eingeschachtelte Packages sehen alles, was das umgebende Package
sieht.
Ein Package stellt einen Namensraum dar.
Verschiedene Arten von Model Elementen können denselben Namen haben, jedoch darf es
z.B. keine zwei Klassen mit demselben Namen geben.
2.3.3.7
2.3.3.7.1
Erstellen des Deployment Diagrams
Beispiel Getränkeautomat
121
122
<<Windows2000>>
Automaten Controller
<<CanBus>>
Geldein-/aus
geber
<<CanBus>>
Flaschenaus
geber
<<Internet>>
<<CanBus>>
<<Linux>>
Zentraler
Monitor
2.3.3.7.2
Flaschenein
sammler
Beispiel Bibliothek
BibliothekApplication
TransportSystemCo
ntroller
TransportSy
stem
EingabeAuto
mat
<<Linux>>
HTML-Client
<<Windows>>
SwingClient
AusgabeAut
omat
122
123
2.3.3.7.3
Konzept
Deployment Diagrams zeigen die physikalische Verteilung der Hard- und Software des
Systems zusammen mit deren Verbindungen.
Es wird erkennbar, welche Software Component (vgl. weiter unten) auf welchem Gerät
ausgeführt wird.
Nodes
Nodes sind physikalische Objekte wie Computer, Drucker, Kartenleser usw.
Sie repräsentieren die Hardware, auf welchen die Components ausgeführt werden.
Ein Node kann als Typ (Klasse) und als Instanz (Objekt) dargestellt werden. Die Klasse
beschreibt etwa die Charakteristik eines Processors und das Objekt repräsentiert einen
konkreten Processor dieses Typs. Die Eigenschaften (Prozessorgeschwindigkeit oder der
vorhandene Speicher) können im Rahmen der Klassenattribute oder der Properties
beschrieben werden.
Im Falle von Klassen, können auch Vererbungsbeziehungen dargestellt werden.
Im Falle einer Instanz wird der Name unterstrichen.
Communication Association
Nodes werden mittels Communication Association miteinander verbunden.
Mittels Stereotyp wird das Netzwerk oder das Protokoll gekennzeichnet.
Executable Components
Die Instanzen der ausgeführten Programme können in den Instanzen der Nodes aufgeführt
werden.
Objects
Instanzen von Klassen, also Objects, können Instanzen von Nodes zugeordnet werden. Z.B.
kann ein Passive Object innerhalb eines Active Objects und dieses innerhalb einer
Executable Component dargestellt werden.
2.3.3.8
Unterschied zwischen Analyse und Design
2.3.3.8.1
Analyse
Booch
„Unsere Erfahrung in der Entwicklung großer Systeme hat gezeigt, dass die zu Anfang
erfolgte Spezifizierung der Anforderungen niemals vollständig ist, oft nur ungenau und
immer im Widerspruch zu sich selbst steht.“
OOA nach Booch :
„Die objektorientierte Analyse ist eine Analysemethode, welche die Anforderungen aus der
Perspektive der Klassen und Objekte, die sich im Vokabular des Problembereichs finden,
betrachtet.“
Die Analyse beschreibt, "was" das System tut, nicht "wie" es das tut.
Hierzu gehört die Identifikation von wichtigen Klassen und Objekten des Problembereichs
und die Beschreibung der UseCases aus der Sicht der kollaborierenden Objekte.
Das bisher monolithische System splittet auf in Analysis Level Objekte.
Damit wird auch das Problem eingegrenzt, indem festgelegt wird, was von Interesse ist und
was nicht.
Das Analysis Model ist noch frei von technischen Details. Es vermittelt daher eine sehr
abstrakte Sichtweise der Zerlegung des Systems.
Das Analysis Model liefert ein genaues Verständnis der Anforderungen.
123
124
2.3.3.8.2
Design
Das Design Model beschreibt, "wie" das System die gestellten Anforderungen erfüllt.
In Rahmen des Design treffen Architekten die grundlegenden architektonischen und
strategischen Entscheidungen.
Sie eignen sich dabei ein Verständnis der verwendeten Technologien wie Betriebssystem,
GUI, Datenbank usw. an.
Sie zerlegen das System in kleinere von ggf. unterschiedlichen Teams bearbeitbare Teile
und definieren die Interfaces.
Die UML Definition der Architektur
Architecture is the organizational structure of a system. An architecture can be recursively
decomposed into parts that interact through interfaces, relationships that connect parts, and
constraints for assembling parts.
Das Analysis Model liefert eine gewisse dem Problembereich angepasste Grundstruktur.
Diese meist an den realen Objekten orientierte Grundstruktur sollte man im Design Model
nach Möglichkeit beibehalten.
2.3.3.9
Weitere Beispiele
: School
software
Engineering :
: Student
peterEdelmann
: Instructor
Course( )
hire( )
enrol( )
visit( )
listenTo( )
124
125
: School
G
3: enrol( )
4: visit( )
:
Student
1: Course( )
softwareEngineering :
Course
P
F
2: hire( )
peterEdelmann :
Instructor
5: listenTo( )
125
126
Activ e
cardInserted
Idle
Validating
Selecting
maintain
Processing
Cancel
Maintenance
TestingDev ices
Printing
Self Diagnosis
Idle
tooHot( desiredTemp )
atTemp / shutDown
tooCold( desiredTemp )[ time > 7.00 ]
atTemp / shutDown
Heating
Cooling
Activ ating
Activ e
126
127
kernel32.dll
<<DOCUMENT>>
animator.hlp
<<DLL>>
dlog.dll
<<EXE>>
animator.
exe
<<FILE>>
animator.i
ni
<<DLL>>
render.dll
<<TABLE>>
shapes.tbl
render.cpp
render.h
<<Console>>
salesA
<<Net>>
Internet
server
<<satelliteLink>>
<<Console>>
<<Ethernet>>
salesB
Printer
Console
127
128
2.3.4
Implementation
2.3.4.1
Erstellen des Component Diagrams
2.3.4.1.1
Beispiel Getränkeautomat
CKunden
Ansicht
CGetränke
Manager
2.3.4.1.2
CAusgabe
Manager
CGeldMan
ager
Beispiel Bibliothek
// Einfügen
2.3.4.1.3
Konzept
Ein Component Diagram stellt die physikalische Realisierung von logischen
Modellelementen und ihre Abhängigkeiten dar.
Es zeigt damit die physikalische Struktur des Codes.
Eine Dependency Relation zwischen Components drückt aus, dass eine Component die
andere für ihre vollständige Definition benötigt. In einer compilierten Sprache erfordert die
Modifikation der einen Component die Recompilierung der anderen Component.
Im Falle einer Executable Component kann die Dependency Relation verwendet werden,
um auszudrücken, welche DLL das Programm benötigt.
Components sind Typen. Nur Executable Components können Instanzen haben. Eine
solche Instanz wird auf einem Processor ausgeführt.
Mittels Stereotyp kennzeichnet man die Components genauer, z.B. :
<<file>> Source Code File
<<page>> Web Seite
<<document>> Dokument (nicht compilierbar)
Binary Component
Object Code als Resultat des Compilierens.
Executable Component
Ein ausführbares File als Resultat des Linkens.
Sie repräsentiert die ausführbare Einheit, die auf einem Processor läuft.
<<executable>> Ausführbares Programm
<<library>> Static or dynamic object library
<<table >> Datenbank
128
129
2.3.4.2
Erstellen des Integration Build Plan
2.3.4.2.1
Beispiel Getraenkeautomat
GUI
(from Automat)
AutomatenMana
gement
(from Automat)
VollFlaschenMan
agement
(from Automat)
Persistence
(from Automat)
LeerFlaschenMa
nagement
(from Automat)
GeldManagemen
t
(from Automat)
Utility
Devices
(from Automat)
2.3.4.2.2
Bottom Up Integration
Man identifiziert diejenigen Subsystems, die an den zu implementierenden Use Cases
partizipieren.
Man definiert “Build Sets”, das sind Subsystems, die aus Integrationssicht
zusammengehören.
129
130
Man definiert eine Serie von Builds (typischerweise Bottom Up)
// Im Beispiel Einfügen
130
131
2.3.4.3
Implementieren der Components
2.3.4.3.1
Ziel
Es werden alle Components produziert, die für ein ausführbares System erforderlich sind.
Insbesondere werden die Design Klassen in File Components mit Source Code und
schließlich in Binärfiles bzw. Executables umgesetzt.
Es werden zu den Packages korrespondierende Verzeichnisse angelegt.
2.3.4.3.2
Programmierregeln
2.3.4.3.2.1
Java Code Conventions (Selbststudium)
Vgl. Java-Codeconvention.pdf
Java Code Conventions
1 - Introduction
1.1 Why Have Code Conventions
Code conventions are important to programmers for a number of reasons:
• 80% of the lifetime cost of a piece of software goes to maintenance.
• Hardly any software is maintained for its whole life by the original author.
• Code conventions improve the readability of the software, allowing engineers to
understand new code more quickly and thoroughly.
• If you ship your source code as a product, you need to make sure it is as well packaged
and clean as any other product you create.
For the conventions to work, every person writing software must conform to the code
conventions. Everyone.
1.2 Acknowledgments
This document reflects the Java language coding standards presented in the Java
Language
Specification, from Sun Microsystems, Inc. Major contributions are from Peter King, Patrick
Naughton, Mike DeMoney, Jonni Kanerva, Kathy Walrath, and Scott Hommel.
This document is maintained by Scott Hommel. Comments should be sent to
[email protected]
2 - File Names
This section lists commonly used file suffixes and names.
2.1 File Suffixes
Java Software uses the following file suffixes:
File
Type
Java
source
Java
bytecode
File Name
GNUmakefile
software.
Suffixes
.java
Java
bytecode
2.2 Common File Names
Frequently used file names include:
Use
The preferred name for makefiles. We use gnumake to build our
131
132
README
particular directory.
The preferred name for the file that summarizes the contents of a
3 - File Organization
A file consists of sections that should be separated by blank lines and an optional comment
identifying each section.
Files longer than 2000 lines are cumbersome and should be avoided.
For an example of a Java program properly formatted, see “Java Source File Example” on
page
18.
3.1 Java Source Files
Each Java source file contains a single public class or interface. When private classes and
interfaces are associated with a public class, you can put them in the same source file as
the
public class. The public class should be the first class or interface in the file.
Java source files have the following ordering:
• Beginning comments (see “Beginning Comments” on page 2)
• Package and Import statements
• Class and interface declarations (see “Class and Interface Declarations” on page 3)
3.1.3 Class and Interface Declarations
All source files should begin with a c-style comment that lists the class name, version
information, date, and copyright notice:
/*
* Classname
*
* Version information
*
* Date
*
* Copyright notice
*/
3.1.2 Package and Import Statements
The first non-comment line of most Java source files is a package statement. After that,
import statements can follow. For example:
package java.awt;
import java.awt.peer.CanvasPeer;
3.1.3 Class and Interface Declarations
The following table describes the parts of a class or interface declaration, in the order that
they
should appear. See “Java Source File Example” on page 18 for an example that includes
comments.
Part of Class/Interface
Declaration
Class/interface
documentation
comment (/**...*/)
class or interface
statement
Class/interface
implementation
comment (/*...*/),
if necessary
Notes
See “Documentation Comments” on page 8 for
information on what should be in this comment.
This comment should contain any class-wide or
interface-wide information that wasn’t appropri-ate
for the class/interface documentation com-ment.
132
133
Class (static)
variables
Instance variables.
First the public class variables, then the protected, then package level (no access modifier),
and then the private.
First public, then protected, then package
level (no access modifier), and then private
Constructors
Methods
These methods should be grouped by functional-ity
rather than by scope or accessibility. For example, a
private class method can be in between two public
instance methods. The goal is to make reading and
understanding the code eas-ier.
4 - Indentation
Four spaces should be used as the unit of indentation. The exact construction of the
indentation
(spaces vs. tabs) is unspecified. Tabs must be set exactly every 8 spaces (not 4).
4.1 Line Length
Avoid lines longer than 80 characters, since they’re not handled well by many terminals and
tools.
Note: Examples for use in documentation should have a shorter line length—generally no
more than 70 characters.
4.2 Wrapping Lines
When an expression will not fit on a single line, break it according to these general
principles:
Break after a comma.
Break before an operator.
Prefer higher-level breaks to lower-level breaks.
Align the new line with the beginning of the expression at the same level on the previous
line.
If the above rules lead to confusing code or to code that’s squished up against the right
margin, just indent 8 spaces instead.
Here are some examples of breaking method calls:
someMethod(longExpression1, longExpression2, longExpression3,longExpression4,
longExpression5);
var = someMethod1(longExpression1,
someMethod2(longExpression2,
longExpression3));
Following are two examples of breaking an arithmetic expression. The first is preferred,
since
the break occurs outside the parenthesized expression, which is at a higher level.
longName1 = longName2 * (longName3 + longName4 - longName5)
+ 4 * longname6; // PREFER
longName1 = longName2 * (longName3 + longName4
- longName5) + 4 * longname6; // AVOID
Following are two examples of indenting method declarations. The first is the conventional
case. The second would shift the second and third lines to the far right if it used
conventional
indentation, so instead it indents only 8 spaces.
//CONVENTIONAL INDENTATION
someMethod(int anArg, Object anotherArg, String yetAnotherArg,
133
134
Object andStillAnother) {
...
}
//INDENT 8 SPACES TO AVOID VERY DEEP INDENTS
private static synchronized horkingLongMethodName(int anArg,
Object anotherArg, String yetAnotherArg,
Object andStillAnother) {
...
}
Line wrapping for if statements should generally use the 8-space rule, since conventional
(4
space) indentation makes seeing the body difficult. For example:
//DON’T USE THIS INDENTATION
if ((condition1 && condition2)
|| (condition3 && condition4)
||!(condition5 && condition6)) { //BAD WRAPS
doSomethingAboutIt(); //MAKE THIS LINE EASY TO MISS
}
//USE THIS INDENTATION INSTEAD
if ((condition1 && condition2)
|| (condition3 && condition4)
||!(condition5 && condition6)) {
doSomethingAboutIt();
}
//OR USE THIS
if ((condition1 && condition2) || (condition3 && condition4)
||!(condition5 && condition6)) {
doSomethingAboutIt();
}
Here are three acceptable ways to format ternary expressions:
alpha = (aLongBooleanExpression) ? beta : gamma;
alpha = (aLongBooleanExpression) ? beta
: gamma;
alpha = (aLongBooleanExpression)
? beta
: gamma;
5 - Comments
Java programs can have two kinds of comments: implementation comments and
documentation comments. Implementation comments are those found in C++, which are
delimited by /*...*/, and //. Documentation comments (known as “doc comments”) are
Java-only, and are delimited by /**...*/. Doc comments can be extracted to HTML files
using the javadoc tool.
Implementation comments are means for commenting out code or for comments about the
particular implementation. Doc comments are meant to describe the specification of the
code,
from an implementation-free perspective to be read by developers who might not
necessarily
have the source code at hand.
Comments should be used to give overviews of code and provide additional information that
is
not readily available in the code itself. Comments should contain only information that is
134
135
relevant to reading and understanding the program. For example, information about how the
corresponding package is built or in what directory it resides should not be included as a
comment.
Discussion of nontrivial or nonobvious design decisions is appropriate, but avoid duplicating
information that is present in (and clear from) the code. It is too easy for redundant
comments
to get out of date. In general, avoid any comments that are likely to get out of date as the
code
evolves.
Note: The frequency of comments sometimes reflects poor quality of code. When you feel
compelled to add a comment, consider rewriting the code to make it clearer.
Comments should not be enclosed in large boxes drawn with asterisks or other characters.
Comments should never include special characters such as form-feed and backspace.
5.1 Implementation Comment Formats
Programs can have four styles of implementation comments: block, single-line, trailing and
end-of-line.
5.1.1 Block Comments
Block comments are used to provide descriptions of files, methods, data structures and
algorithms. Block comments may be used at the beginning of each file and before each
method. They can also be used in other places, such as within methods. Block comments
inside
a function or method should be indented to the same level as the code they describe.
A block comment should be preceded by a blank line to set it apart from the rest of the
code.
/*
* Here is a block comment.
*/
Block comments can start with /*-, which is recognized by indent(1) as the beginning of a
block comment that should not be reformatted. Example:
/** Here is a block comment with some very special
* formatting that I want indent(1) to ignore.
*
* one
* two
* three
*/
Note: If you don’t use indent(1), you don’t have to use /*- in your code or make any other
concessions to the possibility that someone else might run indent(1) on your code.
See also “Documentation Comments” on page 8.
5.1.2 Single-Line Comments
Short comments can appear on a single line indented to the level of the code that follows. If
a
comment can’t be written in a single line, it should follow the block comment format (see
section 5.1.1). A single-line comment should be preceded by a blank line. Here’s an
example
of a single-line comment in Java code:
if (condition) {
/* Handle the condition. */
...
}
5.1.3 Trailing Comments
Very short comments can appear on the same line as the code they describe, but should be
shifted far enough to separate them from the statements. If more than one short comment
appears in a chunk of code, they should all be indented to the same tab setting.
Here’s an example of a trailing comment in Java code:
135
136
if (a == 2) {
return TRUE; /* special case */
} else {
return isPrime(a); /* works only for odd a */
}
5.1.4 End-Of-Line Comments
The // comment delimiter can comment out a complete line or only a partial line. It
shouldn’t
be used on consecutive multiple lines for text comments; however, it can be used in
consecutive multiple lines for commenting out sections of code. Examples of all three styles
follow:
if (foo > 1) {
// Do a double-flip.
...
}
else{
return false; // Explain why here.
}
//if (bar > 1) {
//
// // Do a triple-flip.
// ...
//}
//else{
// return false;
//}
5.2 Documentation Comments
Note: See “Java Source File Example” on page 18 for examples of the comment formats
described here.
For further details, see “How to Write Doc Comments for Javadoc” which includes
information on the doc comment tags (@return, @param, @see):
http://java.sun.com/products/jdk/javadoc/writingdoccomments.html
For further details about doc comments and javadoc, see the javadoc home page at:
http://java.sun.com/products/jdk/javadoc/
Doc comments describe Java classes, interfaces, constructors, methods, and fields. Each
doc
comment is set inside the comment delimiters /**...*/, with one comment per class,
interface, or member. This comment should appear just before the declaration:
/**
* The Example class provides ...
*/
public class Example { ...
Notice that top-level classes and interfaces are not indented, while their members are. The
first
line of doc comment (/**) for classes and interfaces is not indented; subsequent doc
comment
lines each have 1 space of indentation (to vertically align the asterisks). Members, including
constructors, have 4 spaces for the first doc comment line and 5 spaces thereafter.
If you need to give information about a class, interface, variable, or method that isn’t
appropriate for documentation, use an implementation block comment (see section 5.1.1) or
single-line (see section 5.1.2) comment immediately after the declaration. For example,
details
about the implementation of a class should go in in such an implementation block comment
following the class statement, not in the class doc comment.
Doc comments should not be positioned inside a method or constructor definition block,
136
137
because Java associates documentation comments with the first declaration after the
comment.
6 – Declarations
6.1 Number Per Line
One declaration per line is recommended since it encourages commenting. In other words,
int level; // indentation level
int size; // size of table
is preferred over
int level, size;
Do not put different types on the same line. Example:
int foo, fooarray[]; //WRONG!
Note: The examples above use one space between the type and the identifier. Another
acceptable alternative is to use tabs, e.g.:
int level; // indentation level
int size; // size of table
Object currentEntry; // currently selected table entry
6.2 Initialization
Try to initialize local variables where they’re declared. The only reason not to initialize a
variable where it’s declared is if the initial value depends on some computation occurring
first.
6.3 Placement
Put declarations only at the beginning of blocks. (A block is any code surrounded by curly
braces “{” and “}”.) Don’t wait to declare variables until their first use; it can confuse the
unwary programmer and hamper code portability within the scope.
void myMethod() {
int int1 = 0; // beginning of method block
if (condition) {
int int2 = 0; // beginning of "if" block
...
}
}
The one exception to the rule is indexes of for loops, which in Java can be declared in the
for
statement:
for (int i = 0; i < maxLoops; i++) { ... }
Avoid local declarations that hide declarations at higher levels. For example, do not declare
the
same variable name in an inner block:
int count;
...
myMethod() {
if (condition) {
int count; // AVOID!
...
}
...
}
137
138
6.4 Class and Interface Declarations
When coding Java classes and interfaces, the following formatting rules should be followed:
• No space between a method name and the parenthesis “(“ starting its parameter list
• Open brace “{” appears at the end of the same line as the declaration statement
• Closing brace “}” starts a line by itself indented to match its corresponding opening
statement, except when it is a null statement the “}” should appear immediately after the
“{“
class Sample extends Object {
int ivar1;
int ivar2;
Sample(int i, int j) {
ivar1 = i;
ivar2 = j;
}
int emptyMethod() {}
...
}
• Methods are separated by a blank line
7 - Statements
7.1 Simple Statements
Each line should contain at most one statement. Example:
argv++; // Correct
argc++; // Correct
argv++; argc--; // AVOID!
7.2 Compound Statements
Compound statements are statements that contain lists of statements enclosed in braces
“{ statements }”. See the following sections for examples.
The enclosed statements should be indented one more level than the compound statement.
The opening brace should be at the end of the line that begins the compound statement; the
closing brace should begin a line and be indented to the beginning of the compound
tatement.
Braces are used around all statements, even single statements, when they are part of a
control structure, such as a if-else or for statement. This makes it easier to add
statements without accidentally introducing bugs due to forgetting to add braces.
7.3 return Statements
A return statement with a value should not use parentheses unless they make the return
value
more obvious in some way. Example:
return;
return myDisk.size();
return (size ? size : defaultSize);
7.4 if, if-else, if else-if else Statements
The if-else class of statements should have the following form:
if ( condition) {
statements;
}
if ( condition) {
statements;
} else {
statements;
}
if ( condition) {
statements;
} else if ( condition) {
statements;
} else {
138
139
statements;
}
Note: if statements always use braces {}. Avoid the following error-prone form:
if ( condition) //AVOID! THIS OMITS THE BRACES {}!
statement;
7.5 for Statements
A for statement should have the following form:
for ( initialization; condition; update) {
statements;
}
An empty for statement (one in which all the work is done in the initialization, condition,
and
update clauses) should have the following form:
for ( initialization; condition; update);
When using the comma operator in the initialization or update clause of a for statement,
avoid
the complexity of using more than three variables. If needed, use separate statements
before
the for loop (for the initialization clause) or at the end of the loop (for the update clause).
7.6 while Statements
A while statement should have the following form:
while ( condition) {
statements;
}
An empty while statement should have the following form:
while ( condition);
7.7 do-while Statements
A do-while statement should have the following form:
do {
statements;
} while ( condition);
7.8 switch Statements
A switch statement should have the following form:
switch ( condition) {
case ABC:
statements;
/* falls through */
case DEF:
statements;
break;
case XYZ:
statements;
break;
default:
statements;
break;
}
Every time a case falls through (doesn’t include a break statement), add a comment
where the
break statement would normally be. This is shown in the preceding code example with the
/* falls through */ comment.
Every switch statement should include a default case. The break in the default case is
139
140
redundant, but it prevents a fall-through error if later another case is added.
7.9 try-catch Statements
A try-catch statement should have the following format:
try {
statements;
} catch (ExceptionClass e) {
statements;
}
A try-catch statement may also be followed by finally,
which executes regardless of whether or not the try block has completed successfully.
try {
statements;
} catch (ExceptionClass e) {
statements;
} finally {
statements;
}
8 - White Space
8.1 Blank Lines
Blank lines improve readability by setting off sections of code that are logically related.
Two blank lines should always be used in the following circumstances:
Between sections of a source file
Between class and interface definitions
One blank line should always be used in the following circumstances:
Between methods
Between the local variables in a method and its first statement
Before a block (see section 5.1.1) or single-line (see section 5.1.2) comment
Between logical sections inside a method to improve readability
8.2 Blank Spaces
Blank spaces should be used in the following circumstances:
A keyword followed by a parenthesis should be separated by a space. Example:
while (true) {
...
}
Note that a blank space should not be used between a method name and its opening
parenthesis. This helps to distinguish keywords from method calls.
A blank space should appear after commas in argument lists.
All binary operators except . should be separated from their operands by spaces. Blank
spaces should never separate unary operators such as unary minus, increment (“++”), and
decrement (“--”) from their operands. Example:
a += c + d;
a = (a + b) / (c * d);
while (d++ = s++) {
n++;
}
prints("size is " + foo + "\n");
The expressions in a for statement should be separated by blank spaces. Example:
for (expr1; expr2; expr3)
Casts should be followed by a blank space. Examples:
myMethod((byte) aNum, (Object) x);
myMethod((int) (cp + 5), ((int) (i + 3))
+ 1);
9 - Naming Conventions
Naming conventions make programs more understandable by making them easier to read.
140
141
They can also give information about the function of the identifier—for example, whether it’s
a
constant, package, or class—which can be helpful in understanding the code.
Identifier
Type
Packages
Classes
Interfaces
Methods
Rules for Naming
Examples
The prefix of a unique
package name is
always written in all-lowercase
ASCII letters
and should be one of the toplevel domain
names, currently com, edu,
gov, mil, net, org,
com.sun.eng
com.apple.quicktime.v2
edu.cmu.cs.bovik.cheese
class Raster;
141
142
Identifier Type
Constants
Rules for Naming
Except for variables,
all instance, class,
and class constants
are in mixed case
with a lowercase
first letter. Internal
words start with
capital letters.
Variable names
should not start with
underscore _ or
dollar sign $
characters, even
though both are
allowed.
Variable names
should be short yet
meaningful. The
choice of a variable
name should be
mnemonic— that is,
designed to indicate
to the casual
observer the intent of
its use. Onecharacter variable
names should be
avoided except for
temporary
“throwaway”
variables. Common
names for temporary
variables are i, j, k,
m, and n for integers;
c, d, and e for
characters.
The names of
variables declared
class constants
Variables
The names of
variables declared
class constants and
of ANSI constants
should be all
uppercase with words
separated by
underscores
(“_”). (ANSI constants
should be
avoided, for ease of
debugging.)
int i;
char c;
float myWidth;
static final int
MIN_WIDTH = 4;
static final int
MAX_WIDTH = 999;
static final int
GET_THE_CPU = 1;
142
143
10 - Programming Practices
10.1 Providing Access to Instance and Class Variables
Don’t make any instance or class variable public without good reason. Often, instance
variables don’t need to be explicitly set or gotten—often that happens as a side effect of
method calls.
One example of appropriate public instance variables is the case where the class is
essentially a
data structure, with no behavior. In other words, if you would have used a struct instead
of a
class (if Java supported struct), then it’s appropriate to make the class’s instance
variables
public.
10.2 Referring to Class Variables and Methods
Avoid using an object to access a class (static) variable or method. Use a class name
instead.
For example:
classMethod(); //OK
AClass.classMethod(); //OK
anObject.classMethod(); //AVOID!
10.3 Constants
Numerical constants (literals) should not be coded directly, except for -1, 0, and 1, which
can
appear in a for loop as counter values.
10.4 Variable Assignments
Avoid assigning several variables to the same value in a single statement. It is hard to read.
Example:
fooBar.fChar = barFoo.lchar = 'c'; // AVOID!
Do not use the assignment operator in a place where it can be easily confused with the
equality
operator. Example:
if (c++ = d++) { // AVOID! (Java disallows)
...
}
should be written as
if ((c++ = d++) != 0) {
...
}
Do not use embedded assignments in an attempt to improve run-time performance. This is
the
job of the compiler. Example:
d = (a = b + c) + r; // AVOID!
should be written as
a = b + c;
d = a + r;
10.5 Miscellaneous Practices
10.5.1 Parentheses
It is generally a good idea to use parentheses liberally in expressions involving mixed
operators
to avoid operator precedence problems. Even if the operator precedence seems clear to
you, it
might not be to others—you shouldn’t assume that other programmers know precedence as
well as you do.
143
144
if (a == b && c == d) // AVOID!
if ((a == b) && (c == d)) // USE
10.5.2 Returning Values
Try to make the structure of your program match the intent. Example:
if ( booleanExpression) {
return true;
} else {
return false;
}
should instead be written as
return booleanExpression;
Similarly,
if (condition) {
return x;
}
return y;
should be written as
return (condition ? x : y);
10.5.3 Expressions before ‘?’ in the Conditional Operator
If an expression containing a binary operator appears before the ? in the ternary ?:
operator, it
should be parenthesized. Example:
(x >= 0) ? x : -x;
10.5.4 Special Comments
Use XXX in a comment to flag something that is bogus but works. Use FIXME to flag
something
that is bogus and broken.
11 - Code Examples
11.1 Java Source File Example
The following example shows how to format a Java source file containing a single public
class.
Interfaces are formatted similarly. For more information, see “Class and Interface
Declarations” on page 3 and “Documentation Comments” on page 8
144
145
19
/*
* @(#)Blah.java 1.82 99/03/18
*
* Copyright (c) 1994-1999 Sun Microsystems, Inc.
* 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
* All Rights Reserved.
*
* This software is the confidential and proprietary information of Sun
* Microsystems, Inc. ("Confidential Information"). You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Sun.
*/
package java.blah;
import java.blah.blahdy.BlahBlah;
/**
* Class description goes here.
*
* @version 1.82 18 Mar 1999
* @author Firstname Lastname
*/
public class Blah extends SomeClass {
/* A class implementation comment can go here. */
/** classVar1 documentation comment */
public static int classVar1;
/**
* classVar2 documentation comment that happens to be
* more than one line long
*/
private static Object classVar2;
/** instanceVar1 documentation comment */
public Object instanceVar1;
/** instanceVar2 documentation comment */
protected int instanceVar2;
/** instanceVar3 documentation comment */
private Object[] instanceVar3;
/**
* ... constructor Blah documentation comment...
*/
public Blah() {
// ...implementation goes here...
}
/**
* ... method doSomething documentation comment...
*/
public void doSomething() {
// ...implementation goes here...
}
/**
* ...method doSomethingElse documentation comment...
* @param someParam description
*/
public void doSomethingElse(Object someParam) {
// ...implementation goes here...
}
}
145
146
2.3.4.3.3
Round Trip Engineering
2.3.4.3.3.1
Beispiel Bank
// Einfügen
2.3.4.3.3.2
Beispiel Wertpapier
public class A extends WP
{
public A()
{
}
}
public class D
{
public IWP theIWP;
public K theK;
public D()
{
}
}
public class FV extends WP
{
public K theK;
public FV()
{
}
}
public interface IWP
{
}
public class K
{
protected String n;
public D theD;
public FV theFV;
146
147
public K()
{
}
/**
@roseuid 3CCA79B6001F
*/
public String getN()
{
}
}
public class O extends WP
{
public O()
{
}
}
public abstract class WP implements IWP
{
public WP()
{
}
}
// Einfügen
147
148
2.3.4.3.3.3
Sinn und Zweck
Die Umsetzung der Design Klassen in Code kann partiell durch Codegenerierung erfolgen.
Im Rahmen der Codegenerierung werden z.B. Associations in eine Referenz auf die
entsprechende Klasse umgesetzt. Der Name der Referenz entspricht der Rolle der Klasse.
Die Anzahl der Pointer entspricht der Muliplizität der Association.
Die Implementierung der Operationen erfolgt durch Umsetzung der Interaction Diagrams,
der Statechart Diagrams und des Pseudocodes aus dem Design Model. Dies geschieht in
der Regel manuell.
Das Reeengineering dient dazu, im Code vorgenommene Änderungen wieder in die UML
Diagramme zu integrieren. Somit befinden sich die Diagramme und der Code in einem
einheitlichen Zustand, ohne die Änderungen am Code per Hand in die UML Notation
übertragen zu müssen.
Die Kombination aus Codegenerierung und Reengineering bezeichnet man als Round Trip
Engineering.
2.3.4.3.3.4
2.3.4.3.3.4.1
Tutorial für Round Trip Engineering mit Rational Rose
Grundeinstellungen
2.3.4.3.3.4.1.1 Setzen des CLASSPATH
Grundvoraussetzung ist ein korrekt installiertes Java SDK (hier das aktuelle 1.4) und somit
eine komplett gesetzte CLASSPATH Variable.
Die CLASSPATH Variable wird z.B. vom Java Compiler verwendet, um die SystemBibliotheken (oder die eigenen) zu finden.
Unter Windows NT/2000 setzt man folgendermaßen:
Programme
Einstellungen
Systemsteuerung
System
Sytemeigenschaften
Erweitert
Umgebungsvariablen
Neu
Bei „Name der Variablen“: CLASSPATH eingeben
Bei „Wert der Variablen“ werden die Pfade zu den einzelnen JAR Dateien und notwendigen
Verzeichnissen eingegeben. So sei das SDK unter E:\jdk1.4.0 installiert. Somit wird
folgendes eingetragen:
E:\jk1.4.0\jre\lib\rt.jar;E:\jdk1.4.0\jre\lib\jce.jar;E:\jdk1.4.0\jre\lib\jsse.jar;E:\jdk1.4.0\jre\lib\jaw
s.jar;E:\jdk1.4.0\jre\lib\charsets.jar;E:\jdk1.4.0\jre\lib\sunrsaign.jar\;E:\jdk1.4.0\jre\lib\ext\dnsn
s.jar;E:\jdk1.4.0\jre\lib\ext\ldapsec.jar;E:\jdk1.4.0\jre\lib\ext\localedata.jar;E:\jdk1.4.0\lib\dt.jar
;E:\jdk1.4.0\lib\htmlconverter.jar;E:\jdk1.4.0\lib\tools.jar;.
2.3.4.3.3.4.1.2 Anlegen eines Projektverzeichnisses
Z.B. auf Laufwerk F: das Verzeichnis JavaTest.
2.3.4.3.3.4.1.3 Starten von Rose
Die Einstellungen beziehen sich auf die Rational Rose 2000 enterprise Version. Somit
können sich die Menüpunkte je nach Version unterscheiden, wobei die Vorgehensweise
148
149
identisch ist.
Nach dem Starten wird beim Wizard „Create New Model“ das WorkModel „J2SE 1.3“ oder
ein höher ausgewählt. Dies ist notwendig, damit Rose alle benötigten Java Klassen und
Elemente auflösen kann.
2.3.4.3.3.4.1.4 Hinzufügen des Projektverzeichnisses zum CLASSPATH
Nun wird das Projektverzeichnis den Java Projekt Spezifikationen hinzugefügt:
Tools
Java
Project Specification
Icon: New
Directory
Unser Verzeichnis auf Laufwerk F auswählen und mit OK bestätigen
Mit OK das Verzeichnis übernehmen
2.3.4.3.3.4.1.5 Speichern des Rose Projektes
Nun wird das Rose Projekt gespeichert. Hier: in unserem Projektverzeichnis unter dem
Namen HelloWorld.mdl.
2.3.4.3.3.4.2
Vorgehensweise am Beispiel HelloWorld
2.3.4.3.3.4.2.1 LogicalView: Erzeugen der Start Klasse
Das HelloWorld Projekt soll aus 2 Klassen bestehen. Die erste ist nur die Startklasse mit
der Methode „main“, während die andere Klasse „HelloWorldGUI“ das eigentliche
Programm bildet. In dieser Klasse soll eine JFrame erzeugt und die Meldung „Hello World“
ausgegeben werden.
Hieraus folgt folgendes UML Diagramm:
2.3.4.3.3.4.2.2 Erzeugen des Codes der Klasse
Markieren der Klasse „HelloWorld“
Tools
Java
Generate Java
Nun erscheint folgender Dialog
149
150
In diesem wird unsere Klasse einem Verzeichnis (welches vorher in der Classpath Variable
aufgenommen wurde) zugewiesen. Dies muss für jede Klasse nur 1 mal absolviert werden.
Select Java Files oder SelectAll
Select Class Path Entry
Nach dem Bestätigen des „Assign“ Buttons und des OK Buttons wird der Code erzeugt.
2.3.4.3.3.4.2.3 Bearbeiten des Codes
Die Klasse HelloWorld.java wird nun in einem Editor (oder IDE) geladen und sieht
folgendermaßen aus:
//Source file: F:\\JAVATEST\\HelloWorld.java
public class HelloWorld
{
}
Nun wir den restlichen Code (hier blau):
//Source file: F:\\JAVATEST\\HelloWorld.java
public class HelloWorld
{
public static void main(String args[])
{
HelloWorldGUI hwg = new HelloWorldGUI();
Hwg.setSize(300,100);
hwg.setLocation(100,100);
hwg.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
hwg.setVisible(true);
}
}
Somit ergibt sich die nächste Klasse aus dem Code: HelloWorldGUI. Diese wird nun
ebenfalls erst in UML verfasst und dann ebenfalls mittels Codeerzeugung generiert.
Vorher ist es jedoch sinnvoll das UML Diagramm der HelloWorld Klasse auf den neuesten
(den die Datei HelloWorld.java vorgibt) Stand zu bringen.
2.3.4.3.3.4.2.4 ReverseEngineering der HelloWorld Klasse
Tools
Java
150
151
Reverse Engineer Java:
Auswahl des Verzeichnisses im Verzeichnisbaum
Mittels „Add“ oder „AddAll“ bei mehreren Dateien die Klasse HelloWorld.java aufnehmen
Per „SelectAll“ oder per Click auf den Namen die upzudatende Klasse auswählen.
Per „Reverse“ starten
Wenn keine Fehler gemeldet wurden einfach Fenster per „Done“ schließen
Ergebnis:
Die Datei HelloWorld.java darf nicht gelöscht werden, da sonst deren Zustand nicht
komplett per Codeerzeugung hergestellt werden kann. D.h. Das UML Diagramm und die
Datei bilden jetzt eine Einheit und sind nicht trennbar.
2.3.4.3.3.4.2.5
Erzeugen von HelloWorldGUI
Das UML Diagramm wird erweitert :
151
152
2.3.4.3.3.4.2.6 Erzeugen des Codes der Klasse
Der Code wird wie unter 2.3.4.3.3.4.2.2 beschreiben erzeugt
2.3.4.3.3.4.2.7 Bearbeiten des Codes
Der Code sieht folgendermaßen aus
//Source file: F:\\JAVATEST\\HelloWorldGUI.java
import javax.swing.*;
import java.awt.*;
public class HelloWorldGUI extends JFrame
{
public HelloWorldGUI()
{
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
}
public void paint(Graphics g)
{
g.drawString(„HelloWorld“,100,50);
}
}
2.3.4.3.3.4.2.8 ReverseEngineering der HelloWorldGUI Klasse
Durchführung wie oben beschrieben
152
153
2.3.4.4
Integrieren des Systems
Der Integrator integriert das System in Übereinstimmung mit dem Build Plan.
2.3.5
Test
2.3.5.1
Beispiel Klassentest MyMath
Kleine Testumgebung für den Klassentest einer selbst geschriebenen MyMath Klasse.
2.3.5.1.1
BlackBoxTest
2.3.5.1.1.1
Testling Interface
public interface IMiniMath
{
public double calcDurch (double a, double b);
public double calcPotenz (double b, int n);
public double calcMal (double a, double b);
public double calcExp (double x, double epsilon);
public int getError();
public void resetError();
}
2.3.5.1.1.2
Testapplikation
public class TestApp
extends JFrame
implements ActionListener
{
private TestController testController = null;
private Tracer tracer = null;
public TestApp()
{
super("Test Applikation");
addWindowListener(new WindowClosingAdapter());
JMenuBar menubar = new JMenuBar();
menubar.add(createFileMenu());
setJMenuBar(menubar);
testController = new TestController();
tracer = new Tracer();
}
public void actionPerformed(ActionEvent event)
{
String c = event.getActionCommand();
System.out.println(c);
if (c.equals("Start"))
{
System.out.println("Start Test");
testController.start();
}
if (c.equals("Stop"))
{
System.out.println("Stop Test");
testController.stop();
}
if (c.equals("Beenden"))
{
System.out.println("Beenden");
tracer.shutDown();
}
}
//---Private Methoden--------------private JMenu createFileMenu()
{
JMenu ret = new JMenu("Test");
ret.setMnemonic('T');
JMenuItem mi;
153
154
//Starten
mi = new JMenuItem("Start", 'S');
setCtrlAccelerator(mi, 'S');
mi.addActionListener(this);
ret.add(mi);
//Separator
ret.addSeparator();
//Speichern
mi = new JMenuItem("Stop", 'p');
setCtrlAccelerator(mi, 'S');
mi.addActionListener(this);
ret.add(mi);
//Separator
ret.addSeparator();
//Beenden
mi = new JMenuItem("Beenden", 'B');
setCtrlAccelerator(mi, 'B');
mi.addActionListener(this);
ret.add(mi);
return ret;
}
private void setCtrlAccelerator(JMenuItem mi, char acc)
{
KeyStroke ks = KeyStroke.getKeyStroke(
acc, Event.CTRL_MASK
);
mi.setAccelerator(ks);
}
public static void main(String[] args)
{
TestApp frame = new TestApp();
frame.setLocation(100, 100);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
2.3.5.1.1.3
Tracer
public class Tracer {
static Tracer tracer = null;
private Calendar calendar = null;
private PrintWriter out = null;
public Tracer ()
{
tracer = this;
calendar = Calendar.getInstance( TimeZone.getTimeZone( "ECT" ));
try
{
out = new PrintWriter(
new FileWriter( "TracerLog.txt" ));
}
catch (IOException ioe)
{
}
}
public void shutDown()
{
out.close();
String s = getTime() + " Monitor File closed !";
System.out.println(s);
out.println(s);
}
static public void put(String s)
{
tracer.send(s);
}
public void send(String s)
{
String str = getTime() + " Traceeintrag : " + s;
System.out.println(str);
out.println(str);
}
154
155
private String getTime() {
calendar.setTime( new Date());
return setLeadingZero( calendar.get( Calendar.HOUR_OF_DAY ))
+ ":" + setLeadingZero( calendar.get( Calendar.MINUTE ))
+ ":" + setLeadingZero( calendar.get( Calendar.SECOND ));
}
private String setLeadingZero( int i ) {
return ( i < 10 ) ? "0" + String.valueOf( i ): String.valueOf( i );
}
}
2.3.5.1.1.4
TestController
public class TestController {
static public boolean whiteBoxTest = false;
public TestController ()
{
}
public void start()
{
Tracer.put("Start des Testlaufs !!!");
// Test der Klasse MiniMath
IMiniMath mm = new MiniMath();
double result;
/*
// BlackBoxTest
// Testserie für die Funktion calcDurch()
// Testfall Standarddivision
mm.resetError();
result = mm.calcDurch(3.5, 0.5);
Tracer.put("Result " + result);
Tracer.put("error " + mm.error);
Tracer.put(" Testfall Ende");
// Testfall Division durch negative Zahl
mm.resetError();
result = mm.calcDurch(0.8, -0.2);
Tracer.put("Result " + result);
Tracer.put("error " + mm.error);
Tracer.put(" Testfall Ende");
// Testfall Division durch Null
mm.resetError();
result = mm.calcDurch(3, 0);
Tracer.put("Result " + result);
Tracer.put("error " + mm.error);
Tracer.put(" Testfall Ende");
*/
/*
// BlackBoxTest
// Testserie für die Funktion calcPotenz()
// Testfall Standard
mm.resetError();
result = mm.calcPotenz(2, 3);
Tracer.put("Result " + result);
Tracer.put("error " + mm.error);
Tracer.put(" Testfall Ende");
// Testfall Negativer Exponent
mm.resetError();
result = mm.calcPotenz(2, -3);
Tracer.put("Result " + result);
Tracer.put("error " + mm.error);
Tracer.put(" Testfall Ende");
// Testfall Exponent 0
mm.resetError();
result = mm.calcPotenz(2, 0);
Tracer.put("Result " + result);
Tracer.put("error " + mm.error);
Tracer.put(" Testfall Ende");
*/
155
156
// WhiteBoxTest
whiteBoxTest = true;
// Testserie für die Funktion calcPotenz()
// Testfall Standard
mm.resetError();
result = mm.calcPotenz(2, 3);
Tracer.put("Result " + result);
Tracer.put("error " + mm.getError());
Tracer.put(" Testfall Ende");
// Testfall Negativer Exponent
mm.resetError();
result = mm.calcPotenz(2, -3);
Tracer.put("Result " + result);
Tracer.put("error " + mm.getError());
Tracer.put(" Testfall Ende");
// Testfall Exponent 0
mm.resetError();
result = mm.calcPotenz(2, 0);
Tracer.put("Result " + result);
Tracer.put("error " + mm.getError());
Tracer.put(" Testfall Ende");
// End of WhiteBoxTest
whiteBoxTest = false;
/*
// BlackBoxTest
// Testserie für die Funktion calcExp()
// Testfall exp(1)
result = mm.calcExp(1, 0.001);
Tracer.put("Result " + result);
Tracer.put(" Testfall Ende");
*/
}
public void stop()
{
Tracer.put("Beenden des Testlaufs !!!");
}
}
2.3.5.1.1.5
Testprozedur
Doppelclick auf TestApp im Verzeichnis .../TestSystem
Aktivieren des Codes für den Testfall durch Entfernen der Kommentare
Ausführen der TestApp Applikation
Click auf MenueItem Start
Öffnen der Datei ExpectedTracerLog.txt im Verzeichnis .../TestSystem
Vergleich der erwarteten Ausgaben mit den Ausgaben der Datei ExpectedTracerLog.txt im
Verzeichnis .../TestSystem
2.3.5.1.1.6
Einige Testfälle
2.3.5.1.1.6.1
Test calcDurch()
2.3.5.1.1.6.1.1 Testfall Standarddivision
2.3.5.1.1.6.1.1.1
Voraussetzungen
error = 0
2.3.5.1.1.6.1.1.2
Eingabe
a = 3.5
b = 0.5
156
157
2.3.5.1.1.6.1.1.3
Erwartete Ausgabe
7
error = 0
2.3.5.1.1.6.1.1.4
Ausgabe
7
error = 0
2.3.5.1.1.6.1.2 Testfall Division durch negative Zahl
2.3.5.1.1.6.1.2.1
Voraussetzungen
error = 0
2.3.5.1.1.6.1.2.2
Eingabe
a = 0.8
b = -0.2
2.3.5.1.1.6.1.2.3
Erwartete Ausgabe
-4
error = 0
2.3.5.1.1.6.1.2.4
Ausgabe
-4
error = 0
2.3.5.1.1.6.1.3 Testfall Division durch Null
2.3.5.1.1.6.1.3.1
Voraussetzungen
error = 0
2.3.5.1.1.6.1.3.2
Eingabe
a=3
b=0
2.3.5.1.1.6.1.3.3
Erwartete Ausgabe
0
error = -1
2.3.5.1.1.6.1.3.4
Ausgabe
157
158
Infinity
error = 0
2.3.5.1.1.6.2
Test calcPotenz()
2.3.5.1.1.6.2.1 Testfall Standard
2.3.5.1.1.6.2.1.1
Voraussetzungen
error = 0
2.3.5.1.1.6.2.1.2
Eingabe
b=2
n=3
2.3.5.1.1.6.2.1.3
Erwartete Ausgabe
8
error = 0
2.3.5.1.1.6.2.1.4
Ausgabe
8
error = 0
2.3.5.1.1.6.2.2 Testfall Negativer Exponent
2.3.5.1.1.6.2.2.1
Voraussetzungen
error = 0
2.3.5.1.1.6.2.2.2
Eingabe
b=2
n = -3
2.3.5.1.1.6.2.2.3
Erwartete Ausgabe
0.125
error = 0
2.3.5.1.1.6.2.2.4
Ausgabe
0.5
error = 0
2.3.5.1.1.6.2.3 Testfall Exponent Null
158
159
2.3.5.1.1.6.2.3.1
Voraussetzungen
error = 0
2.3.5.1.1.6.2.3.2
Eingabe
b=3
n=0
2.3.5.1.1.6.2.3.3
Erwartete Ausgabe
1
error = 0
2.3.5.1.1.6.2.3.4
Ausgabe
2
error = 0
2.3.5.1.2
WhiteBoxTest
2.3.5.1.2.1
Testling
public class MiniMath implements IMiniMath
{
static int whileCounter_Block_2_3_while = 0;
private int error = 0;
public double calcDurch (double a, double b)
{
if (0 != a)
{
return a/b;
}
else
{
error = -1;
return 0;
}
}
public double calcPotenz (double b, int n)
{
int i;
double p = 0;
if (0 > b)
{
if (TestController.whiteBoxTest) Tracer.put("Block_1_if_then");
error = -1;
}
else
{
if (TestController.whiteBoxTest) Tracer.put("Block_2_if_else");
if (0 == n)
{
if (TestController.whiteBoxTest) Tracer.put("Block_2.1_if_then");
p = 1;
}
{
159
160
if (TestController.whiteBoxTest) Tracer.put("Block_2.2_if_else");
}
p = b;
i = 1;
while (i < n)
{
whileCounter_Block_2_3_while++;
if (TestController.whiteBoxTest) Tracer.put("Block_2.3_while Loops " +
whileCounter_Block_2_3_while);
p = p * b;
i++;
}
if (0 > n)
{
if (TestController.whiteBoxTest) Tracer.put("Block_2.4_if_then");
p = 1/p;
}
else
{
if (TestController.whiteBoxTest) Tracer.put("Block_2.5_if_else");
}
}
return p;
}
public double calcMal (double a, double b)
{
return a*b;
}
public double calcExp (double x, double epsilon)
{
double summand = 1;
int k;
double expo;
if (x == 0)
{
expo = 0;
}
else
{
expo = 1;
k = 0;
do
{
summand = summand * x / (k + 1);
expo = expo + summand;
k = k+1;
}while (summand >= epsilon);
}
System.out.println("Math Funktion exp(x) = " + java.lang.Math.exp(x));
return expo;
}
public int getError()
{
return error;
}
public void resetError()
{
error = 0;
}
}
2.3.5.1.2.2
Test Log
160
161
TracerLog.txt
12:25:36
12:25:36
12:25:36
12:25:36
12:25:36
12:25:36
12:25:36
12:25:36
12:25:36
12:25:36
12:25:36
12:25:36
12:25:36
12:25:36
12:25:36
12:25:36
12:25:36
12:25:36
12:25:36
12:25:36
12:25:36
12:25:36
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
Traceeintrag
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
Start des Testlaufs !!!
Block_2_if_else
Block_2.2_if_else
Block_2.3_while Loops 1
Block_2.3_while Loops 2
Block_2.5_if_else
Result 8.0
error 0
Testfall Ende
Block_2_if_else
Block_2.2_if_else
Block_2.4_if_then
Result 0.5
error 0
Testfall Ende
Block_2_if_else
Block_2.1_if_then
Block_2.2_if_else
Block_2.5_if_else
Result 2.0
error 0
Testfall Ende
161
162
2.3.5.1.2.3
Kontrollflussgraph
162
163
2.3.5.2
Beispiel für Test Guidelines
Guidlines wurden für industrielle Bildverarbeitungs-Projekte mit einer Größe von ca. 5
Entwickler, 500 Klassen geschrieben.
2.3.5.2.1
Klassentest
2.3.5.2.1.1
Ziel
Der Klassentest überprüft das Zusammenspiel der einzelnen Operationen einer Klasse und
das Zusammenspiel einer Klasse mit ihren Oberklassen.
Das Zusammenspiel mit Objekten anderer verwendeter Klassen wird dabei nicht explizit
überprüft.
Verwendet die zu testende Klasse andere nicht implementierte Klassen, so werden diese
durch Stubs ersetzt.
2.3.5.2.1.2
Zu testende Klassen
Alle wichtigen Klassen sind zu testen.
Existiert eine Vererbungshierarchie, so sind jeweils die letzten abgeleiteten Klassen zu
testen. Ein isolierter Test von Schnittstellen oder abstrakten Basisklassen kann entfallen.
2.3.5.2.1.3
2.3.5.2.1.3.1
Black Box Test (Funktionaler Test)
Ziel
Der Specification Test oder Black Box Test verifiziert das externe Verhalten der Klasse.
Specification testing is done to verify the components behaviour without considering how the
behaviour is implemented within the component. The specification tests thus look at what
output the component will return, when given certain input and when starting in a particular
state.
2.3.5.2.1.3.2
Definition der Testfälle
2.3.5.2.1.3.2.1 Allgemeine Hinweise
Beim Black Box Test werden die Testfälle aus der Spezifikation der Klasse abgeleitet.
Ein vollständiger Funktionstest ist i.A. nicht möglich.
Die Testfälle sind daher so auszuwählen, dass die Wahrscheinlichkeit groß ist, Fehler zu
finden.
Alle wichtigen, kritischen und mit hohem Risiko behafteten Funktionen der Klasse sind zu
testen.
Die funktionale Überdeckung soll mindestens 70% betragen.
Jede Unterklasse definiert einen neuen Kontext, der zu einem fehlerhaften Verhalten von
geerbten Operationen führen kann.
Polymorphe Methodenaufrufe sind zu testen.
Überladene Operatoren und Methoden sind zu testen.
2.3.5.2.1.3.2.2 Äquivalenzklassenbildung
163
164
Zum Argumentbereich einer Operation gehören nicht nur ihre Eingangsparameter sondern
auch der Zustand ihres Objektes.
Die Kombinationsmöglichkeiten für Input, Output und States ermöglichen in der Regel keine
vollständige Testüberdeckung. Daher sind Input, Output und States in Äquivalenzklassen
zu zerlegen und alle wichtigen Kombinationen von Äquivalenzklassen zu testen.
Es wird davon ausgegangen, dass ein Programm bei der Verarbeitung eines
Repräsentanten aus einer Äquivalenzklasse so reagiert, wie bei allen anderen Werten
dieser Äquivalenzklasse. Wenn das Programm mit dem repräsentativen Wert fehlerfrei
läuft, dann ist zu erwarten, dass es auch für andere Werte aus dieser Äquivalenzklasse
korrekt funktioniert.
Beispiel
1 <= monat <= 12
A1: 1 <= monat <= 12
A2: 12 < monat
A3: monat < 1
Testfälle:
monat = 5
monat = 17
monat = -3
Die Ausgabeäquivalenzklassen müssen in entsprechende Äquivalenzklassen der
Eingabewerte umgeformt werden.
Die Testfälle für ungültige Äquivalenzklassen werden durch Auswahl eines Testdatums aus
einer ungültigen Äquivalenzklasse gebildet. Dieses Testdatum wird mit Werten kombiniert,
die ausschließlich aus gültigen Äquivalenzklassen entnommen sind. Werden mehrere
fehlerhafte Eingaben pro Testfall verwendet, so ist nicht tranparent, welches fehlerhafte
Datum die Fehlerbehandlung ausgelöst hat.
2.3.5.2.1.3.2.3 Grenzwertanalyse
Erfahrungen haben gezeigt, dass Testfälle, die die Grenzwerte der Äquivalenzklassen
abdecken oder in der unmittelbaren Umgebung dieser Grenzen liegen, besonders häufig
Fehler aufdecken.
Daher soll nicht irgendein Element aus der Äquivalenzklasse als Repräsentant ausgewählt
werden, sondern mehrere Elemete sollen so ausgesucht werden, dass der Rand der
Äquivalenzklasse getestet wird.
Beispiel
1 <= monat <= 12
A1 : 1 <= monat <= 12
A2 : 12 < monat
A3 : monat < 1
Testfälle
monat = 0
monat = 1
monat = 12
monat = 13
Weitere Beispiele für Grenzwerte
Leeres Array, volles Array, Array mit nur einem freien oder nur einem gefüllten Platz
164
165
2.3.5.2.1.3.2.4 Test spezieller Werte (special values testing)
Aus der Erfahrung heraus soll eine Liste möglicher Fehlersituationen aufgestellt und daraus
Testfälle abgeleitet werden.
Beispiele für spezielle Werte :
Null-Werte
Sonderzeichen und Steuerzeichen
2.3.5.2.1.3.2.5 Zufallstest
Aus dem Wertebereich der Eingabedaten werden zufällig Testfälle erzeugt.
Tester neigen dazu, bestimmte Testfälle zu erzeugen, die auch bei der Implementierung als
naheliegend betrachtet worden sind und für die sich das Programm folglich gutartig verhält.
2.3.5.2.1.3.2.6 Ableitung von Testfällen aus State Charts
Die Operationen einer Klasse sind über die Variablen der Klasse gekoppelt.
Für Klassen mit relevanten Zuständen sollen die Testfälle so spezifiziert werden, dass alle
wichtigen und kritischen, d.h. mit hohem Risiko behafteten, Folgen von
Zustandsübergängen mit den Testfällen abgedeckt werden.
Ein Objekt muss durch explizite Initialisierung (Konstruktor, Set-Funktionen) oder durch
vorhergehende Testfälle in einen für den aktuellen Testfall relevanten Zustand versetzt
werden.
2.3.5.2.1.4
2.3.5.2.1.4.1
White Box Test (Strukturtest, Glass Box Test)
Ziel
Die Spezifikation einer Klasse besitzt ein höheres Abstraktionsniveau als die
Implementierung.
Daher soll der Klassentest nicht auf Black Box Tests beschränkt werden.
Der Structure Test oder White Box Test verifiziert das interne Verhalten der Klasse.
RUP Text :
Structure testing is done to verify that a component works internally as intended. The tester
should be sure to test the most commonly followed and the most critical paths through the
algorithms and paths that are associated with high risks.
2.3.5.2.1.4.2
Definition der Testfälle beim Kontrollflussorientierte Strukturtestverfahren
Bein kontrollflussorientierten Test werden Strukturelemente wie Anweisungen oder
Verzweigungen verwendet, um Testziele zu definieren.
Jeder Knoten eines Kontrollflussgraphen stellt eine ausführbare Anweisung dar. Eine
gerichtete Kante von einem Knoten zu einem anderen beschreibt einen möglichen
Kontrollfluss. Die Kanten werden als Zweige bezeichnet.
2.3.5.2.1.4.2.1 Anweisungsüberdeckung
Der Anweisungsüberdeckungs- oder C0 –Test verlangt die Ausführung aller Knoten des
Kontrollflussgraphen.
(C = Coverage)
165
166
Metrik
C0 = Anz ausgef Anw / Anz vorh Anw
Probleme :
Kontrollstrukturen werden nicht berücksichtigt.
Fehleridentifizierungsquote erfahrungsgemäß ca 18%
2.3.5.2.1.4.2.2 Zweigüberdeckung
Der Zweigüberdeckungs- oder C1 –Test fordert verschärfend die Ausführung aller Kanten.
Die Anweisungsüberdeckung ist in der Zweigüberdeckung vollständig enthalten.
C1 = Anz ausgef Zweige / Anz vorh Zweige
Probleme :
Kombinationen von komplexen Bedingungen werden nicht berücksichtigt.
Schleifen werden nicht ausreichend gestestet.
Abhängigkeiten zwischen Verzweigungen werden nicht berücksichtigt.
Fehleridentifizierungsquote erfahrungsgemäß 34%
2.3.5.2.1.4.2.3 Pfadüberdeckung
Der vollständige Pfadüberdeckungstest fordert die Ausführung aller unterschiedlichen Pfade
des zu testenden Programmes.
Ein Pfad ist eine Sequenz von Knoten des Kontrollflussgraphen.
Die Anzahl der möglichen Pfade ist bereits bei kleinen überschaubaren Kontrollstrukturen
unpraktikabel hoch.
Ein Teil der konstruierbaren Pfade ist nicht ausführbar, da sich die Bedingungen an den
Verzweigungen gegenseitig ausschließen.
Daher sollen nur die wichtigsten Pfade durch den Code durchlaufen werden. Dies beinhaltet
insbesondere die am häufigsten durchlaufenen, kritischen und mit hohem Risiko
verbundenen Pfade.
The tester should be sure to test the most commonly followed and the most critical paths
through the algorithms and paths that are associated with high risks.
(Boundary Interior Pfadtest :
Beim Testen von Schleifen wird auf die Überprüfung von Pfaden verzichtet, die durch mehr
als einmalige Schleifenwiederholungen erzeugt werden.)
2.3.5.2.1.4.2.4 Bedingungsüberdeckung
Es ist darauf zu achten, dass alle wichtigen Bedingungen im Code mit den Testfällen
abgedeckt werden.
Minimale Mehrfach-Bedingungsüberdeckung
Jede Bedingung -ob atomar oder nicht- muss mindestens einmal true und einmal false
sein.
Alternative Kriterien
Einfache Bedingungsüberdeckung
166
167
Die Evaluation aller atomaren Bedingungen muss mindestens einmal den Wahrheitswert
true oder false ergeben.
2.3.5.2.2
Integrationstest
2.3.5.2.2.1
Ziel
Integrationstestfälle verifizieren, dass eine bestimmte Menge von Komponenten nach der
Integration richtig zusammenarbeiten.
Integration test cases are used to verify that the components interact properly with each
other after they have been integrated.
2.3.5.2.2.2
Für welche Gruppen von Klassen ist ein Integrationstest durchzuführen
Die inkrementelle Entwicklung führt zu einer inkrementellen Integrationsstrategie. Die
Zusammenstellung der zu integrierenden Klassen orientiert sich dabei an den zu
realisierenden Use Cases.
Für jeden Use Case ist die Interaktion aller wichtigen an der Realiserung beteiligten Klassen
zu testen. Das Zusammenspiel bereits integrierter Teilsysteme wird dabei nicht erneut
aufgezeichnet und überprüft.
2.3.5.2.2.3
Definition der Testfälle
Die Testfälle ergeben sich aus den Interaktionsdiagrammen der Use Case Realisierungen.
Integration test cases can be derived from use case realizations.
The tester should try to find a set of test cases, each of with tests an interesting scenario
through a use case realization.
Each use case will be tested for its normal flow and at least one alternative flow.
Insbesondere sollen Use Cases getestet werden, die parallel ablaufen oder ausgeführt
werden, sich gegenseitig beeinflussen, in die mehrere Threads oder Prozesse involviert
sind.
2.3.5.2.3
Regressionstest
Wird in dem Testling ein Fehler korrigiert, so ist mit den bereits vorhandenen Testfällen ein
Regressionstest durchzuführen.
Es soll darauf geachtet werden, dass möglichst viele Testfälle im Verlaufe der Iterationen
wiederverwendet werden können.
2.3.5.2.4
Systemtest
2.3.5.2.4.1
Ziel
Der Systemtest ist der abschließende Test in der realen Umgebung ohne den Auftraggeber.
System tests are used to test that the system functions properly as a whole.
Der Test basiert auf den für das System spezifizierten Use Cases.
Das Innere des Systems ist nicht sichtbar.
2.3.5.2.4.2
Definition der Testfälle
Funktionstest (Vollständigkeit)
167
168
Das System muss alle funktionalen Anforderungen erfüllen.
Massentest (Datenvolumen)
Das System sollte mit umfangreichen Datenmengen getestet werden.
Zeittest
Bei starker Systembelastung ist zu prüfen, ob die geforderten Antwortzeiten und
Durchsatzraten eingehalten werden.
Lasttest (Zuverlässigkeit)
Das System ist über längere Zeit unter Spitzenbelastung zu testen.
Z.B. Mehrbenutzerbetrieb mit der maximalen Benutzeranzahl.
Stresstest (Fehlertoleranz, Robustheit)
Das System wird unter Überlast getestet. Z.B. wird der Arbeitsspeicher reduziert.
Usability Test (Benutzbarkeit)
Es wird die Verständlichkeit, Erlernbarkeit und Bedienbarkeit aus der Sicht des
Endbenutzers getestet.
Sicherheitstest
Hier werden Datenschutzmechanismen getestet.
Z.B. verschlüsselte Passwörter, Überprüfung von Zugriffsrechten.
Interoperabilitätstest
Es wird die Zusammenarbeit mit anderen Systemen getestet.
Z.B. auch Ausfall von Hard- und Softwarekomponenten, mit denen das System
kommuniziert.
Konfigurationstest
Das System wird auf unterschiedlichen Plattformen getestet.
Dokumententest
Vorhandensein und Güte der Benutzer- und Wartungsdokumentation ist zu testen.
2.3.5.2.5
Abnahmetest (Acceptance Test)
Systemtest unter Beobachtung, Mitwirkung und Federführung des Auftraggebers unter
realen Einsatzbedingungen beim Auftraggeber und mit Daten des Auftraggebers.
Beim Alpha-Test wird das System in der Zielumgebung des Herstellers durch Anwender
erprobt.
Beim Beta-Test wird das System bei ausgewählten Pilotkunden in deren eigener Umgebung
zur Probenutzung zur Verfügung gestellt.
Verification
Es wird geprüft, ob das System entsprechend der Spezifikation entwickelt wurde.
Validation
Es wird geprüft, ob das System mit den Erwartungen des Kunden übereinstimmt.
2.3.5.2.6
Testdurchführung
2.3.5.2.6.1
Testumgebung
Die Testungebung besteht aus einem Testcontroller, dem Tracer, den Stubs und einer
Standardtestapplikation (Testrahmen).
168
169
Der Tracer protokolliert die Botschaften an Objekte, die Input und Outputparameter von
Operationen und die States des Testlings.
Stubs werden benötigt, wenn eine zu testende Systemkomponente andere
Systemkomponenten aufruft, die zwar spezifiziert aber nicht implementiert oder aus
anderen Gründen (z.B. Hardware) nicht verwendet werden sollen. Stubs simulieren die
verlangten Funktionen mit eingeschränktem Leistungsumfang.
Ein Testcontroller wird benötigt, um eine Systemkomponente zu testen, deren Dienst nicht
direkt von der Benutzungsoberfläche aufgerufen werden kann oder soll.
Der Testcontroller erzeugt das Testobjekt, versetzt es in den gewünschten
Ausgangszustand und stößt die gewünschte Operation an.
Der Testcontroller erzeugt ggf. Stubs.
Im Falle komplexer Testszenarien wird derTestcontroller ggf. durch TestHandler
Hilfsklassen ergänzt.
Testcontroller bzw. Stubs können ihre Testdaten bzw. Simulationsdaten aus Data-Files
beziehen.
Sowohl der Testcontroller als auch die Stubs können zusätzlich mit einer grafischen
Oberfläche TestcontrollerDialog bzw. StubDialog versehen werden, um einen interaktiven
Test zu ermöglichen (Beispiel TestDialog für grafische Objekte).
Im Hinblick auf den erheblichen Aufwand eines interaktiven Regressionstests ist jedoch in
der Regel ein codegesteuerter Test zu bevorzugen.
Die Standardtestapplikation bindet den Tracer und den Testcontroller ein und ermöglicht
deren Bedienung (Sie enthält z.B. einen Menüpunkt „Start Test“).
2.3.5.2.6.2
Durchführung eines kombinierten White und Black Box Test für Klassen
Zur Vereinfachung der Testdurchführung und der Dokumentation können Black und White
Box Tests kombiniert werden :
An Hand der Klassendokumentation werden für den Black Box Test Äquivalenzklassen
gebildet, Grenzwerte ermittelt und Spezialfälle überlegt. Die Testdaten werden zu Testfällen
kombiniert und die Sollwerte überlegt. Die Testfälle werden zu Testsequenzen kombiniert.
Es wird ein Testcontroller geschrieben, der den Code zur Durchführung der Testsequenzen
enthält.
Der Testcontroller erzeugt den Testling und ggf. Stubs, versetzt den Testling in den
gewünschten State und stößt die zum Testfall gehörende Operation an.
Nach der Ausführung der Operation werden mindestens die Output- Parameter und die
relevanten States mittels Tracer in einem zum Testling gehörenden File
<TestlingName>Test.log protokolliert.
Zur Unterstützung des Tests müssen entsprechende Set() und Get() Funktionen für das
Setzen und Auslesen der States vorhanden sein.
Die Ergebnisse der Funktionsaufrufe werden mit den erwarteten Ergebnissen verglichen.
Die zugehörigen .log Dateien dienen als Vorlage für spätere Regressionstests.
Durch Codeinspektion wird ermittelt, welche wichtigen und kritischen Pfade und
Bedingungen durch die funktionalen Testfälle noch nicht abgedeckt sind.
Es werden Testfälle für die noch nicht durchlaufenen Pfade oder Bedingungen aufgestellt
und dem Testcontroller hinzugefügt.
2.3.5.2.6.3
Durchführung eines Integrationstests für eine Gruppe von Klassen
An Hand der Dokumentation der Use Case Realizations werden Testdaten für Testfälle
überlegt.
Es wird ein Testcontroller geschrieben, der den Code zur Durchführung der Testfälle
enthält.
169
170
Der Testcontroller erzeugt die Instanzen der zu integrierenden Klassen und ggf. Stubs,
versetzt die Instanzen in den gewünschten State und stößt die zum Testfall gehörenden
Operationen an.
Während der Interaktion der beteiligten Instanzen werden alle wichtigen Interaktionen vom
Tracer in einem zum integrierten Subsystem gehörenden File <SubsystemName>Test.log
protokolliert.
Nach der Ausführung der Operation werden die Output- Parameter und die relevanten
States protokolliert.
Die zugehörigen .log Dateien dienen als Vorlage für spätere Regressionstests.
Die aufgezeichneten Interaktionen sind mit dem für den Testfall zugrundeliegenden
Interaktionsdiagramm zu vergleichen.
Existiert kein solches Diagramm, so ist das aufgezeichnete Interaktionsszenario auf
Plausibilität zu prüfen.
Zur Vorbereitung des Integrationstests sind alle wichtigen öffentlichen Operationen aller
wichtigen Klassen mit Traceaufrufen zu versehen, die im Falle eines Testlaufes über den
Tracer durch Setzen des Trace Modus „Test“ aktiviert werden können.
2.3.5.2.6.4
Wer führt den Test durch
Software-Entwicklung und Qualitätssicherung sind organisatorisch getrennt.
Der Entwickler testet sein eigenes Programm. Auf diese Weise führt er sein eigenes Code
Review durch.
Die Abnahme durch die QS erfolgt erst nach dem Test.
Der Package Owner testet die Klassen seines Packages.
Der Package Owner integriert die Klassen seines Packages und die verwendeten Klassen
anderer Packages.
2.3.5.2.6.5
Testendekriterium
Das Ende der Tests ist mit einem fehlerfreien Durchlauf aller Testfälle erreicht. Bei sehr
fehlerhafter Software entscheidet das Projekt-Team über die Weiterführung der Tests.
2.3.5.2.6.6
Verwaltung der gefundenen Fehler
Gefundene Fehler werden priorisiert (medium, high, low), im Package Dokument verwaltet
und entsprechend ihrer Bedeutung von dem Package Owner behoben.
2.3.5.2.6.7
Verwaltung der Testumgebung und Testdaten
Alle für den Test benötigten Komponenten der Testumgebung, also der Testcontroller, die
Stubs, das Logfile mit den Testergebnissen werden im Rahmen des Package des Testlings
verwaltet.
2.3.5.3
Erstellen eines Test Case
Ein Testfall beschreibt den Input, Conditions für die Ausführung, die erwarteten und die
tatsächlichen Ergebnisse.
Aus RUP
The definition (usually formal) of a specific set of test inputs, execution conditions, and
expected results, identified for the purpose of making an evaluation of some particular
aspect of a Target Test Item.
170
171
2.3.5.4
Erstellen einer Test Prozedur
Eine Test Prozedur beschreibt, wie die Evaluierung für einen Testfall durchzuführen ist.
Z.B. die manuelle Durchführung eines Tests oder die Verwendung eines Testtools oder
Testframeworks.
Aus RUP
A test procedure specifies how to perform one ore several test cases.
How to perform one test case may be specified by one test procedure, but is often usful to
reuse a test procedure a test procedure for several test cases.
The step-by-step instructions that realize a test, enabling its execution. Test Scripts may
take the form of either documented textual instructions that are executed manually or
computer readable instructions that enable automated test execution.
2.3.5.5
Erstellen eines Test Log
Ein TestLog File enthält die Ausgabe eines Testlaufs.
Aus RUP
A collection of raw output captured during a unique execution of one or more tests, usually
representing the output resulting from the execution of a Test Suite for a single test cycle
run
2.3.5.6
Erstellen einer Test Suite (vgl. JUnit)
Eine Test Suite dient zur Gruppierung von Testfällen.
(Bsp. Vgl Test Tool JUnit weiter unten)
Aus RUP
A package-like artifact used to group collections of Test Scripts, both to sequence the
execution of the tests and to provide a useful and related set of Test Log information from
which Test Results can be determined.
2.3.5.7
Unit Tests mit dem Test-Tool JUnit
2.3.5.7.1
Sinn und Zweck
Ein Testtool automatisiert eine Test Prozedure oder Teile davon. Testtools werden oft
mittels Scripting Language (Test Treiber, Test Skript) entwickelt.
Sie stellen z.B. den Input bereit, führen den Test automatisch durch, zeichnen die
Ergebnisse auf und vergleichen diese mit den erwarteten Ergebnissen.
Beispiele sind JUnit, Cantata, Testat der Fa. IDAS, Siemens ITF, Visual Basic Test Script.)
JUnit ist ein kleines Java-Framework zum Schreiben und Ausführen automatischer Unit
Tests. Die Software ist frei und im Kern von Kent Beck und Erich Gamma geschrieben. Die
Tests werden direkt in Java kodiert, sind selbstüberprüfend und damit wiederholbar.
Testfälle können mit JUnit einfach organisiert und über eine Bedienungsoberfläche
ausgeführt werden.
Der Framework-Kern besteht aus neun Klassen und circa tausend Zeilen Code.
2.3.5.7.2
Download und Installation (Selbststudium)
JUnit ist Open Source Software unter IBM Common Public License. Die aktuelle Version
kann von SourceForge heruntergeladen werden : http://sourceforge.net/projects/junit/
171
172
Die offizielle Homepage von JUnit ist erreichbar unter: http://www.junit.org
Entsprechende Frameworks sind auch für alle anderen heute gängigen
Programmiersprachen frei erhältlich: http://www.xprogramming.com/software.htm
Das JUnit-Framework ist in einem JAR-Archiv namens junit.jar verpackt. Die vollständige
Distribution besteht gegenwärtig aus einem ZIP-Archiv, in dem neben junit.jar auch seine
Quellen (in src.jar), seine Tests, einige Beispiele, die Javadoc-Dokumentation, die FAQs,
ein Kochbuch sowie zwei Artikel aus dem amerikanischen Java Report beiliegen.
Zur Installation muss das ZIP-Archiv entpackt und junit.jar in den CLASSPATH
übernommen werden.
Hilfestellung gibt es ggf. im Archiv der JUnit-Yahoogroup :
http://groups.yahoo.com/group/junit/
2.3.5.7.3
Ausführung der Tests mittels TestRunner
Zur Ausführung der Tests bietet JUnit drei verschiedene Gesichter
:
junit.swingui.TestRunner mit grafischer Swing-Oberfläche
junit.awtui.TestRunner auf einfacher Basis des Java AWT
junit.textui.TestRunner als Batch-Job auf der Text-Konsole
Zur Ausführung kann man Testfallklassen und Testsuiteklassen mit vollklassifiziertem
Namen eingeben und unter Swing auch aus dem Klassenpfad oder der Historie auswählen.
Im mittleren Fenster werden die fehlschlagenden Tests aufgelistet, die man nach Mausklick
im unteren Fenster mit Fehlermeldung und Stacktrace betrachten kann.
Unter Swing kann man in dem mittleren Fenster zusätzlich den Testbaum öffnen und dann
Testfälle und Testsuiten daraus auch einzeln ausführen.
172
173
Die rein textuelle Variante bietet eine Alternative zur grafischen Oberfläche, die besonders
für den Testlauf zum Integrationszeitpunkt oder im nächtlichen Buildprozess geeignet ist.
Die GUI-Oberflächen haben über der textuellen Darstellung den Vorteil, dass sie
modifizierte Klassen automatisch neu laden können.
2.3.5.7.4
Wichtige Klassen des Frameworks
// Einfügen
Assert bietet Methoden zum Prüfen von Werten und Bedingungen.
Eine Instanz der Klasse TestCase repräsentiert einen abstrakten Testfall
Eine Instanz der Klasse EuroTest repräsentiert einen spezifischen Testfall
Die Klasse EuroTest sammelt darüber hinaus alle Methoden, die ein bestimmtes
Testobjekt, hier eine Instanz der Klasse Euro, testen sollen
Die Klasse Euro repräsentiert das Testobjekt
2.3.5.7.5
SingleTestCase
2.3.5.7.5.1
Beispiel EuroTestSingleTestCase
2.3.5.7.5.1.1
Requirements
Die Klasse Euro repräsentiert einen Geldbetrag.
Ein Euro Objekt wird eindeutig durch seinen Wert beschrieben.
Die Requirements für die Klasse können grob durch die Signatur der public Methoden
beschrieben werden.
public class Euro {
public
public
public
public
double getAmount(){};
Euro add(Euro other) {};
String toString(){};
boolean equals(Object o) {};
}
Ableitung der Testfälle aus den Requirements :
Die Testfälle werden auf Basis der Requirements vor dem Code geschrieben.
2.3.5.7.5.1.2
Schreiben des Testfalles in Form der Testmethode
public class EuroTest extends TestCase {
public void testAmount() {
Euro two = new Euro(2.00);
assertTrue(2.00==two.getAmount());
}
public static void main(String[] args) {
junit.swingui.TestRunner.run(EuroTest.class);
}
}
Die Tests werden getrennt von der Klasse Euro in einer Klasse namens EuroTest definiert.
173
174
Um die Testklasse in JUnit einzubinden, wird sie von dessen Framework-Basisklasse
junit.framework.TestCase abgeleitet.
Der eigentliche Testfall verbirgt sich hinter der Methode testAmount.
Das Framework erkennt diese Methode automatisch als Testfall, weil sie der Konvention
des Signaturmusters public void test...() folgt.
Der Test erfolgt mit dem Aufruf der assertTrue Methode, die die Testklasse aus ihrer
Oberklasse erbt.
Das assertTrue Statement formuliert eine Annahme oder Forderung, die für den Code gilt
und die JUnit automatisch verifiziert.
Durch Ausführung der main Routine wird der JUnit-Test ausgeführt. Der
junit.swingui.TestRunner stellt eine grafische Oberfläche auf Basis von Java Swing dar, um
Unit Tests kontrolliert ablaufen zu lassen.
2.3.5.7.5.1.3
Schreiben der zu testenden Methode
public class Euro {
private long cents;
public Euro(double euro) {
this.cents=Math.round(euro*100.0);
}
public Euro(long cents) {
this.cents=cents;
}
public double getAmount() {
return cents/100.0;
}
}
2.3.5.7.5.2
Assert
JUnit erlaubt, Werte und Bedingungen zu testen, die jeweils erfüllt sein müssen, damit der
Test erfolgreich durchläuft.
Die Klasse Assert definiert dazu eine Familie spezialisierter assert Methoden :
assertTrue(boolean condition) ist die allgemeinste Zusicherung. Sie verifiziert, ob eine
logische Bedingung wahr ist:
assertTrue(theJungleBook.isChildrensMovie());
assertTrue(40 == xpProgrammer.workingHours());
Der Test ist erfolgreich, wenn die Bedingung erfüllt ist, das heißt der Ausdruck zu true
ausgewertet werden konnte. Ist der Ausdruck dagegen false, protokolliert JUnit einen
Fehlschlag.
assertEquals(Object expected, Object actual) verifiziert, ob zwei Objekte gleich sind. Der
Vergleich der Objekte erfolgt in JUnit über die equals Methode.
assertEquals("foobar", "foo" + "bar");
assertEquals(new Euro(2.00), movie.getCharge(1));
Der Vorteil dieser und der noch folgenden assertEquals Varianten gegenüber dem Test mit
assertTrue liegt darin, dass JUnit nützliche zusätzliche Informationen bieten kann, wenn der
Test tatsächlich fehlschlägt. JUnit benutzt in diesem Fall die toString Repräsentation des
Objekts, um den erwarteten Wert auszugeben.
174
175
assertEquals(int expected, int actual) verifiziert, ob zwei ganze Zahlen gleich sind. Der
Vergleich erfolgt für die primitiven Java-Typen über den == Operator.
assertEquals(40, xpProgrammer.workingHours());
assertEquals(double expected, double actual, double delta) verifiziert, ob zwei
Fließkommazahlen gleich sind. Da Fließkommazahlen nicht mit unendlicher Genauigkeit
verglichen werden können, wird zusätzlich eine Toleranz erwartet:
assertEquals(3.1415, Math.pi(), 1e-4);
assertNull(Object object) verifiziert, ob eine Referenz null ist:
assertNull(hashMap.get(key));
assertNotNull(Object object) verifiziert, ob eine Objektreferenz
nicht null ist:
assertNotNull(httpRequest.getParameter("action"));
assertSame(Object expected, Object actual) verifiziert, ob zwei Referenzen auf das selbe
Objekt verweisen:
assertSame(crouchingTigerHiddenDragon, movies.get(0));
Die assertEquals Methode ist neben den genannten Argumenttypen auch für die primitiven
Datentypen float, long, boolean, byte, char und short überladen.
assertFalse(boolean condition) verifiziert, ob eine Bedingung falsch ist.
assertFalse(pulpFiction.isChildrensMovie());
Codebeispiel aus der Klasse Assert
static public void assertEquals(String message, Object expected, Object actual) {
if (expected == null && actual == null)
return;
if (expected != null && expected.equals(actual))
return;
failNotEquals(message, expected, actual);
}
static private void failNotEquals(String message, Object expected, Object actual) {
fail(format(message, expected, actual));
}
static public void fail(String message) {
throw new AssertionFailedError(message);
}
2.3.5.7.5.3
AssertionFailedError
Was passiert, wenn ein Test fehlschlägt?
Die im Testcode durch assert Anweisungen kodierten Behauptungen werden von der
Klasse Assert automatisch verifiziert. Im Fehlerfall bricht JUnit den laufenden Testfall sofort
ab und wirft eine Ausnahme AssertionFailedError mit entsprechendem Fehlerprotokoll aus.
Wenn die Fehlermeldung nicht ausreichen sollte, bietet JUnit für alle assert Methoden an,
einen Erklärungstext zu tippen, der im Fehlerfall mitprotokolliert wird.
assertTrue("Beträge stimmen nicht überein !", 1.00==two.getAmount());
175
176
2.3.5.7.6
MultipleTestCases
2.3.5.7.6.1
Beispiel EuroTestMultipleTestCases
public class EuroTest extends TestCase {
private Euro two;
protected void setUp() {
two= new Euro(2.00);
}
protected void tearDown() {
two= null;
}
public void testAmount() {
assertTrue(2.00==two.getAmount());
}
public void testRounding() {
Euro rounded = new Euro(1.995);
assertTrue("amount not rounded",2.00==rounded.getAmount());
assertEquals("rounding",2.00,rounded.getAmount(),0.001);
assertEquals("rounding",two,rounded);
}
public void testAdding() {
Euro sum = two.add(two);
assertEquals("sum",4.00,sum.getAmount(),0.01);
assertEquals("two",2.00,two.getAmount(),0.01);
assertEquals("sum",new Euro(4.00),sum);
assertEquals("two",new Euro(2.00),two);
}
public void testEqualty() {
assertEquals(two,two);
assertEquals(two,new Euro(2.00));
assertEquals(new Euro(2.00),two);
assertFalse(two.equals(new Euro(7.00)));
assertFalse(two.equals(null));
assertFalse(two.equals(new Object()));
}
public static void main(String[] args) {
junit.swingui.TestRunner.run(EuroTest.class);
}
}
2.3.5.7.6.2
Definition von mehreren Testfällen in der Klasse EuroTest
In der Regel beziehen sich mehrere Testfälle auf ein und dasselbe zu testende Objekt bzw.
ein und dieselbe Menge von Testobjekten.
Diese Menge von Testobjekten wird auch als Test-Fixture bezeichnet.
176
177
Diese Test-Fixture muss für jeden Testfall zunächst konstruiert und initialisiert werden.
Testfälle, die sich auf die gleiche Test-Fixture beziehen, fasst man unter dem Dach der
Testklasse EuroTest zusammen.
(Alle Testfälle einer Testklasse sollten also von der gemeinsamen Fixture Gebrauch
machen. Hat eine Testfallmethode keine Verwendung für die Fixture-Objekte, so ist dies
meist ein guter Indiz dafür, dass die Methode auf eine andere Testklasse verschoben
werden sollte.
Testklassen sollten also um die Fixture organisiert werden, nicht um die getestete Klasse.
Somit kann es auch durchaus vorkommen, dass zu einer Klasse mehrere
korrespondierende Testfallklassen existieren, von denen jede ihre individuelle Fixture
besitzt.)
Damit fehlerhafte Testfälle nicht andere Testfälle beeinflussen können, läuft jeder Testfall
im Kontext seiner eigenen Fixture. D.h. für jeden Testfall wird vor der Durchführung das zu
testende Objekt neu initialisiert.
Die Testobjekte werden als Instanzvariable der Testklasse definiert.
Für Initialisierungs- bzw. Aufräumarbeiten kann man die Einschubmethoden des
Frameworks setUp() bzw. tearDown() überschreiben.
setUp() wird gerufen, bevor ein Testfall ausgeführt wird.
tearDown() wird gerufen, nachdem ein Testfall ausgeführt wurde.
Achtung :
Die Fixture-Variablen werden im setUp() initialisiert, nicht etwa im Deklarationsteil oder im
Konstruktor der Testfallklasse.
2.3.5.7.6.3
Generierung von EuroTest Instanzen für jeden Testfall
Was passiert, wenn JUnit die Tests einer Klasse ausführt?
JUnit unterscheidet im Prinzip zwischen zwei verschiedenen Phasen:
Testfallerzeugung
Das Test-Framework durchsucht die Testklasse EuroTest nach public Methoden mit der
Signatur void test...(). Für jede gefundene test...() Methode erzeugt es jeweils ein eigenes
Objekt der Klasse EuroTest und legt in diesem den Namen der test...() Methode ab.
Die Testfallklasse EuroTest resultiert in so vielen Testfallobjekten, wie sie Testfallmethoden
enthält. Auf diesen Objekten erfolgt dann der Testlauf:
Testlauf
Das Test-Framework durchläuft die Liste der EuroTest Objekte und ruft jeweils die darin
gemerkte test...() Methode auf.
Der Aufruf der test...() Methode wird durch den Aufruf vob setUp() und tearDown()
gesandwiched.
Genauer :
Vor der Ausführung eines Testfalls wird jeweils erst die setUp Methode ausgeführt, sofern
diese redefiniert wurde.
Anschließend wird die den auszuführenden Testfall betreffende test... Methode gerufen.
Abschließend wird noch die tearDown Methode ausgeführt, falls diese in der Testklasse
redefiniert wurde.
JUnit führt die gesammelten Testfälle also voneinander isoliert im Rahmen eigener
Testobjekte aus, damit zwischen den einzelnen Testfällen keinerlei Seiteneffekte entstehen.
Die Reihenfolge, in der Testfälle vom Framework ausgeführt werden, ist dabei prinzipiell
undefiniert.
Für JUnit sind die Testfälle vollkommen unabhängig voneinander: Sie besitzen keinen
inhärenten Zustand, bauen sich selbst ihre Testumgebung auf, räumen hinter sich selbst
auf und können in beliebiger Reihenfolge laufen.
177
178
Es dürfen keine Annahmen über die Reihenfolge im Testlauf getroffen werden. Voneinander
abhängige Tests müssen stattdessen gemeinsam in einem Testfall ausgeführt werden.
Darstellung des Sachverhaltes im Sequenzdiagramm
// Einfügen
2.3.5.7.7
TestCaseTree
2.3.5.7.7.1
Beispiel AllTests
public class AllTests {
public static Test suite() {
TestSuite suite=new TestSuite();
suite.addTestSuite(EuroTest.class);
suite.addTestSuite(NameTest.class);
return suite;
}
public static void main(String[] args) {
junit.swingui.TestRunner.run(AllTests.class);
}
}
2.3.5.7.7.2
TestSuite
Wie führt man eine Reihe von Tests zusammen aus?
Mit JUnit kann man beliebig viele Tests in einer Testsuite zusammenfassen und
gemeinsam ausführen.
Um eine Testsuite zu erhalten, erzeugt man ein TestSuite Exemplar und fügt diesem mittels
der addTestSuite() Methode die gewünschten Testfallklassen hinzu.
public void addTestSuite(Class testClass) {
addTest(new TestSuite(testClass));
}
Der Code der addTestSuite() Methode lässt erkennen, dass für jede Testfallklasse implizit
eine Suite erzeugt wird.
Der Konstruktor von TestSuite generiert dann für jede test...() Methode eine Instanz der
TestCase Klasse.
Das Interface Test wird sowohl von TestCase als auch von TestSuite implementiert. Diese
Technik erlaubt es, beliebig viele TestCase und TestSuite Objekte zu einer umfassenden
Testsuite-Hierarchie, also einem TestCaseTree, zu kombinieren.
// Einfügen
Test abstrahiert von Testfällen und Testsuiten.
TestSuite führt als Knoten des TestCaseTrees eine Reihe von Tests zusammen aus.
TestCase repräsentiert als Leave einen ausführbaren Testfall
Nach diesem Muster kann man dann auch Hierarchien von Testsuiten bilden:
suite.addTest(database.AllTests.suite());
Die main Methode verschiebt sich von der EuroTest Testfallklasse in die AllTests
Testsuiteklasse.
2.3.5.7.8
Exceptions
178
179
2.3.5.7.8.1
Testen von Exceptions
Wie testen wir, ob eine Exception wie erwartet geworfen wird?
//
public Euro(double euro) {
assert euro >=0.0 : "Negative amount"; //erst ab SDK 1.4
if (euro < 0.0)
{
throw (new IllegalArgumentException());
}
this.cents=Math.round(euro*100.0);
}
public void testNegativeAmount() {
try {
final double NEGATIVE_AMOUNT=-2.00;
new Euro(NEGATIVE_AMOUNT);
fail("Should have raised an AssertionError");
}
catch (IllegalArgumentException expected) {
}
}
Wenn der Konstruktor den Fehler auslöst, kommt der catch Block zum Tragen.
Der erwartete Fehler wird gefangen und alles ist gut. Löst der Konstruktor den Fehler
dagegen nicht aus, läuft der Test zu weit, die fail Anweisung wird ausgeführt und JUnit
protokolliert die Fehlermeldung.
Wichtig: Um unter Java ab 1.4 asserts zu benutzen ist folgendes notwendig:
Kompilieren: explizit dem Compiler per „Schalter“ zu Codegenerierung für Java 1.4 aufwärts
zwingen: -source 1.4
Ausführen: hier ist auch ein Schalter zum Aktivieren der „asserts“ nötig:
-ea um Asserts für die eigenen Klassen zu nutzen
bzw. –esa um auch für die Javasystemklassen Asserts zu aktivieren.
2.3.5.7.8.2
Unerwartete Exceptions
JUnit unterscheidet in seinem TestRunner zwischen einem »Failure« und einem »Error«:
Ein Failure kennzeichnet eine fehlschlagende Zusicherung.
Ein Error kennzeichnet ein unerwartetes Problem wie zum Beispiel eine
NullPointerException, die im Test auch nicht behandelt werden soll.
Die Ausnahmebehandlung von JUnit arbeitet wie folgt:
Tritt im setUp ein Fehler auf, wird sowohl der Testfall als auch tearDown nicht mehr
ausgeführt.
Tritt in der test... Methode ein Fehler auf, wird trotzdem noch tearDown ausgeführt.
Tritt im tearDown ein Fehler auf, gibt es keine weitere Rettung.
2.3.5.7.9
Implementationsdetails des Frameworks
Aus Debug Session
179
180
AllTests
public static void main(String[] args) {
junit.swingui.TestRunner.run(AllTests.class);
}
TestRunner
public static void run(Class test) {
String args[]= { test.getName() };
main(args);
}
public static void main(String[] args) {
new TestRunner().start(args);
}
public void start(String[] args) {
String suiteName= processArguments(args); // Im Beispiel “AllTests”
fFrame= createUI(suiteName);
fFrame.pack();
fFrame.setVisible(true);
if (suiteName != null) {
setSuite(suiteName);
runSuite();
}
}
synchronized public void runSuite() {
if (fRunner != null) {
fTestResult.stop();
} else {
setLoading(shouldReload());
reset();
showInfo("Load Test Case...");
final String suiteName= getSuiteText();
final Test testSuite= getTest(suiteName);
if (testSuite != null) {
addToHistory(suiteName);
doRunTest(testSuite);
}
}
}
suiteName ist “AllTests“
In getTest() wird über Reflection API gerufen
AllTests
public static Test suite() {
TestSuite suite=new TestSuite();
suite.addTestSuite(EuroTest.class);
suite.addTestSuite(NameTest.class);
return suite;
}
Hier wird der Test-Tree aufgebaut
180
181
Aus addTestSuite() wird gerufen
TestSuite
public void addTest(Test test) {
fTests.addElement(test);
}
fTests ist ein einfacher Vector
Die Wurzel des Test Trees ist testSuite
Die Analyse einer Leave TestCase Klasse erfolgt im Konstruktor der TestSuite Klasse :
TestSuite
public TestSuite(final Class theClass) {
fName= theClass.getName();
try {
getTestConstructor(theClass); // Avoid generating multiple error messages
} catch (NoSuchMethodException e) {
addTest(warning("Class "+theClass.getName()+" has no public constructor
TestCase(String name) or TestCase()"));
return;
}
if (!Modifier.isPublic(theClass.getModifiers())) {
addTest(warning("Class "+theClass.getName()+" is not public"));
return;
}
Class superClass= theClass;
Vector names= new Vector();
while (Test.class.isAssignableFrom(superClass)) {
Method[] methods= superClass.getDeclaredMethods();
for (int i= 0; i < methods.length; i++) {
addTestMethod(methods[i], names, theClass);
}
superClass= superClass.getSuperclass();
}
if (fTests.size() == 0)
addTest(warning("No tests found in "+theClass.getName()));
}
Mittels Reflection API werden zunächst die test...() Methoden der TestCase Klasse gesammelt
private void addTestMethod(Method m, Vector names, Class theClass) {
String name= m.getName();
if (names.contains(name))
return;
if (! isPublicTestMethod(m)) {
if (isTestMethod(m))
addTest(warning("Test method isn't public: "+m.getName()));
return;
}
names.addElement(name);
addTest(createTest(theClass, name));
}
Für jede test..() Methode wird eine Instanz der Klasse TestCase erzeugt
Nach dem Aufbau des Trees wird er ausgeführt
181
182
TestRunner
private void doRunTest(final Test testSuite) {
setButtonLabel(fRun, "Stop");
fRunner= new Thread("TestRunner-Thread") {
public void run() {
TestRunner.this.start(testSuite);
postInfo("Running...");
long startTime= System.currentTimeMillis();
testSuite.run(fTestResult);
if (fTestResult.shouldStop()) {
postStatus("Stopped");
} else {
long endTime= System.currentTimeMillis();
long runTime= endTime-startTime;
postInfo("Finished: " + elapsedTimeAsString(runTime) + "
seconds");
}
runFinished(testSuite);
setButtonLabel(fRun, "Run");
fRunner= null;
System.gc();
}
};
// make sure that the test result is created before we start the
// test runner thread so that listeners can register for it.
fTestResult= createTestResult();
fTestResult.addListener(TestRunner.this);
aboutToStart(testSuite);
fRunner.start();
}
Vor dem Start wird der Test Tree in aboutToStart() grafisch dargestellt
Der Testlauf erfolgt im Context eines eigenen Threads
Die run() Funktion dieses Threads ruft nach einigen Vorbereitungen (z.B. Progressbalken)
testSuite.run()
TestSuite
public void run(TestResult result) {
for (Enumeration e= tests(); e.hasMoreElements(); ) {
if (result.shouldStop() )
break;
Test test= (Test)e.nextElement();
runTest(test, result);
}
}
Diese Funktion ruft runTest() für alle Tests der Suite
public void runTest(Test test, TestResult result) {
test.run(result);
}
Diese Funktion ruft wiederum die run() Funktion der Tests
TestCase
182
183
public void run(TestResult result) {
result.run(this);
}
Im Falle eines Leaves wird schließlich result.run() gerufen
TestResult
protected void run(final TestCase test) {
startTest(test);
Protectable p= new Protectable() {
public void protect() throws Throwable {
test.runBare();
}
};
runProtected(test, p);
endTest(test);
}
TestCase
public void runBare() throws Throwable {
setUp();
try {
runTest();
}
finally {
tearDown();
}
}
protected void runTest() throws Throwable {
assertNotNull(fName);
Method runMethod= null;
try {
// use getMethod to get all public inherited
// methods. getDeclaredMethods returns all
// methods of this class but excludes the
// inherited ones.
runMethod= getClass().getMethod(fName, null);
} catch (NoSuchMethodException e) {
fail("Method \""+fName+"\" not found");
}
if (!Modifier.isPublic(runMethod.getModifiers())) {
fail("Method \""+fName+"\" should be public");
}
try {
runMethod.invoke(this, new Class[0]);
}
catch (InvocationTargetException e) {
e.fillInStackTrace();
throw e.getTargetException();
}
catch (IllegalAccessException e) {
e.fillInStackTrace();
throw e;
}
}
183
184
TestCase enthält den Namen der auszuführenden Funktion in der String Variablen fName (Jede
Funktion wird ja im Context eines anderen TestCase Objektes ausgeführt!)
Diese Funktion holt über Reflection API die gewünschte Funktion fName und führt sie aus.
2.3.6
Configuration&Change Management
2.3.6.1
Beispiel für Configuration Management Guidelines
2.3.6.1.1
Software-Element
Ein Softwareelement ist jeder identifizierbare und maschinenlesbare Bestandteil des
entstehenden Produktes.
Ein Software-Element bildet eine atomare Einheit.
Ein Atom enthält keine Untereinheiten, die unabhängig voneinander variieren.
2.3.6.1.2
Version
Eine Version kennzeichnet die Ausprägung eines Software-Elementes zu einem
bestimmten Zeitpunkt. Unter Versionen werden zeitlich nacheinander liegende
Ausprägungen eines Software-Elementes verstanden. Eine Version wird in der Regel durch
eine Nummer beschrieben.
CVS
Beim ersten Einchecken wird der Datei die Nummer 1.1 zugewiesen. Werden nun
Modifikationen eingecheckt, so wird dieser Zähler an der zweiten Stelle erhöht, somit wird
aus 1.1 die 1.2 usw. Diesen internen Zähler nennt CVS Revision, während man
umgangssprachlich den Begriff Version verwendet.
Datei D
1.1
1.2
1.3
1.4
Bild Perlenschnur
2.3.6.1.3
Checkin-Checkout Modell
Der Reservierungsmechanismus sorgt dafür, das sich Änderungen nicht gegenseitig
überschreiben.
Eine Checkout Operation holt eine Kopie eines Software-Elementes aus einem Repository
und reserviert es für den Ausbucher.
184
185
Nach der Überarbeitung befördert die Checkin Operation das Element in das Repository
und löscht die Reservierung. Die Operation vermerkt zusätzlich den Autor, die
Einbuchungszeit, usw.
Ein eingebuchtes Element ist „eingefroren“. Jeder Checkin-Checkout Zyklus erzeugt ein
neues Element.
2.3.6.1.4
Konfiguration (Lineup) und Release (Baseline)
Eine Menge von Software-Elementen, die zusammen ein Produkt oder ein
Zwischenergebnis darstellen, bezeichnet man als Konfiguration.
Konfigurations repräsentieren signifikante Kombinationen von files.
Eine Release ist eine konsistente Konfiguration des Systems.
Sie bezeichnet eine benannte und formal freigegebene Menge von Software Elementen, die
zu einem bestimmten Zeitpunkt in ihren Schnittstellen aufeinander abgestimmt sind und
gemeinsam eine vorgesehene Aufgabe erfüllen. Sie ist getestet und archiviert. Sie ist
Ausgangspunkt für weitere Systemerweiterungen.
// Einfügen
Bild Release
Eine Konfiguration wird als Release markiert, indem man der Konfiguration einen
symbolischen Namen z.B. „Release 1.1“ oder „Release Automat mit Flaschenrückgabe“
zuweist. Man labelt alle zur Konfiguration gehörenden Dokumente.
Dadurch lässt sich auch nach einem längeren Zeitraum diese Release z.B. für eine
Fehlerbeseitigung auschecken.
2.3.6.1.5
Configuration Specification (Konfigurations-Identifikationsdokument KID)
Im KID wird für eine Konfiguration aufgeführt, welche Software-Elemente in welcher Version
zu ihr gehören.
In der Regel werden auch verwendete Tools aufgeführt.
Bei Verwendung eines Tools mit Label-Möglichkeit erübrigt sich dieses Dokument.
2.3.6.1.6
Single Stream Versioning
„latest is greatest“
Jeder Entwickler verwendet die jüngste Version der files.
Zum Editieren eines files wird die jüngste Version ausgecheckt, die Änderung
vorgenommen und das file wieder eingecheckt. Die Änderung ist sofort für alle Entwickler
sichtbar.
Vorteil
Einfache Vorgehensweise
Nachteil
Die Änderung eines Files zwingt in der Regel viele Entwickler zur Änderung der eigenen
Files.
2.3.6.1.7
Varianten von Versionen
Unter Varianten werden zeitlich nebeneinander liegende Ausprägungen eines SoftwareElementes verstanden.
185
186
2.3.6.1.8
Branches
Verschiedene Versionen eines Softwareelementes führen zu einem sequentiellen
Versionsstamm. Dieser wird als main trunk bezeichnet. Eine Verzweigung dieses Stammes
wird als branch bezeichnet.
Z.B. soll ein vom Kunden gemeldeter Fehler in einem eigenen Branch behoben werden.
// Einfügen
Bild Branch
Die Dateien des neuen Branches werden ebenfalls in Versionen (CVS : Revisionen)
verwaltet, wobei sich jedoch die Numerierung ändert.
Varianten von Versionen kann man folgendermaßen kennzeichnen :
Variantennummer = release.level.branch.sequence
CVS übernimmt die Revisions Nummer der jeweiligen Datei und fügt eine weiter Stelle an,
wobei dieser die nächste freie gerade Zahl beginnend mit 2 zugewiesen wird. Erst die vierte
Stelle wird bei jeder neue Revision inkrementiert.
Die vierte Ziffer ist zu Beginn mit der 0 besetzt und besagt, dass diese Dateien identisch mit
den vorherigen Revisionen (aus dem main trunk) sind. Erst wenn Änderungen an den
Dateien des Branches vorgenommen und eingecheckt werden, wird die vierte Ziffer
inkrementiert.
Jede Datei die neu in einen Branch eingebunden wird und nicht aus einem anderen Branch
(auch dem main trunk) stammt bekommt zwar die dritte Ziffer von dem aktuellen Branch
(hier die 2) zugewiesen, doch wird bei der vierten Ziffer wieder bei 1 angefangen.
2.3.6.1.9
Parallel Stream Versioning
Parallel Stream Versioning erlaubt jedem file ein branching tree von Versionen.
In der Regel wird ein Zweig als integration branch deklariert. Dieser Zweig wird verwendet,
um alle Änderungen zu sammeln und zu integrieren. Wenn eine configuration des
integration branches gewisse Tests passiert hat, kann er als release (baseline) für die
weitere Entwicklung zur Verfügung gestellt werden.
Entwicklungsaktivitäten erfolgen also nicht direkt im Rahmen des integration branch
sondern in separaten branches.
Für jeden Entwickler existiert ein eigener branch.
Nach Beendigung einer Entwicklungsaktivität werden die Änderungen mit dem integration
branch gemerged.
Branch 2 - Rechner
1.5.4.1
Main Trunk
1.1
Branch 1 - GUI
1.5.2.1
1.5.4.2
1.2
1.3
1.5.2.2
1.5.4.3
1.4
1.5.2.3
1.5
1.5.2.4
1.6
1.7
Symbolischer Name:
Release-1
1.5.2.5
1.5.2.6
Bild Branch and Merge
Vorteile
Kontrollierte Releases (Baselines)
Beide Entwickler setzen daher auf der jüngsten baseline auf. Die Zwischenversionen des
jeweils anderen Entwicklers werden nicht sichtbar, da es sich um andere branches handelt.
186
187
Nachteile
separater Integrationsschritt
2.3.6.1.10
File based System
File based Systeme verlangen, dass der Anwender eine Kopie der benötigten files in einem
lokalen Verzeichnis hat.
z.B.
CVS Control Version System
Microsoft Visual source Save
Clear Case with snapshot Views
SCCS Source Code Control System
2.3.6.1.11
View based System
Die Anwender arbeiten nicht direkt mit dem Inhalt des file systems.
Sie verwenden stattdessen einen privaten Arbeitsbereich, der view genannt wird. Dieser
view ermöglicht den Zugriff auf Versionen einer gewissen Menge von files.
Eine configuration specification steuert die configuration für einen speziellen view. Der
Anwender sieht die spezifizierten Versionen, als wären diese in einem gewöhnlichen file
system abgelegt.
z.B.
Rational Clear Case
2.3.6.1.12
Zustandsübergänge bei der Bearbeitung eines Software-Elementes
Zustände
geplant
in Bearbeitung
QS vorgelegt
akzeptiert
Werden Änderungen an einem akzeptierten Software-Element vorgenommen, dann wird
dessen Zustand von „akzeptiert“ auf „in Bearbeitung“ zurückversetzt. Die Versionsnummer
wird inkrementiert. Nach der Bearbeitung wird es der Qualitätssicherung (QS) vorgelegt.
Gibt die QS das Software-Element frei, so wird es in den Zustand „akzeptiert“ versetzt.
Einen entsprechenden Zyklus durchläuft die gesamte Konfiguration.
Das KID wird kopiert und in den Zustand „in Bearbeitung“ versetzt. Dabei wird die
Versionsnummer erhöht. Wenn alle modifizierten Software-Elemente den Zustand
„akzeptiert“ erreicht haben, wird das KID der QS vorgelegt. Nach der Freigabe durch die QS
erreicht das KID den Zustand „akzeptiert“.
2.3.6.2
Beispiel Configuration Management mit CVS (Tutorial, Selbststudium)
2.3.6.2.1
Struktur des Projektes
In dem folgenden Beispiel wird eine kleine Taschenrechner Applikation erstellt. Das Team
besteht aus einem Projektleiter und 2 Entwickler.
Der Projektleiter erstellt das Grundgerüst und speichert alles in dem CVS Projekt
TaschenRechner.
187
188
Der Projektleiter organisiert das Projekt in Packages. Wobei folgende Struktur verwendet
werden soll:
Die Startklasse (Main Class) soll den Namen TaschenRechner.java tragen und im
Hauptverzeichnis TaschenRechner liegen.
Die GUI Klasse soll den Namen TaschenRechnerGUI.java tragen und im Unterverzeichnis
gui liegen. Sie wird vom Entwickler E1 entwickelt.
Der eigentliche Rechner soll ein Interface mit dem Namen Rechner.java implementieren
und den Namen RechnerImpl.java besitzen. Beide Dateien sollen im Unterverzeichnis
rechner liegen. Sie werden vom Entwickler E2 entwickelt.
2.3.6.2.2
Allgemeine Vorbereitungen
2.3.6.2.2.1
Download von CVS
CVS gibt es für verschiedene Computersysteme. Es wurde ursprünglich für UNIX
entwickelt. Für Windows gibt es eine Version für den reinen Kommandomodus.
Unter http://www.cvshome.org/dev/codewindow.html kann ein ZIP Archiv geladen werden,
das die eigentliche .exe Datei enthält.
Diese .exe wird in ein beliebiges Verzeichnis extrahiert und nach dem Setzen des
Programmpfades in die PATH Umgebungsvariable von Windows kann das Programm
benutzt werden.
Achtung :
Aktuell ist die Version cvs-1-11-2.zip.
Die aktuelle Version wurde nicht getestet. Bitte verwenden Sie die „alte“ Version von der
HomePage Edelmann.
2.3.6.2.2.2
Installation von CVS
Ordner CVS auf Laufwerk C anlegen
Datei cvs.exe in dieses Verzeichnis extrahieren
Setzen des Pfades als Admin, dann ist es auch für alle Nutzer gültig :
Start -> Systemsteuerung -> System -> Umgebung -> Systemvariablen -> PATH Variable
bearbeiten: folgenden Eintrag hinzufügen: ;C:\CVS
Setzen des Pfades als User, dann ist es nur für diesen Nutzer gültig :
Start -> Systemsteuerung -> System -> Umgebung -> Benutzervariablen -> PATH Variable
bearbeiten: folgenden Eintrag hinzufügen: ;C:\CVS
2.3.6.2.2.3
Setzen des Classpath für den Taschenrechner
Damit nachfolgendes Beispiel auch richtig kompiliert und ausgeführt werden kann, muss
CLASSPATH wie folgt gesetzt werden :
Für den Projektleiter werde Laufwerk E angenommen:
set
CLASSPATH=%CLASSPATH%;E:\TaschenRechner;E:\TaschenRechner\gui;E:TaschenRechner\rec
hner
(Das Setzen kann temporär in der Kommandobox erledigt werden)
2.3.6.2.2.4
Anlegen des Projektverzeichnisses
Anlegen des Verzeichnisses auf Laufwerk E :
188
189
md E:\TaschenRechner
Wechseln in dieses Verzeichnis :
cd E:\TaschenRechner
2.3.6.2.2.5
Anlegen des Repository
CVS benötigt für seine Arbeit eine Art Datenbank, in der es die Projekte verwaltet. Diese
Datenbank wird Repository genannt und stellt sich auf der Festplatte als ein Ordner mit
Verwaltungs-, Konfigurationsdateien und Unterverzeichnissen dar.
Der Befehl hierzu lautet allgemein :
cvs –d :local:<Verzeichnis>\<ReposName> init
In unserem Beispiel :
Auf Laufwerk E wechseln :
md CVSRepos
Erstellen des Repository :
cvs –d:local:E:\CVSRepos\newRepos init
2.3.6.2.2.6
Setzen der CVSROOT Variablen
set CVSROOT=:local:E:\CVSRepos\newRepos
2.3.6.2.2.7
Sonderbehandlung von Binärdateien einstellen
Delta Speicherung
Beim allerersten Einchecken des Projektes speichert CVS eine Kopie jeder Datei. Werden
nun Veränderungen an der Datei vorgenommen und diese eingecheckt, so wird nicht wieder
eine Dateikopie angelegt, sondern die Änderung (zusammen mit internen Befehlen) in die
bereits bestehende Datei geschrieben. Der Vorteil liegt in dem eingesparten Speicherplatz
und in dem schnellen Zugriff von CVS auf Änderungen.
Ändern des Verhaltens bei Binärdateien
CVS ist nicht in der Lage, Binärdateien mittels Delta-Speicherung abzulegen. Da jedoch
auch solche Dateien unter Kontrolle von CVS gestellt werden sollen, muss man CVS beim
Einchecken von Binärdateien mitteilen, dass es sich um eine Binärdatei handelt und daher
keine Delta Speicherung verwendet werden soll.
Damit nicht jedes mal beim Einchecken die Option „Binärdatei“ gesetzt werden muss, kann
man Dateien mit bestimmten Namensendungen z.B. *.bin etc. in der CVS Steuerdatei
cvswrappers als Binärdateien eintragen.
Um diese Steuerdatei zu editieren müssen die sogenannten Administrationsdateien
ausgecheckt werden. Dazu erstellt man sich einen Tempordner, wechselt in diesen und ruft
cvs folgendermaßen auf :
cvs –d <Pfad und ReposName> checkout CVSROOT.
Im Beispiel :
189
190
Anlegen des Tempordners:
md E:\t
cd E: \t
CVSROOT auschecken:
cvs checkout CVSROOT
Ändern der Datei cvswrappers (Änderungen sind rot markiert)
# This file affects handling of files based on their names.
#
# The -t/-f options allow one to treat directories of files
# as a single file, or to transform a file in other ways on
# its way in and out of CVS.
#
# The -m option specifies whether CVS attempts to merge files.
#
# The -k option specifies keyword expansion (e.g. -kb for binary).
#
# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)
#
# wildcard
[option value][option value]...
#
# where option is one of
# -f
from cvs filter
value: path to filter
# -t
to cvs filter
value: path to filter
# -m
update methodology
value: MERGE or COPY
# -k
expansion mode
value: b, o, kkv, &c
#
# and value is a single-quote delimited value.
# For example:
*.gif -k 'b'
*.class -k 'b'
*.jpeg -k 'b'
*.bin -k 'b'
*.exe -k 'b'
*.vep -k 'b'
Achtung :
Falls Word verwendet wird, Abspeicherung als Textfile ohne Extension .txt.
Die Einträge sind je nach Bedürfnis anzupassen.
Nach den Änderungen an den Administrationsdateien müssen diese wieder eingecheckt
werden, damit CVS sich „umstellen“ kann. Der allgemeine Befehl zum Einchecken sieht wie
folgt aus:
cvs commit –m <“Kommentar“> <Projektname>.
Im Beispiel :
Einchecken :
cvs commit –m „Bin File Status geändert“
Löschen des Tempordners :
rd G:\t /S /Q. (/S = alle Dateien und Unterverzeichnisse; /Q = ohne Nachfrage)
2.3.6.2.3
Erstellen des Main Trunk durch den Projektleiter
2.3.6.2.3.1
Anlegen des Projektes
190
191
Das Anlegen des Projektes erfolgt mit dem Aufruf :
cvs import –m <“Kommentar“> <Projektname> <StartInitial> start
Durch den Aufruf wird das Projekt mit dem <Projektnamen> in das Repository „importiert“.
Der Kommentar dient zur Charakterisierung des Eincheckvorganges und wird auch bei
jedem weiteren Einchecken angeben (natürlich mit anderem Inhalt). Unter dem StartInitial
ist eine Art Benutzerkennung zu verstehen. Sie kann z.B. aus dem Namenskürzel des
Projektleiters bestehen.
Im Beispiel :
In das Verzeichnis TaschenRechner wechseln
Anlegen des Projektes :
cvs import -m "Start des Projektes mit dem Hauptverzeichnis" TaschenRechner cs start
Löschen des Verzeichnisses :
cd \
rd TaschenRechner /S /Q
Anschließend ist noch das soeben angelegt Projekt auszuchecken, da nicht direkt im
Repository gearbeitet wird, sondern in einer Arbeitskopie :
Auschecken unseres Projektes :
cvs checkout TaschenRechner
2.3.6.2.3.2
Erzeugen des Package rechner
Das Package rechner wird durch Anlegen des gleichnamigen Verzeichnisses innerhalb des
Projektverzeichnisses erreicht:
cd TaschenRechner
md rechner
Nun muss noch dieses unter die Kontrolle von CVS gestellt werden.
Neue Verzeichnisse innerhalb des Projektes werden mittels cvs add <Dateiname oder
Ordnername> aufgenommen.
Im Beispiel :
cvs add rechner
Achtung :
Dateien in diesem neuen Verzeichnis werden nicht automatisch mit unter die Kontrolle von
cvs gestellt !
Die Dateien müssen zusätzlich einzeln aufgenommen werden.
2.3.6.2.3.3
Erzeugen des Interfaces
Nun wird die Datei Rechner.java, die dass Interface darstellt in diesem Verzeichnis erzeugt :
// Rechner.java
package rechner;
public interface Rechner
{
public void setOperand(double operand);
191
192
public void setOperator(char operator);
public double getErgebnis();
}
2.3.6.2.3.4
Erzeugen einer Dummy Implementation des Interfaces
// RechnerImpl.java
package rechner;
public class RechnerImpl implements Rechner
{
public void setOperand(double operand)
{
}
public void setOperator(char operator)
{
}
public double getErgebnis()
{
return 7;
//beliebiger DummyRückgabeWert
}
public RechnerImpl()
{
}
}
2.3.6.2.3.5
Aufnehmen der Dateien in das Projekt
cvs add Rechner.java
cvs add RechnerImpl.java
2.3.6.2.3.6
Einchecken der Dateien
cvs commit –m „Package rechner und Rechner.java und RechnerImpl.java
aufgenommen“
Anmerkung :
Es werden alle Files eingecheckt.
Die Files können auch einzeln eingecheckt werden.
Verzeichnisse werden nicht mittels commit eingeckeckt sondern nur mittels add
hinzugefügt.
2.3.6.2.3.7
Labeln der Konfiguration
Nun wird diesem Projektzustand ein symbolischer Name gegeben, um diesen speziellen
Projektzustand wieder auschecken zu können.
cvs tag PackageRechnerUndInterface
2.3.6.2.3.8
Erzeugen das Package gui und der Datei TaschenRechnerGUI
Das Anlegen und Aufnehmen geschieht analog zu dem obigem Package rechner :
// TaschenRechnerGUI.java
192
193
package gui;
import rechner.Rechner;
import rechner.RechnerImpl;
public class TaschenRechnerGUI
{
private Rechner tr = null;
public TaschenRechnerGUI()
{
tr=new RechnerImpl();
}
}
Aufnehmen vom Projekthauptverzeichnis aus :
md gui
cvs add gui
cvs .\gui\add TaschenRechnerGUI.java
cvs commit –m „Package gui sowie Datei TaschenRechnerGUI aufgenommen“
Vergeben des symbolischen Namens PackageGUIundGUIklasse
cvs tag PackageGUIundGUIklasse
2.3.6.2.3.9
Erzeugen der Main Klasse TaschenRechner.java
Damit das Projekt auch gestartet werden kann, muss noch eine Startklasse mit einer main
Funktion erstellt werden.
Die Datei Rechner.java wird im Projekthauptverzeichnis gespeichert :
//TaschenRechner.java
import gui.TaschenRechnerGUI;
public class TaschenRechner
{
public static void main(String args[])
{
TaschenRechnerGUI rechner=new TaschenRechnerGUI();
}
}
Nun wird auch diese Datei eingecheckt und dem Projekt der symbolische Name
MainTrunkVorBranchBildung vergeben :
cvs add TaschenRechner.java
cvs commit –m „Startklasse Rechner.java erzeugt und aufgenommen“
cvs tag MainTrunkVorBranchBildung
2.3.6.2.3.10
Anlegen der Branches
Der Projektleiter ist nun mit dem Maintrunk fertig und teilt das Projekt den beiden
Entwicklern zu. Damit beide unabhängig und ohne Störungen arbeiten können, werden von
dem Maintrunk 2 Branches angelegt. Branch 1 für das GUI und Branch 2 für den
eigentlichen Rechner. E1 ist für die GUI und E2 für den Rechner zuständig.
Auschecken der letzten lineup (Revision) :
cvs checkout TaschenRechner
In die Arbeitskopie wechseln :
193
194
cd TaschenRechner
Von dieser Arbeitskopie (die auf dem neusten Stand ist) ausgehend den neuen Branch
anlegen:
cvs tag –b Entwickler-1-GUI
Mit diesem Befehl wird im Repository ein neuer Branch unter dem Namen Entickler-1-GUI
angelegt. Die aktuell ausgecheckte Arbeitskopie ist noch nicht aus diesem neuen Branch.
Deshalb kann nun im nächsten Schritt auch gleich der zweite Branch erzeugt werden.
Anlegen von Branch 2: rechner
cvs tag –b Entwickler-2-Rechner
2.3.6.2.3.11
Zugriff auf zentrales Repository ermöglichen
Entwickler 1 und 2 müssen Zugriff auf das zentrale Repository haben. Dies gelingt ggf.
unter Verwendung von Netzlaufwerken.
2.3.6.2.4
Entwickeln der GUI durch Entwickler1
Der Entwickler 1 checkt nun seinen Zweig des Projektes aus und erhält dadurch eine
Arbeitskopie aus seinem Branch.
Hiermit entwickelt er die GUI Klasse TaschenRechnerGUI.java und erstellt zum Testen
auch eine kleine Testversion von TaschenRechnerImpl.java.
Nach der Fertigstellung und dem Test kann der Projektleiter die GUI Klasse wieder in den
Maintrunk überführen.
2.3.6.2.4.1
Auschecken der Arbeitskopie vom Branch Entwickler-1-GUI
Allgemein :
cvs checkout –r <BranchName> <ProjektName>
Im Beispiel :
cvs checkout –r Entwickler-1-GUI TaschenRechner
Die Arbeitskopie ist jetzt noch auf Stand der letzten MainTrunk Version des Projektes.
2.3.6.2.4.2
Codieren der GUI Klasse
In diesem einfachen Besipiel wird davon ausgegangen, dass der Entwickler die GUI Klasse
in einem Stück schreibt und somit auch nur einmal eincheckt. In der Regel wird er seine
Klasse jedoch in mehreren Schritten entwickeln und jede einzelne Version einchecken.
//Source file: P:\\TaschenRechner\\gui\\TaschenRechnerGUI.java
package gui;
import
import
import
import
import
java.awt.*;
java.awt.event.*;
javax.swing.*;
rechner.Rechner;
rechner.RechnerImpl;
194
195
public class TaschenRechnerGUI extends JFrame implements ActionListener
{
private Rechner tr = null;
private
JButton
operatorPlus
=
new
JButton
(new
("/TaschenRechner/gui/OperatorPlus.gif"));
private
JButton
operatorMinus
=
new
JButton
(new
("/TaschenRechner/gui/OperatorMinus.gif"));
private
JButton
operatorMal
=
new
JButton
(new
("/TaschenRechner/gui/OperatorMal.gif"));
private
JButton
operatorDurch
=
new
JButton
(new
("/TaschenRechner/gui/OperatorDurch.gif"));
private
JButton
operatorGleich
=
new
JButton
(new
("/TaschenRechner/gui/OperatorGleich.gif"));
private JTextField ausgabeFenster = new JTextField (20);
private
JButton
operand0
=
new
JButton
(new
("/TaschenRechner/gui/Operand0.gif"));
private
JButton
operand1
=
new
JButton
(new
("/TaschenRechner/gui/Operand1.gif"));
private
JButton
operand2
=
new
JButton
(new
("/TaschenRechner/gui/Operand2.gif"));
private
JButton
operand3
=
new
JButton
(new
("/TaschenRechner/gui/Operand3.gif"));
private
JButton
operand4
=
new
JButton
(new
("/TaschenRechner/gui/Operand4.gif"));
private
JButton
operand5
=
new
JButton
(new
("/TaschenRechner/gui/Operand5.gif"));
private
JButton
operand6
=
new
JButton
(new
("/TaschenRechner/gui/Operand6.gif"));
private
JButton
operand7
=
new
JButton
(new
("/TaschenRechner/gui/Operand7.gif"));
private
JButton
operand8
=
new
JButton
(new
("/TaschenRechner/gui/Operand8.gif"));
private
JButton
operand9
=
new
JButton
(new
("/TaschenRechner/gui/Operand9.gif"));
private
JButton
operandP
=
new
JButton
(new
("/TaschenRechner/gui/OperandPunkt.gif"));
ImageIcon
ImageIcon
ImageIcon
ImageIcon
ImageIcon
ImageIcon
ImageIcon
ImageIcon
ImageIcon
ImageIcon
ImageIcon
ImageIcon
ImageIcon
ImageIcon
ImageIcon
ImageIcon
public TaschenRechnerGUI()
{
super("CVS - Testprojekt: Taschenrechner");
setSize(460,300);
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
tr=new RechnerImpl();
Container content=this.getContentPane();
content.setLayout(new FlowLayout());
operatorPlus.setActionCommand("plus");
operatorPlus.addActionListener(this);
operatorMinus.setActionCommand("minus");
operatorMinus.addActionListener(this);
operatorMal.setActionCommand("mal");
operatorMal.addActionListener(this);
operatorDurch.setActionCommand("durch");
operatorDurch.addActionListener(this);
operatorGleich.setActionCommand("gleich");
operatorGleich.addActionListener(this);
operand0.setActionCommand("0");
operand0.addActionListener(this);
operand1.setActionCommand("1");
operand1.addActionListener(this);
operand2.setActionCommand("2");
operand2.addActionListener(this);
operand3.setActionCommand("3");
operand3.addActionListener(this);
operand4.setActionCommand("4");
operand4.addActionListener(this);
operand5.setActionCommand("5");
operand5.addActionListener(this);
operand6.setActionCommand("6");
operand6.addActionListener(this);
operand7.setActionCommand("7");
195
196
operand7.addActionListener(this);
operand8.setActionCommand("8");
operand8.addActionListener(this);
operand9.setActionCommand("9");
operand9.addActionListener(this);
operandP.setActionCommand(".");
operandP.addActionListener(this);
content.add(operatorPlus);
content.add(operatorMinus);
content.add(operatorMal);
content.add(operatorDurch);
content.add(operatorGleich);
content.add(operand0);
content.add(operand1);
content.add(operand2);
content.add(operand3);
content.add(operand4);
content.add(operand5);
content.add(operand6);
content.add(operand7);
content.add(operand8);
content.add(operand9);
content.add(operandP);
content.add(ausgabeFenster);
this.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent we) {
System.exit(0);
}});
this.setVisible(true);
}
private void setOperand(char operand)
{
String szahl=ausgabeFenster.getText();
ausgabeFenster.setText(szahl+operand);
szahl=ausgabeFenster.getText();
tr.setOperand(Double.valueOf(szahl).doubleValue());
}
private void setOperator(char operator)
{
tr.setOperator(operator);
ausgabeFenster.setText("");
}
private void ausgabeOperator(char operator)
{
try {
tr.setOperator(operator);
//da jetzt berechnet wird, kann Exception
geworfen werden
}
catch (java.lang.ArithmeticException e) {
String message=e.getMessage();
if (message.equals("/ by zero"))
System.out.println("Division durch Null!");
}
double ergebnis=tr.getErgebnis();
ausgabeFenster.setText(String.valueOf(ergebnis));
}
private void punktOperand(char operator)
{
String szahl=ausgabeFenster.getText();
if (szahl.equals("")==true)
ausgabeFenster.setText("0.");
else
ausgabeFenster.setText(szahl+'.');
}
public void actionPerformed(ActionEvent e)
{
String command=e.getActionCommand();
char operand=command.charAt(0);
if (command.equals("plus"))
setOperator('+');
196
197
else
if (command.equals("minus"))
setOperator('-');
else
if (command.equals("mal"))
setOperator('*');
else
if (command.equals("durch"))
setOperator('/');
else
if (command.equals("gleich"))
ausgabeOperator('=');
else
switch(operand) {
case '0':case '1':case '2':case
'5':case '6':case '7':case'8':case'9':
setOperand(operand);
break;
case '.':
punktOperand(operand);
}
}
}
'3':case
'4':case
Diese Datei unterscheidet sich völlig von der Version aus dem MainTrunk.
2.3.6.2.4.3
Einchecken der Arbeitskopie in den Branch Entwickler-1-GUI
cvs commit –m „GUI komplett fertiggestellt!“
cvs tag GetestetetVersion-Release1
2.3.6.2.5
Entwickeln des Rechners durch Entwickler2
2.3.6.2.5.1
Auschecken der Arbeitskopie vom Branch Entwickler-2-Rechner
cvs checkout –r Entwickler-2-Rechner TaschenRechner
2.3.6.2.5.2
Codieren der Rechnerlogik – Datei TaschenRechnerImpl.java
//Source file: P:\\Rechner\\rechner\\RechnerImpl.java
package rechner;
public class RechnerImpl implements Rechner
{
private double ergebnis;
private double operand1 = 0;
private double operand2 = 0;
private char operator;
private boolean operandOneEmpty;
private boolean operatorEmpty;
public void setOperand(double operand)
{
if (operandOneEmpty==true) { //noch keine Zahl eingegeben
operand1=operand;
}
else
//jetzt die 2te Zahl abspeichern
operand2=operand;
}
public void setOperator(char operator)
{
if (operator=='=') {
operandOneEmpty=true;
berechne();
}
else {
this.operator=operator;
operandOneEmpty=false;
197
198
operatorEmpty=false;
}
}
public double getErgebnis()
{
if (operatorEmpty==false) { //nur wenn Rechenoperation gewählt wurde Rechne
operatorEmpty=true;
operatorEmpty=true;
operand1=ergebnis;
//für den Fall, daß das Ergebnis aus einer
vorherigen Rechnung direkt weiter verwendet wird und somit nach dem =OPerator ein
anderer Operator folgt
return ergebnis;
}
else
return operand1;
}
public RechnerImpl()
{
operandOneEmpty=true;
operatorEmpty=true;
}
private void berechne() throws java.lang.ArithmeticException
{
switch (this.operator) {
case '+':
this.ergebnis=operand1+operand2; break;
case '-':
ergebnis=operand1-operand2; break;
case '*':
ergebnis=operand1*operand2; break;
case '/': {
if (operand2==0.0) {
ergebnis=0; //nicht ganz richtig (Divsision durch Null)
throw (new java.lang.ArithmeticException("/ by zero")) ;
}
ergebnis=operand1/operand2; //wenn oben keine Exception->alles
OK->
berechne
}
}
}
}
2.3.6.2.5.3
Codieren der GUI Klasse als Testtreiber
Jetzt wird die TaschenRechnerGUI für einen kleinen Testlauf umgebaut (dies geschieht nur
in diesem Branch und hat somit keinen Einfluss auf die gleichnamige Klasse von dem GUIBranch).
// TaschenRechnerGUI.java -> nur zum Testen der RechnerLogik
package gui;
import rechner.Rechner;
import rechner.RechnerImpl;
public class TaschenRechnerGUI
{
private Rechner tr = null;
public TaschenRechnerGUI()
{
tr=new RechnerImpl();
rechne();
}
private void rechne() {
double erg=0;
//Addition
tr.setOperand(12); tr.setOperator('+'); tr.setOperand(2);
tr.setOperator('='); erg=tr.getErgebnis();
System.out.println("Addition: 12+2 = (Soll: 14) Ist: "+erg);
//Subtraktion
tr.setOperand(0.5); tr.setOperator('-'); tr.setOperand(12.5);
198
199
tr.setOperator('='); erg=tr.getErgebnis();
System.out.println ("Subtraktion: 0.5-12.5 = (Soll: -12) Ist: "+erg);
//Multiplikation
tr.setOperand(24.5); tr.setOperator('*'); tr.setOperand(2);
tr.setOperator('='); erg=tr.getErgebnis();
System.out.println ("Multiplikation: 24.5*2 = (Soll: 49) Ist: "+erg);
//Division
tr.setOperand(27.5); tr.setOperator('/'); tr.setOperand(5.5);
tr.setOperator('='); erg=tr.getErgebnis();
System.out.println ("Division: 27.5/5.5 = (Soll: 5) Ist: "+erg);
//Division durch Null
tr.setOperand(34); tr.setOperator('/'); tr.setOperand(0);
try {
tr.setOperator('=');
erg=tr.getErgebnis();
System.out.println ("Division: 34/0= "+erg);
}
catch (java.lang.ArithmeticException e) {
String message=e.getMessage();
if (message.equals("/ by zero"))
System.out.println
("Division
durch
Null!
"+tr.getErgebnis());
else
System.out.println ("Division: 34/0= "+erg);
}
Division:
34/0=
//Addition+Multiplikation
tr.setOperand(4.5); tr.setOperator('+'); tr.setOperand(5.5);
tr.setOperator('='); erg=tr.getErgebnis();
System.out.println ("Addition+Multiplikation: Teil 1: 4.5+5.5 = (Soll: 10) Ist:
"+erg);
tr.setOperator('*'); tr.setOperand(2);
tr.setOperator('='); erg=tr.getErgebnis();
System.out.println ("Addition+Multiplikation: Teil 2: 10*2 = (Soll: 20) Ist:
"+erg);
}
}
2.3.6.2.5.4
Einchecken der Arbeitskopie in den Branch Entwickler-2-Rechner
Zum Schluss noch einchecken und einen sym. Namen vergeben
cvs commit –m „Rechnerlogik gebaut und getestet“
cvs tag GetestetetRechnerVersion-Release1
2.3.6.2.6
Zusammenführen (Mergen) der Entwicklungsstränge durch den Projektleiter
Ggf. TaschenRechner Verzeichnis löschen :
rd TaschenRechner /s /q
Auschecken des aktuellen Zustandes des Maintrunks :
cvs checkout TaschenRechner
In das Arbeitsverzeichnis wechseln :
cd \TaschenRechner
Package gui in den Maintrunk mergen :
cvs update –j Entwickler-1-GUI gui
Package rechner in den Maintrunk mergen :
cvs update –j Entwickler-2-Rechner rechner
199
200
Einchecken der Änderungen :
cvs commit –m „Die Packages gui und rechner der beiden Branches mit dem Maintrunk
zusammengeführt“
Vergabe des symbolischen Namens :
cvs tag Release-1
2.3.6.2.7
Darstellung der Entwicklungsstränge
CVS bietet die Möglichkeit, sich einen Überblick über den Entwicklungsstand des Projektes
anhand der eingecheckten Dateien und deren Revisionen zu machen. Mit dem Befehl cvs
log, ausgeführt im Projekthauptverzeichnis, wird die Logdatei auf dem Bildschirm
ausgegeben. Damit eine Datei erstellt wird leitet man die Ausgabe einfach in eine Datei um.
Mit cvs log > log.txt wird die Logdatei log.txt im Projekthauptverzeichnis erzeugt.
Die folgenden Beispielausgaben dokumentieren einen etwas komplexeren
Entwicklungsprozess. Der Code wurde in mehreren Entwicklungsstufen erzeugt und
eingecheckt. Dadurch ergeben sich für die einzelnen Dateien höhere Revisionsnummern.
RCS file: p:\CVSRepos\newRepos/TaschenRechner/Rechner.java,v
Working file: Rechner.java
head: 1.1
branch:
locks: strict
access list:
symbolic names:
MainTrunkVorBranchBildung: 1.1
keyword substitution: kv
total revisions: 1;
selected revisions: 1
description:
---------------------------revision 1.1
date: 2002/05/07 19:50:21; author: shikar; state: Exp;
Startklasse Rechner.java erzeugt und aufgenommen
=============================================================================
RCS file: p:\CVSRepos\newRepos/TaschenRechner/Taschenrechner.mdl,v
Working file: Taschenrechner.mdl
head: 1.5
branch:
locks: strict
access list:
symbolic names:
MainTrunkVorBranchBildung: 1.5
PackageGUIundGUIklasse: 1.4
TaschenRechnerImpl: 1.3
PackageRechnerUndInterface: 1.2
UseCaseInit: 1.1
keyword substitution: kv
total revisions: 5;
selected revisions: 5
description:
---------------------------revision 1.5
date: 2002/05/07 19:50:22; author: shikar; state: Exp; lines: +419 -257
Startklasse Rechner.java erzeugt und aufgenommen
---------------------------revision 1.4
date: 2002/05/07 04:47:10; author: shikar; state: Exp; lines: +455 -788
Package gui sowie Datei und UML von TaschenRechnerGUI erzeugt
---------------------------revision 1.3
date: 2002/05/07 04:27:27; author: shikar; state: Exp; lines: +270 -10
Datei TaschenRechnerImpl.java und UML erzeugt
---------------------------revision 1.2
date: 2002/05/07 04:07:45; author: shikar; state: Exp; lines: +243 -3
Package rechner und TaschenRechner.java aufgenommen
---------------------------revision 1.1
date: 2002/05/07 04:02:09; author: shikar; state: Exp;
UseCase Diagramme erzeugt und aufgenommen
=============================================================================
200
201
RCS file: p:\CVSRepos\newRepos/TaschenRechner/gui/TaschenRechnerGUI.java,v
Working file: gui/TaschenRechnerGUI.java
head: 1.2
branch:
locks: strict
access list:
symbolic names:
MainTrunkVorBranchBildung: 1.2
PackageGUIundGUIklasse: 1.1
keyword substitution: kv
total revisions: 2;
selected revisions: 2
description:
---------------------------revision 1.2
date: 2002/05/07 19:50:22; author: shikar; state: Exp; lines: +3 -3
Startklasse Rechner.java erzeugt und aufgenommen
---------------------------revision 1.1
date: 2002/05/07 04:47:10; author: shikar; state: Exp;
Package gui sowie Datei und UML von TaschenRechnerGUI erzeugt
=============================================================================
RCS file: p:\CVSRepos\newRepos/TaschenRechner/rechner/TaschenRechner.java,v
Working file: rechner/TaschenRechner.java
head: 1.1
branch:
locks: strict
access list:
symbolic names:
MainTrunkVorBranchBildung: 1.1
PackageGUIundGUIklasse: 1.1
TaschenRechnerImpl: 1.1
PackageRechnerUndInterface: 1.1
keyword substitution: kv
total revisions: 1;
selected revisions: 1
description:
---------------------------revision 1.1
date: 2002/05/07 04:07:46; author: shikar; state: Exp;
Package rechner und TaschenRechner.java aufgenommen
=============================================================================
RCS file: p:\CVSRepos\newRepos/TaschenRechner/rechner/TaschenRechnerImpl.java,v
Working file: rechner/TaschenRechnerImpl.java
head: 1.2
branch:
locks: strict
access list:
symbolic names:
MainTrunkVorBranchBildung: 1.2
PackageGUIundGUIklasse: 1.2
TaschenRechnerImpl: 1.1
keyword substitution: kv
total revisions: 2;
selected revisions: 2
description:
---------------------------revision 1.2
date: 2002/05/07 04:47:10; author: shikar; state: Exp; lines: +7 -5
Package gui sowie Datei und UML von TaschenRechnerGUI erzeugt
---------------------------revision 1.1
date: 2002/05/07 04:27:28; author: shikar; state: Exp;
Datei TaschenRechnerImpl.java und UML erzeugt
Was sagt diese Datei nun aus?
Es werden alle Dateien des Projektes mit den Erstellungs- und Änderungsinformationen,
sowie den symbolischen Namen und der zugehörigen Revisionsnummer aufgelistet.
Im Einzelnen betrachtet:



Rechner.java:
Aktuelle Revsion: 1.1
Anzahl der gesamten Revisionen: 1
Revision: 1.1 Erstellungsdatum: 2002/05/07 (eng. Darstellung ) um 19:50:21
o Zugehöriger Info Text: Startklasse Rechner.java erzeugt und aufgenommen
201
202
o
Vorhanden in der Projektversion mit dem symbolischen Namen
MainTrunkVorBranchBildung



TaschenRechnerGUI.java
Aktuelle Revsion: 1.2
Anzahl der gesamten Revisionen: 2
Revision: 1.1 Erstellungsdatum: 2002/05/07 (eng. Darstellung ) um 04:47:10
o Zugehöriger Info Text: Package gui sowie Datei und UML von TaschenRechnerGUI
erzeugt
o
Vorhanden in der Projektversion mit dem symbolischen Namen
PackageGUIundGUIklasse

Revision: 1.2 Erstellungsdatum: 2002/05/07 (eng. Darstellung ) um 19:50:22 Zugehöriger
Info Text: Startklasse Rechner.java erzeugt und aufgenommen
o Vorhanden in der Projektversion mit dem symbolischen Namen
MainTrunkVorBranchBildung



TaschenRechner.java
Aktuelle Revsion: 1.1
Anzahl der gesamten Revisionen: 1
Revision: 1.1 Erstellungsdatum: 2002/05/07 (eng. Darstellung ) um 04:07:46
o Zugehöriger Info Text: Package rechner und TaschenRechner.java aufgenommen
Vorhanden in der Projektversion mit dem symbolischen Namen
MainTrunkVorBranchBildung; PackageGUIundGUIklasse; TaschenRechnerImpl;;
PackageRechnerUndInterface




TaschenRechnerImpl.java
Aktuelle Revsion: 1.2
Anzahl der gesamten Revisionen: 2
Revision: 1.1 Erstellungsdatum: 2002/05/07 (eng. Darstellung ) um 04:27:28
o Zugehöriger Info Text: Datei TaschenRechnerImpl.java und UML erzeugt
o Vorhanden in der Projektversion mit dem symbolischen Namen TaschenRechnerImpl
Revision: 1.2 Erstellungsdatum: 2002/05/07 (eng. Darstellung ) um 04:47:10
o Zugehöriger Info Text: Package gui sowie Datei und UML von TaschenRechnerGUI
erzeugt
o
Vorhanden in der Projektversion mit dem symbolischen Namen
MainTrunkVorBranchBildung; PackageGUIundGUIklasse
2.3.6.3
Beispiel Configuration Management Unterstützung durch Rational Rose
2.3.6.3.1
Controlled Units
Per Default speichert Rose das gesamte Model in einem einzigen .mdl File.
Parallele Entwicklung wird durch die Zerlegung des Files in eine Menge von individuellen
Files, sogenannte controlled units, ermöglicht.
controlled units sind die configuration elements für die Versionskontrolle.
Jedes Team ist für die Entwicklung und Wartung einer spezifischen unit verantwortlich.
Im Falle einer controlled unit enthält nicht mehr das .mdl file den Inhalt des Packages,
sondern z.B. das .cat file. Das .mdl file enthält lediglich Referenzen auf die controlled units.
Falls die controlled units weitere controlled units enthalten, besitzen sie ebenfalls wiederum
Referenzen auf die enthaltenen controlled units.
Die Struktur muss streng hierarchisch sein.
2.3.6.3.2
Verwalten von controlled units
202
203
Erzeugen :
Markieren des package
file
unit
control
Speichern des übergeordneten controlled units sichert die Referenzen auf die neu
erzeugten controlled units.
Im Falle einer Versionskontrolle, muss die übergeordnete controlled unit vor dem Speichern
ausgecheckt werden. D.h. für die übergeordnete controlled unit ensteht eine neue Version.
Laden :
file
units
load
Updaten :
file
units
reload
Importieren
file
import
Uncontrol
file
units
uncontrol
Beim Laden einer controlled unit aus einem read only file wird diese unit in rose als writeprotected deklariert.
Eine checked-in unit ist daher automatisch write-protected im Gegensatz zu einer checkedout unit.
Das Importieren einer controlled unit fügt dem model lediglich eine Referenz auf das
zugehörige file hinzu.
Uncontrol fügt den inhalt des zur unit gehörenden files in das file der umschließenden unit
ein.
Das file der umschließenden unit muss zuvor ausgechecked werden.
2.3.6.4
Bereitstellen eines Project Repository
Z.B. mittels CVS
2.3.6.5
Erstellen von Base Lines
Z.B. mittels CVS
2.3.6.6
2.3.7
2.3.7.1
Erstellen von Change Requests
Deployment
Erstellen von Training Material
203
204
Overhead slides for classroom teaching.
Teacher's instructions.
Example programs, databases, and so on.
Textbooks, tutorials
2.3.7.2
Erstellen von User Support Material
User Guides
Maintenance Guides
Online demos
Online help system
Context-sensitive help
2.3.7.3
Erstellen von Release Notes
Release Notes identify changes and known bugs in a version of a build or deployment unit
that has been made available for use.
2.3.7.4
Ausliefern an ß-Tester
It is a good idea for a company to keep a database of potential beta reviewers and an
archive of their feedback.
Create a beta program to solicit feedback on the product under development from a subset
of the intended users
2.3.8
2.3.8.1
Project Management
Erstellen einer Risk List
A sorted list of known and open risks to the project, sorted in decreasing order of
importance and associated with specific mitigation or contingency actions.
2.3.8.2
Erstellen eines Iteration Plan
Z.B. mit MS Project
2.3.8.3
Aquire Staff
The Project Manager will have determined the staffing needs for the iteration and will look to
the Human Resources function of the organization to provide staff with the needed domain,
skills and experience profiles. Most organizations do not have the luxury of keeping a large
pool of staff on stand-by for projects, and project starts do not always neatly synchronize
with the termination of previous projects. Frequently then, except for a few staff engaged on
the project from the outset, many will need to be hired. This may be a lengthy process, so
the prudent Project Manager will always be looking ahead, and initiating the acquisition of
staff for future iterations as well as the current one. It may be possible to cover shortfalls by
working overtime or by the use of contract rather than permanent staff.
2.3.8.4
Erstellen von Work Orders
The work order is the Project Manager's means of communicating what is to be done, and
when, to the responsible staff. It becomes an internal contract between the Project Manager
and those assigned responsibility for completion.
204
205
2.3.9
2.3.9.1
Environment
Erstellen von Templates
Z.B. Template für Vision, Risk List, Glossary
2.3.9.2
Erstellen von Guidelines
Z.B. Programming Guidelines, Test Guidelines
2.3.9.3
Bereitstellen von Tools
Z.B. Eclipse, Rational Rose, CVS
2.4
Phasen des RUP
2.4.1 Zitate zum Begriff Komplexität
Autor unbekannt
Ein Arzt, ein Hochbauingenieur und ein Informatiker unterhielten sich darüber, was der
älteste Beruf der Welt sei. Der Mediziner führte an, "Schon in der Bibel heißt es, dass Gott
Eva aus Adams Rippe erschaffen hat. Dafür wurde natürlich die Medizin gebraucht, und so
darf ich wohl behaupten, dass ich den ältesten Beruf der Welt habe."
Der Hochbauingenieur unterbrach ihn und sagte, "Aber noch vorher im Buch Genesis heißt
es, dass Gott die Ordnung des Himmels und der Erde aus dem Chaos geschaffen hat. Das
war der erste und wahrscheinlich der spektakulärste Einsatz des Bauingenieurwesens.
Deshalb, lieber Doktor, irren Sie sich : Ich habe den ältesten Beruf der Welt."
Der Informatiker lehnte sich in seinem Stuhl zurück, lächelte und sagte dann verbindlich :
"Und wer, meine Herren, glauben Sie, hat das Chaos erschaffen ? "
Booch
„Die Komplexität industriell einsetzbarer Software-Systeme überschreitet die Kapazität der
menschlichen Intelligenz.“
2.4.2
Die Struktur komplexer Systeme
Nahezu alle komplexen Systeme nehmen dieselbe kanonische Form an :
205
206
NewClass
A1
NewClass2
NewClass13
NewClass5
A2
NewClass4
NewClass3
NewClass7
NewClass8
NewClass9
A3
NewClass10
NewClass6
NewClass14
NewClass15
NewClass11
A4
NewClass12
Bild Kanonische Form komplexer Systeme
Komplexe Systeme sind hierarchisch. Die Ebenen der Hierarchie stellen auch
unterschiedliche Abstraktionsebenen dar. Auf jeder Abstraktionsebene finden wir eine
Ansammlung von Funktionseinheiten, welche zusammenarbeiten, um den höheren Ebenen
bestimmte Dienste zur Verfügung zu stellen.
Im Prinzip müssen auf alle Teile eines komplexen Systems die Aktivitäten Requirements, Analyse,
Design, Implementation und Test angewendet werden.
Wie kann man die Fülle von Aktivitäten sinnvoll strukturieren ?
2.4.3
2.4.3.1
Grundlegende Konzepte des RUP
Use Case Driven
Das Use Case Model beschreibt die komplette Funktionalität des Systems und ersetzt die
traditionelle funktionale Spezifikation.
206
207
Das Analyse&Design Model wird ausgehend von den Use Cases erarbeitet und zeigt die
Realisierung der Use Cases.
Die Implementation wird gegen die Use Cases getestet.
Use Cases bilden die Basis der Bedienungsanleitung.
Use Cases helfen die Reihenfolge der Releases festzulegen. Jede Release imlementiert
eine Menge von Use Cases.
2.4.3.2
Architecture Centric
Use Cases und Architektur beeinflussen sich gegenseitig.
Die Architektur erwächst zunächst aus einigen Use Cases und ermöglicht dann die
Realisierung weiterer oder zukünftiger Use Cases.
Damit werden die Use Cases dann auch durch die Architektur beeinflusst.
2.4.3.3
Iterative and Incremental
Sinnvollerweise unterteilt man große Softwareprojekte in viele kleine Miniprojekte. Jedes
kleine Miniprojekt stellt eine Iteration dar und resultiert in einem Inkrement des Produktes.
Die inkrementelle Vorgehensweise besitzt einige Vorteile.
Kontrollierte Iterationen reduzieren das Kosten- und Zeitrisiko.
Fehler treten am Anfang der Entwicklung auf. Die Architektur kann daher meist noch ohne
eine Verzögerung der Auslieferung korrigiet werden.
Die Iterationen erlauben eine Anpassung des Produktes an die Anforderungen des
Anwenders. Die Anforderungen des Anwenders ändern sich in der Regel durch die
Verwendung des Produktes.
Alle Beteiligten Personen lernen während des Entwicklungsprozesses. Dies erleichtert
insbesondere die Einarbeitung neuer Entwickler.
Manche Risiken eines Projektes weden erst sichtbar, wenn erste Releases vorliegen.
Die iterative Vorgehensweise beinhaltet hingegen die Gefahr, echte Verbesserungen durch
eine Serie nicht konvergenter Änderungen zu ersetzen. Das Softwaresystem wird instabil.
Solche Schleifen entstehen auch bereits in der Analyse-, der Entwurfs- oder der
Implementationsphase.
Die verschiedenen Phasen Analyse, Entwurf sind daher nur schwer zu trennen.
2.4.3.4
Strukturierung des Entwicklungsprozesses
2.4.3.4.1
Phasen-Aktivitäten Diagramm
Man strukturiert einerseits die Lebenszeit eines Softwareproduktes in Phasen und
andererseits die für das Produkt aufgewendete Arbeit in Aktivitäten. Die Aktivitäten werden
in Disciplines gruppiert.
207
208
Bild Phasen-Aktivitäten Diagramm
2.4.3.4.2
Disciplines
Business Modeling
Requirements
Analyse&Design
Implementation
Test
Deployment
Configuration&Change Management
Project Management
Environment
(vgl. oben)
2.4.3.4.3
Zyklen
Das Leben des Entwicklungsprozesses besteht aus einer Reihe von Zyklen. Jeder Zyklus
endet mit der Auslieferung einer neuen Release des Systems an den Kunden.
Eine Release umfasst nicht nur den ausführbaren Code sondern alle für eine Wartung und
Weiterentwicklung benötigten Model Elemente.
2.4.3.4.4
Phasen
Jeder Zyklus wird in die 4 Phasen Inception, Elaboration, Construction, Transition unterteilt.
Buch S. 11 Bild 1.5
Buch S. 104 Bild 5.6
Jede Phase kann von dem Manager oder dem Entwickler noch feiner in Iterationen unterteilt
werden.
Im Prinzip kann jede Iteration als Miniwasserfall betrachtet werden, der alle Aktivitäten von
Requirements bis Test durchläuft.
Je nach Phase ist jedoch das Ausmaß der einzelnen Aktivitäten unterschiedlich ausgeprägt.
So liegt z.B. in der Inception Phase der Schwerpunkt auf der Ermittlung der Anforderungen,
in der Elaboration Phase der Schwerpunkt auf Analyse&Design und in der Construction
Phase liegt der Schwerpunkt auf Implementation und Test.
208
209
Jede Iteration führt zu einem Inkrement der Model Elemente, die das System
repräsentieren.
2.4.3.4.5
Milestones
Jede Phase endet mit einem Meilenstein.
Ein Meilenstein bildet einen Synchronisationspunkt für parallel laufende Aktivitäten. Er ist
definiert durch die Verfügbarkeit eine wohldefinierten Menge von Model Elementen und
Dokumenten.
Jede kleine Iteration endet mit einem minor Milestone.
Diese kleinen Meilensteine dienen zur Reflexion des Zeitplans und des Budgets.
Meilensteine ermöglichen z.B., die Entscheidung zu treffen, ob die nächste Phase
angegangen wird oder das Projekt abgebrochen wird.
Aufzeichnungen über den verwendeten Aufwand innerhalb eines Meilensteins erlauben
bessere Schätzungen in zukünftigen Projekten.
2.4.4
2.4.4.1
Phasen des RUP und zugehörige primäre Aktivitäten
Inception
Das Ziel ist es, entscheiden zu können, ob eine Entwicklung des Produktes lohnt.
RUPTool
The scope of the project should be understood, and the stakeholders initiating the project
should have a good understanding of the project's ROI (return on investment), i.e. what is
returned, for what investment cost. Given this knowledge, a go/no go decision can be taken.
Die Vision des Endproduktes wird entwickelt.
Die Grenzen des Systems werden in Form von Actors identifiziert sowie die Interfaces zu
externen Systemen auf einem abstrakten Level beschrieben.
Die Interaktion des Systems mit den Aktoren wird in Use Cases (< 10% der Use Case
Masse) beschrieben.
Das Analysemodel (< 5% der gesamten Analyse) dient zur Strukturierung und präzisen
Formulierung der Requirements.
Erste Überlegungen zum Kern der Architektur werden angestellt.
Der Focus liegt dabei auf neuen, riskanten oder schwierigen Teilen des Systems, die das
gesamte Projekt gefährden können. Es geht an dieser Stelle nur darum abzuklären, ob eine
Lösung existiert. Wir machen glaubhaft, dass eine Architektur existieren wird.
Erste Überlegungen zu Interfaces für Subsysteme, zur Middleware, zur Realisierung
kritischer Use Cases, zu Supplementary Requirements, zum Deployment Model.
Critical risks werden identifiziert und in einer Risk List festgehalten.
Ein (Wegwerf-)Prototyp demonstriert den potentiellen Usern oder Käufern die Vorzüge des
neuen Systems.
Prototypen dienen auch zur Untersuchung riskanter Systemteile.
(Nur bei dringendem Bedarf !)
Die erwarteten Aufwände werden sehr grob geschätzt (Zeit, Geld, Personal, Hardware usw.)
Der Markt und der Return of Investment wird grob geschätzt.
(Genaue Zahlenangaben sind zu diesem Zeitpunkt nicht möglich)
209
210
Die erwarteten Risiken werden identifiziert und priorisiert
Ggf. wir ein „Proof of concept“ Prototype erstellt.
Kleines Team bestehend aus
Project Manager
Architect
Experienced Developer
Test Engineer
User
Es kann nicht Alles dokumentiert werden.
!!! Die an der Inception Phase beteiligten Personen dienen daher auch als Gedächtnis des
Teams !!!
2.4.4.2
Elaboration
Das Ziel ist es, die Architektur des Systems zu definieren.
RUPTool
The result of this initial iteration would be a first cut at the architecture, consisting of fairly
described architectural views (use-case view, logical view, process view, deployment view,
implementation view) and an executable architecture prototype.
Alle wichtigen Use Cases (80%) werden spezifiziert.
Eine Detailbeschreibung erfolgt für ca. 40% der Use Cases. Aber auch von diesen wird nur
ein Bruchteil der Szenarios im Detail durchdacht.
Der Problembereich wird analysiert.
Das System wird in Packages zerlegt.
Die Realsierung der Use Cases wird überlegt.
Der Focus liegt auf den architekturrelevanten Use Cases (< 10%).
Nicht architekturrelevante Details werden in die Construction Phase verschoben.
Subsysteme, von denen man aus Erfahrung weiss, dass sie ohne Probleme designed
werden können, werden nicht im Detail durchdacht.
Auf diese Weise entsteht ein Skelett des Systems.
Ggf. wird eine ausführbare erste Version des Systems zur Verifizierung der Architektur
gebildet.
Die Architektur wird in mehreren Iterationen entwickelt in Abhängigkeut von der Größe des
Systems, den Risiken, der Neuheit, der Komplexität und der Erfahrung der Entwickler.
Die Iterationen werden solange fortgesetzt, bis die Architektur stabil wird.
Es werden strategische Entscheidungen getroffen bzgl. Frameworks, Middleware (z.B.
MFC, ODBC, Corba)
Die Risiken werden identifiziert und priorisiert.
Ggf. werden ausführbaren Prototypen zur Abschätzung von Risiken entwickelt.
Der Projektplan für den Bau des Systems in der Construction Phase wird entwickelt.
Der Projektplan identifiziert kontrollierte Serien von Releases.
Die Erstellung des Projektplans gelingt oft durch Anordnen der grundlegenden Szenarien.
Die Anordnung wird z.B. so sortiert, dass die riskanten oder für den Kunden wichtigen
Szenarien zuerst realisiert werden.
Es werden Testfälle entwickelt.
Es werden Ziele, Termine und Entwicklungsresourcen festgelegt.
Die Entwicklungsumgebung für die Construction Phase wird vorbereitet.
210
211
Die Architecture Baseline, die in der Elaboration Phase entwickelt wird, überlebt in Form einer
Architecture Description.
Diese Beschreibung enthält den für das Verständnis der Architektur relevanten Teil der Model
Elemente, Begründungen für strategische Entscheidungen, Performance- oder
Speicheranforderungen, verwendete Patterns, kurz all das, was die Entwickler wissen müssen.
Nicht enthalten sind z.B. Testfälle.
Kleines Team bestehend aus
Project Manager
Architect
Experienced Developer
Test Engineer
Wiederverwendungsmanager
Zukünftige Leiter der Subsysteme
User
2.4.4.3
Construction
Das Ziel ist die Erstellung einer „beta release“.
RUPTool
The main result of a late iteration in the construction phase is that more functionality is
added, which yields an increasingly more complete system. The results of the current
iteration are made visible to developers to form the basis of development for the subsequent
iteration.
Das Produkt wird bis zur Einführung beim Kunden in einer Serie von Iterationen entwickelt.
Jede Iteration implementiert eine Gruppe von Use Cases.
Component Engineers designen und implementieren die erforderlichen Subsysteme und
Klassen.
Sie kompletieren die Requirements zu 100 %
Design user interface, Complete use case description
Sie vervollständigen die Use Case Realizations.
Sie designen Subsysteme und Klassen.
Sie implementieren Subsysteme und Klassen.
Service subsystems, die für einen use case benötigt werden, werden in der regel aus
Effizienz Gründen vollständig implementiert und nicht nur soweit wie es den use case
betrifft.
Die resultierenden Subsysteme und Klassen werden getestet. Zunächst werden sie einem
Unit Test unterzogen und dann dem Integrationstest.
Der test engineer definiert test cases and test procedures zur Verifizierung der testbaren
Requirements.
Er modifiziert test cases und test procedures der vorausgehenden builds für
Regressionstests
Der System Integrator definiert die Folge der Builds in Form eines Integration Build Plan
(meist bottom up).
Der System Integrator erzeugt nach dem Build Plan die Builds.
Der Integration Tester testet die Builds
Dazu führt er die Test Cases gemäß der Test Procedures aus
Für manche Klassen werden Stubbs benötigt um ein Build durchführen zu können.
Ferner werden driver implementiert um nicht vorhandene components zu simulieren
Gefundene Fehler werden gelogged und den Projekt Manager mitgeteilt
211
212
Die component engineers fixen die Fehler.
Die test engineer evaluieren die tests.
Ggf werden test cases modifiziert oder neue definiert.
Der Project manager vergleicht den aktuellen Stand des Projektes mit den Schätzungen aus
der Elaboration Phase
Ein Maß für den Fortschritt des Projektes ist completion of builds and iterations im Vergleich
zum Plan
Die gesammelten Erfahrungen werden in die Pläne für die zukünftigen Releases
eingearbeitet
Viele Entwickler, großes Team.
Use Case engineer
Component engineer
Test engineer
System integrator
Itegration tester
System tester
System analyst
Architect
2.4.4.4
Transition
Das Ziel ist, die Ausliederung an den Kunden.
RUPTool
The transition phase culminates in the delivery to the customer of a complete system (and
ancillary support artifacts) with functionality and performance as specified, and
demonstrated in acceptance testing. The customer takes ownership of the software after a
successful acceptance test.
Z.B. werden ß-Tester selektiert und Test Instruktionen vorbereitet.
Es wird ein User Manual oder Tutorial erstellt.
Das Produkt wird an die Anwender verteilt.
Die Anwender werden geschult.
Fehler werden korrigiert.
Schwere Fehler führen zu einer delta Release, kleinere werden auf die nächste Release
verschoben.
Ggf. werden neue kleinere Anforderungen im Rahmen der vorhandenen Architektur
realisiert.
3
3.1
Komponentenbasierte Softwareentwicklung am Beispiel von Enterprise Java Beans
Literatur
Enterprise Java Beans
Richard Monson-Haefel
O’Reilly 1999
3.2
Introduction
Wir betrachten eine E-Commerce Anwendungen im Internet.
Eine Bestellung mittels Internet tangiert in der Regel viele verteilte Glieder der
Geschäftsprozesskette. So möchte der Anbieter z.B. vom Hersteller Angaben über
212
213
individuelle Produktvarianten und Preise, vom Logistikpartner Auslieferungstermine und von
seiner Bank ein Finanzierungsangebot. Zur Automatisierung solcher Geschäftsprozesse,
hätte man gerne ein System von flexibel kombinierbaren und damit wiederverwendbaren
Softwarekomponenten.
Diese Komponenten sollten ihre Daten darüberhinaus selbständig in eine Datenbank legen,
Transaktionen beachten und auf verschiedenen Rechnern laufen können.
Enterprise JavaBeans (EJB) definiert ein standard distributed component model für die
Entwicklung skalierbarer und portierbarer business systems.
Das Komponentenmodell erlaubt die Verteilung von business objects in Form von beans
(Bohnen).
Ein EJB Server ist für object brokering, transaction management, security, persistence und
concurrency verantwortlich.
Die Kapselung von Daten einer Datenbank in business object beans hat Vorteile gegenüber
einem direkten Zugriff auf die Datenbank. Der Aufruf einer Methode eines objects ist in der
Regel einfacher als die Ausführung eines SQL Statements. Die Objektifizierung von Daten
mittels beans erleichtert die Wiederverwendung. Die Datenbankimplementierung kann
modifiziert werden, ohne dass der Client etwas davon bemerkt.
Attribute der bean, z.B. Transaktionsverhalten, Persistenz und Sicherheit können über
Properties ohne Code Modifikationen eingestellt werden.
Eine enterprise bean ist nicht nur betriebssystemunabhängig sondern auch unabhängig von
der Implemenation des application servers sofern dieser die EJB Spezifiaktion
implementiert.
3.2.1
Distributed Object Architectures
3.2.1.1
Three Tier Architecture
Distributed business objects bilden in der Regel die mittlere Schicht einer drei Schichten
Architektur, bestehend aus GUI, business logic und einer Datenbank.
// Einfügen
Bild 3 Schichten Architektur
Distributed object technologie (Java EJB, CORBA, Microsoft DCOM) erlaubt die
Verwendung der Objekte durch client applications auf einem anderen Computer.
Wie funktiononiert das ?
3.2.1.2
Stub und Skeleton
// Einfügen
Bild Stub Skeleton
Das Prinzip besteht darin, ein Stellvertreter-Objekt auf dem lokalen Computer so aussehen
zu lassen wie das Orginal auf einem entfernten Computer.
Die Archietktur besteht aus dem objectServer, skeleton (Skelett) und stub (Stummel).
Jeder objectServer hat eine zugehörige stub- und skeleton-Klasse.
Stub und skeleton sind dafür verantwortlich, den objectServer so aussehen zu lassen, als
würde er auf der lokalen client Maschine laufen.
Dies wird erreicht durch ein remote method invocation (RMI) Protokoll.
Java, Corba, Microsoft DCOM verwenden unterschiedliche RMI Protokolle.
// Einfügen
213
214
3.2.1.3
Interface und Server Technologie am vereinfachten Modell
Beispiel PersonModel
public interface Person {
public int getAge() throws Throwable;
public String getName() throws Throwable;
}
public class PersonServer implements Person {
int age;
String name;
public PersonServer(String name, int age){
this.age = age;
this.name = name;
}
public int getAge(){
System.out.println("getAge() called !");
return age++;
}
public String getName(){
System.out.println("getName() called !");
return name;
}
}
public class PersonClient {
public static void main(String [] args){
try{
Person person = new Person_Stub();
int age = person.getAge();
String name = person.getName();
System.out.println(name+" is "+age+" years old");
age = person.getAge();
name = person.getName();
System.out.println(name+" is "+age+" years old");
age = person.getAge();
name = person.getName();
System.out.println(name+" is "+age+" years old");
}catch(Throwable t) {t.printStackTrace();}
}
}
public class Person_Stub implements Person {
Socket socket;
public Person_Stub()throws Throwable{
}
public int getAge( ) throws Throwable{
// when this method is invoked, stream the method name to the
// skeleton
// create a network connection to the skeleton.
// Replace "myhost" with your own IP Address of your computer.
socket = new Socket("Edelmann",9000);
ObjectOutputStream outStream
= new ObjectOutputStream(socket.getOutputStream());
outStream.writeObject("age");
outStream.flush();
ObjectInputStream inStream = new
ObjectInputStream(socket.getInputStream());
return inStream.readInt();
}
public String getName()throws Throwable{
// when this method is invoked, stream the method name to the
// skeleton
// create a network connection to the skeleton.
// Replace "myhost" with your own IP Address of your computer.
socket = new Socket("Edelmann",9000);
ObjectOutputStream outStream
= new ObjectOutputStream(socket.getOutputStream());
outStream.writeObject("name");
outStream.flush();
ObjectInputStream inStream = new
ObjectInputStream(socket.getInputStream());
return (String)inStream.readObject();
}
}
214
215
public class Person_Skeleton extends Thread{
PersonServer myServer;
public Person_Skeleton(PersonServer server){
// get a reference to the object server that this skeleton wraps
this.myServer = server;
}
public void run(){
try{
// create a server socket on port 9000
ServerSocket serverSocket = new ServerSocket(9000);
// wait for and obtain a socket connection from stub
boolean finished = false;
while (!finished)
{
Socket socket = serverSocket.accept();
if(socket != null)
{
// create an input stream to receive requests from stub
ObjectInputStream inStream
= new ObjectInputStream(socket.getInputStream());
// Read next method request from stub. Block until request is
// sent.
String method = (String)inStream.readObject();
// Evalute the type of method requested
if(method.equals("age")){
// invoke business method on server object
int age = myServer.getAge();
// create an output stream to send return values back to
// stub.
ObjectOutputStream outStream
= new ObjectOutputStream(socket.getOutputStream());
// send results back to stub
outStream.writeInt(age);
outStream.flush();
}else if(method.equals("name")){
// invoke business method on server object
String name = myServer.getName();
// create an output stream to send return values back to
// the stub.
ObjectOutputStream outStream
= new ObjectOutputStream(socket.getOutputStream());
// send results back to stub
outStream.writeObject(name);
outStream.flush();
}
}
}
}catch(Throwable t) {t.printStackTrace();System.exit(0); }
}
public static void main(String args [] ){
// obtain a unique instance Person
PersonServer person = new PersonServer("Richard", 34);
Person_Skeleton skel = new Person_Skeleton(person);
skel.start();
}
}
Der stub implementiert ein interface mit denselben business-methods wie das serverObject.
Jedoch enthalten die Methoden des stub keine business-logic. Stattdessen enthalten sie die
Anweisungen, die erforderlich sind, um Anforderungen an den skeleton zu senden und die
Ergebnisse zu empfangen.
Wenn ein Client eine Methode des stub aufruft, werden Name und Parameter als stream
zum skeleton übertragen, dieser parst den stream und ruft die korrespondierende Methode
des objectServers. Jeder Wert, den die gerufene Methode zurückliefert, wird als stream
zum stub zurücktransportiert. Der stub liefert dann den Wert an den Client als ob die
business logic lokal ausgeführt worden wäre.
Wirkliche distributed object protocols wie CORBA, DCOM, Java RMI generieren stub und
skeleton und unterstützen exception handling. Sogenannte object request broker (ORB)
erlauben die Verwaltung von serverObjects. Sie unterstützen transaction management ind
security.
3.2.2
Server-Side Components (Selbststudium)
215
216
Die Einkapselung von business logic in business objects hat in jünster Zeit eine große
Bedeutung in der IT Industrie erhalten. Wenn die Software, welche das business modelliert,
in business objects gekapselt werden kann, wird sie flexibel, erweiterungsfähig und
wiederverwendbar.
Ein server-side component model definiert eine Architektur für die Entwicklung verteilter
business objects.
In diesem Sinne ist ein business System eine Ansammlung von server-side components wie
customer, product, reservation, warehouse usw. Products können in dem warehouse
gespeichert werden oder an den customer ausgeliefert werden. Ein customer kann ein
product reservieren oder kaufen. Ein solches System ist flexibel, da es objektifiziert ist, und
gut zugreifbar, weil es aus verteilten Objekten besteht.
Component Models
JavaBeans (package java.beans) ist ein Komponentenmodell für intraprocess Zwecke, EJB
(package javax.ejb) ist ein Komponentenmodell für interprocess Zwecke.
JavaBeans kann etwa verwendet werden, um eine GUI zusammenzusetzen. Eine
Komponente ist etwa ein PushButton. EJB wird hingegen verwendet, um eine verteilte
business logic aufzubauen. Eine Komponente ist etwa ein customer object.
3.2.3
Component Transaction Monitors (Selbststudium)
Sogenannte application server managen die Komponenten zur Laufzeit und machen sie für
clients verfügbar. Ein application server besteht in der Regel aus verschiedenen
Technologien.
TP Transaction processing monitors
Transaction processing monitors sind operating systems für business systems. Sie
managen Speicher, Datenbankzugriff und Transaktionen. Die business logic wird dabei
durch prozedurale Applikationen gebildet. Der Zugriff erfolgt meist mittels remote procedure
calls (RPC). TP monitors sind nicht objektorientiert.
ORB object request broker
Der ORB ist vergleichbar mit einer Telefonzentrale. Seine wesentliche Aufgabe besteht in
der Übermittlung von Operationsaufrufen und ihren Ergebnissen zwischen Client- und
Server-Objekten.
Der ORB benutzt dazu die Schnittstellen der Client und Server Objekte.
Die Übertragung basiert z.B. auf dem IIOP (Internet Inter ORB Protokoll). Dieses wiederum
verwendet TCP/IP.
Clients erhalten Dienstleistungen über Anforderungen (Request).
Ein Request umfasst die Angabe des Servers, der Operation, der Ein- und
Ausgabeparameter, optionaler Ausnahmebedingungen und optionaler Kontextangaben.
Für den Client ist nicht mehr erkennbar, welcher Prozess auf welchem Rechner seine
Anforderung erfüllt.
Die Verlagerung von Diensten auf einen anderen Netzknoten oder eine Änderung der
Netztopologie hat also keine Auswirkungen auf den Client.
Die Technologien für verteilte Sytseme wie etwa CORBA oder Java RMI sind aus der RPC
Technologie erwachsen. Der signifikante Unterschied besteht darin, dass man eine
Methode eines Objektes aufruft und nicht eine Funktion einer Applikation. Die verteilten
Objekte werden gewöhnlich von einem ORB verwaltet.
ORB’s haben sich jedoch in Umgebungen mit einem hohen Volumen von Transaktionen als
unbrauchbar erwiesen. Darüberhinaus überlassen sie die Behandlung von Transaktionen,
Persistenz und concurrency der Applikation.
CTM component transaction monitor
Aus der traditionellen transaction Technologie und der ORB Technologie entwickelte sich
(1999) der component transaction monitor (CTM) als hybrid model.
CTM`s managen concurrency, transactions, object distribution, load balancing, security,
persistence und resource management. Entwickler müssen diese Möglichkeiten nicht
implementieren.
216
217
CTM`s werden von der relational database Industrie, der application server Industrie, der
web server Industrie und der CORBA ORB Industrie hergestellt.
Die Beziehung des CTM zu seinen components ist diesebe wie die Beziehung des
Eisenbahnsystems zu seinen Zügen. Das Eisenbahnsystem kümmert sich um load
balancing, concurrency und resourcen management. Das Eisenbahnsystem liefert die
Infrastruktur für die Züge. Der Lokführer kümmert sich um den Transport und nicht um die
Infrastruktur.
Verschiedene Eisenbahnsysteme haben etwa unterschiedliche Spurbreiten. Die Züge des
einen Systems laufen nicht auf den Schienen des anderen Systems.
Der MTS ist Microsofts CTM
Der Microsoft Transaction Server (MTS) wurde 1996 eingeführt. Der Service für verteilte
Objekte basiert auf der DCOM Technologie. Ein verwaltetes business object muss COM
konform sein.
Die Verwendung von MTS erzwingt die Microsoft Plattform.
MTS unterstützt keine persistenten Komponenten.
CORBA Common Object Request Broker Architecture CTM`s
Die meisten nicht Microsoft CTM`s basieren auf dem offenen CORBA Standard als
Technologie für verteilte Objekte.
Systemanbieter und Anwender objektorientierter Techniken haben sich 1989 zur OMG
(Object Management Group) zusammengeschlossen, um Standards und Spezifikationen für
eine Infrastruktur zu entwickeln, die für verteilte objektorientierte Anwendungen erforderlich
ist. Diese Spezifikationen wurden zum de-facto-Standard in der Softwareindustrie.
(Z.Zt., d.h. 1999 ca 800 Firmen)
Die OMG arbeitet lediglich die Spezifikation aus. Sie überläßt die Erstellung der Software
kommerziellen Anbietern.
Neben dem CORBA Standard benötigte man aber ein gemeinsames component model.
EJB Enterprise JavaBeans ist Sun’s CTM
Enterprise JavaBeans is a standard server-side component model for component
transaction monitors.
19 97 gab sun die erste Spezifikation von Enterprise JavaBeans heraus.
Daher kann man nun erwarten, dass business objects, die dem EJB Standard enstprechen,
in jedem CTM arbeiten, welches die EJB Spezifikation unterstützt.
IBM`s Sun Francisco business object framework wird sich dem EJB component model
anpassen.
3.3
Architectural Overview
Man unterscheidet entity beans und session beans.
3.3.1
Beispiel CruiseBean
import javax.ejb.EntityContext;
import javax.ejb.FinderException;
public class CruiseBean implements javax.ejb.EntityBean
{
public String cruiseId;
public String cruiseName;
public String cruiseShip;
public String cruiseDate;
public int cruiseDays;
public int cruiseMaxPerson;
public
String
ejbCreate(String
cruiseId,
String
cruiseName,
cruiseShip,String cruiseDate, int cruiseDays, int cruiseMaxPerson)
{
this.cruiseId = cruiseId;
this.cruiseName = cruiseName;
this.cruiseShip = cruiseShip;
this.cruiseDate=cruiseDate;
this.cruiseDays=cruiseDays;
this.cruiseMaxPerson=cruiseMaxPerson;
return null;
String
217
218
}
public String getCruiseId()
{
return cruiseId;
}
public String getName()
{
return cruiseName;
}
public void setName(String cruiseName)
{
this.cruiseName = cruiseName;
}
public void setShip(String cruiseShip)
{
this.cruiseShip = cruiseShip;
}
public String getShip()
{
return cruiseShip;
}
public void setDate(String cruiseDate)
{
this.cruiseDate = cruiseDate;
}
public String getDate()
{
return cruiseDate;
}
public int getMaxPerson()
{
return cruiseMaxPerson;
}
public void setMaxPerson(int cruiseMaxPerson)
{
this.cruiseMaxPerson = cruiseMaxPerson;
}
public int getDays()
{
return cruiseDays;
}
public void setDays(int cruiseDays)
{
this.cruiseDays = cruiseDays;
}
public
void
ejbPostCreate(String
cruiseId,
String
cruiseName,
cruiseShip,String cruiseDate, int cruiseDays, int cruiseMaxPerson){}
public void setEntityContext(EntityContext ctx){}
public void unsetEntityContext(){}
public void ejbActivate(){}
public void ejbPassivate(){}
public void ejbLoad(){}
public void ejbStore(){}
public void ejbRemove(){}
}
String
EJB erfordert die Verwendung der Java RMI Konventionen.
3.3.2
Entity beans
// Einfügen
Mit anderen Worten modellieren entity beans real world objects. Diese objects entsprechen
gewöhnlich persistenten records in einer Datenbank.
Im Beispiel eines Reisebüros benötigen wir entities welche etwa customer, cruise, cabins
und reservations repräsentieren.
Entity beans modellieren Daten und Verhalten. Das Verhalten betrifft im Wesentlichen
business logic, die sich direkt auf die Daten bezieht.
Entity beans repräsentieren business concepts, die als Nomen ausgedrückt werden können.
3.3.3
Beispiel TravelAgentBean
import javax.naming.InitialContext;
import javax.ejb.SessionBean;
218
219
import
import
import
import
import
import
import
javax.ejb.SessionContext;
javax.ejb.DuplicateKeyException;
javax.ejb.CreateException;
javax.rmi.PortableRemoteObject;
java.rmi.RemoteException;
java.util.*;
Reservation;
public class TravelAgentBean implements SessionBean
{
ReservationHome reservationHome; // Achtung : Kann fuer stateless session beans
kritisch werden
Enumeration reservationListe;
// Achtung : Kann fuer stateless session beans
kritisch werden
public Reservation
String customerId)
travelAgentReservation(String
throws
reservationId,
DuplicateKeyException,
String
cruiseId,
CreateException,
DataExistsException
{
Reservation theReservation = null;
//String idReservation = reservationId;
try {
InitialContext context = new InitialContext();
Object objectReservationHomeRef = context.lookup("reservation");
reservationHome
(ReservationHome)PortableRemoteObject.narrow(objectReservationHomeRef,
ReservationHome.class);
}
catch (Exception NamingException)
{
NamingException.printStackTrace();
}
=
// Pruefe, ob bereits Reservierung fuer den Kunden mit der vorgegebenen Kreuzfahrt
existiert
boolean found = false;
try {
reservationListe = reservationHome.findAll();
while(reservationListe.hasMoreElements())
{
theReservation = (Reservation)reservationListe.nextElement();
if(cruiseId.equals(theReservation.getCruiseId())
&&
customerId.equals(theReservation.getCustomerId()))
{
found = true;
return null;
//throw new DataExistsException();
//noch nicht funktioniert
// break;
}
}
}
catch (Exception ex)
{
ex.printStackTrace();
}
try{
if(found == false)
//theReservation
=
reservationHome.create(idReservation,
cruiseId,
customerId);
theReservation
=
reservationHome.create(reservationId,
cruiseId,
customerId);
}
catch (Exception e)
{
String message = e.getMessage();
e.printStackTrace();
}
return theReservation;
}
public Reservation getRecordReservation(String reservationId)//idReservation)
{
Reservation record = null;
try {
record = reservationHome.findByPrimaryKey(reservationId);//idReservation);
}
catch (java.rmi.RemoteException e)
{
String message = e.getMessage();
}
219
220
catch (javax.ejb.FinderException e)
{
e.printStackTrace();
}
return record;
}
public
public
public
public
public
public
public
void
void
void
void
void
void
void
ejbCreate() { }
setSessionContext(SessionContext context) { }
ejbRemove() { }
ejbActivate() { }
ejbPassivate() { }
ejbLoad() { }
ejbStore() { }
}
3.3.4
Session beans
// Einfügen
Session beans agieren als Agenten für den client.
Session beans managen die Interaktionen zwischen den entity beans.
Session beans sind der angemessene Platz für die business logic. Sie steuern unter
Verwendung von entity beans den workflow. Sie repräsentieren daher den workflow. Der
workflow beschreibt alle Schritte, die getan werden müssen, um eine bestimmte Arbeit zu
erledigen.
Einige Aktionen kann man durch einen direkten Aufruf einer Methode der cruise bean
erledigen. Jedoch weiß die cruise bean nichts über den context einer Aktion. Bucht man
also etwa einen Passagier für ein Kreuzfahrtschiff, so muss man sicher eine cruise bean
verwenden. Man muss jedoch eine Menge Wissen verwenden, welches nichts mit einem
Schiff zu tun hat. Z.B. muss man etwas über den Passagier wissen, über den Ticketpreis,
über den Fahrplan usw. Eine TravelAgent session bean wird eine cruise, eine customer und
eine reservation bean verwenden, um eine Reservierung durchzuführen.
Die TravelAgent session bean repräsentiert in diesem Falle nicht die Menge der Kunden,
Kreuzfahrten und Kabinen. Sie kann daher nicht als Entität aufgefasst werden. Sie
repräsentiert lediglich das Wissen um Beziehungen, Vorschriften, Randbedingungen im
Zusammenhang mit der Buchung. Sie repräsentiert das Wissen eines Travelagent aber
nicht die Person.
Die komplexe Steuerung einer Buchung sollte nicht der GUI und damit im
Verwaltungsmodell der Sekretärin obliegen.
Die Buchung sollte aber auch nicht von dem Bankkonto oder dem Schiff vorgenommen
werden. Das Bankkonto sollte nichts von Kreuzfahrten wissen. Die Kreuzfahrt bean sollte
nichts von Bankkonten wissen. Daher benötigt man für diese Aufgabe ein eigenes Objekt.
Die Aktivität, welche durch die session bean repräsentiert wird, ist in der Regel transient. Sie
repräsentiert keinen Record der Datenbank. Jedoch erzeugt die session bean z.B. eine
Reservierung, die einem customer eine ganz spezielle cabin auf einer ganz speziellen
cruise zuweist. Diese Aktionen führen zu Änderungen der entity beans und diese
Änderungen werden durch die Datenbank reflektiert.
Client applications verwenden die cruise bean oder die customer bean in unterschiedlicher
Weise. Manche Verwendungen sind vorhersagbar, die meisten nicht. Darüberhinaus ändert
sich die Verwendung mit der Zeit. Um nicht die cruise oder customer bean permanent
ändern und anpassen zu müssen, ist es wichtig, die Daten von dem workflow zu trennen.
Die wesentliche Unterscheidung zur entity bean besteht darin, dass eine entity bean einen
persistenten Status hat, wohingegen eine session bean Interaktionen modelliert und keinen
persistenten Status hat.
220
221
Wenn man an ein Theaterstück denkt, repräsentieren die entity beans die Schauspieler und
die session bean das Manuskript.
Die Verschiebung der business logic in session beans führt zu thin clients und reduziert den
Netzverkehr. Ein Request des Client führt zu einem Methodenaufruf der session bean und
vielen Methodenaufrufen für weitere session und entity beans, die in der Regel ebenfalls auf
dem server liegen. So wird das Netz nur durch einen Methodenaufruf belastet. Ferner
benötigt der Client nur eine einzige Netzverbindung zu der einen session bean, z.B. dem
TravelAgent.
3.3.5
The bean container contract
Enterprise beans kommunizieren mit dem EJBServer über ein wohldefiniertes
Komponentenmodell.
Die Basis dieses Komponentenmodels bilden die EntityBean und SessionBean interfaces.
Die entityBean Schnittstelle umfasst etwa solche Methoden wie
ejbCreate()
ejbPostCreate()
ejbActivate()
ejbLoad()
ejbStore()
Sie dienen dazu, die bean durch den EJB Server über Events zu informieren, die seinen
Lebenszyklus betreffen. Wenn der Zustand der bean keine Reaktion auf die Events
erfordert, können die Methoden leer implementiert werden.
EJBContext ist ein interface, welches von dem container implementiert wird.
EntityBeans verwenden die abgeleitete Klasse EntityContext, session beans die abgeleitete
Klasse SessionContext.
EJBContext beliefert die bean mit Informationen über die umgebende Welt.
3.3.6
Beispiel Cruise Interface and Class CruiseHome
import java.rmi.RemoteException;
public interface Cruise extends javax.ejb.EJBObject
{
public String getName( )throws RemoteException;
public void setName(String name)throws RemoteException;
public String getShip( )throws RemoteException;
public String getCruiseId( )throws RemoteException;
public void setShip(String ship)throws RemoteException;
public String getDate( )throws RemoteException;
public void setDate(String termin)throws RemoteException;
public int getMaxPerson()throws RemoteException;
public void setMaxPerson(int maxPers)throws RemoteException;
public int getDays()throws RemoteException;
public void setDays(int dauer)throws RemoteException;
}
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.FinderException;
import java.util.Enumeration;
public interface CruiseHome extends javax.ejb.EJBHome
{
public Cruise create(String cruiseId, String cruiseName,String cruiseShip,String
cruiseDate, int cruiseDays,int cruiseMaxPerson)
throws RemoteException, javax.ejb.CreateException;
public Cruise findByPrimaryKey(String cruiseId)
throws RemoteException, javax.ejb.FinderException;
public Enumeration findAll()
throws FinderException,RemoteException;
}
3.3.7
Classes and Interfaces
221
222
Um eine enterprise bean zu implementieren, muss man zwei Interfaces und zwei Klassen
implementieren.
Remote interface
Dieses definiert die business methods des bean.
Home interface
Dieses definiert die life cycle methods : Erzeugen, Löschen und Finden einer bean.
Bean class
Diese Klasse implementiert die business methods.
Primary key
Diese Klasse definiert den primary key. Er wird verwendet, um eine spezifische bean zu
finden.
Die Klasse liefert einen Pointer in die Datenbank und implementiert Serializable().
Basierend auf den Interfaces und der bean class wird eine Menge code generiert. Dieser
code erzeugt z.B. die beans, speichert sie in der Datenbank, überträgt die Anforderungen
ins Netz usw. Mit diesem generierten code werden die beiden interfaces imlementiert.
3.3.8
Implementation des remote interface
// Einfügen
Bild EJBObject
Auf der Client Seite wird das remote interface duch den stub implementiert.
Auf der Server Seite verpackt das sogenannte EJBObject die erzeugte bean class, im
Beispiel die CruiseBean und erweitert deren Funktionalität. Dieses EJBObject wird generiert
und basiert auf der bean class und den Informationen des sogenannten
DeploymentDescriptors. Das EJBObject arbeitet mit dem Container zusammen.
Wenn ein client eine Methode des stubs aufruft, empfängt das EJBObject die Anforderung
und delegiert sie an die bean instance. Das EJBObject kümmert sich dabei um transactions,
security, concurrency.
Es existieren verschiedene vendorspezifische Strategien für die Implementierung des
EJBObjects
222
223
EJBObject
EntityBean
EJBObject
CruiseBean
Cruise
EntityBean
Cruise
CruiseEJB
Object
CruiseBean
CruiseEJBObject
a)
b)
Bild Implementation EJBObject
Sämtliche remote interfaces sind von EJBObject abgeleitet.
Der Client erhält vom home interface eine Referenz auf ein EJBObject.
GetEJBHome() ermöglicht dem Anwender, wieder eine Referenz auf das home interface zu
erhalten, wenn das home interface nicht mehr direkt erreichbar ist.
GetPrimaryKey() ist für session beans bedeutungslos.
Der primary key kann mittels serialization in ein file geschrieben werden. Er kann verwendet
werden um assoziations zwischen entity beans zu verwalten.
EJBObject.isIdentical() liefert true, wenn zwei Referenzen dieselbe bean repräsentieren.
Object.equals() leistet dies nicht, da diese Methode lediglich die beiden stubs vergleicht.
Diese können verschieden sein, obwohl sie dasselbe bean object referenzieren. Stateless
session beans vom selben Typ werden als gleich angesehen.
Für entity beans invalidiert die remove() Methode die remote reference und löscht die Daten
aus der Datenbank. Session beans werden freigegeben.
3.3.9
Implementation des home interface
Das home interface liefert life-cycle operations und metadata. cruiseHome erweitert
EJBHome um spezifiche create() und find() Methoden.
Die Klasse unterstützt den Container bei der Verwaltung des bean life cycle (vgl. weiter
hinten). Sie ist verantwortlich für die Erzeugung und Vernichtung von beans.
Wenn z.B. eine create() Methode des home interfaces gerufen wird, erzeugt EJBHome eine
Instanz des EJBObjects und koppelt eine Instanz der CruiseBean daran. Anschließend wird
die Methode ejbCreate() der CruiseBean gerufen.
find() Methoden für session beans sind bedeutungslos. Sie repräsentieren keine Daten,
daher gibt es nichts zu suchen.
Die find() Methoden werden im Falle von container managed entities vom EJBServer
automatisch generiert. Im Falle von bean managed entities wird jeder Aufruf an eine
korrespondierende Methode der bean instance delegiert.
223
224
Mittels JNDI (vgl. weiter hinten) erhält man eine remote reference in Form eines stubs zu
dem EJB Home object, welches das home interface auf der Server Seite implementiert.
EJBHome.remove()
Das Entfernen einer entity bean mittels remove() hat das Löschen der Daten in der
Datenbank zur Folge.
EJBHome.getEJBMetaData()
Diese Funktion liefert eine Instanz von EJBMetaData.
Die Klassen EJBObject und EJBHome werden beim Einfügen einer bean in einen Container
basierend auf den Klassen des JAR Files generiert.
3.3.10 Beispiel deployment descriptor
Ejb-jar.xml
<?xml version="1.0" encoding="Cp1252"?>
<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN' 'http://java.sun.com/j2ee/dtds/ejbjar_1_1.dtd'>
<ejb-jar>
<description>no description</description>
<display-name>Ejb1</display-name>
<enterprise-beans>
<entity>
<description>no description</description>
<display-name>CruiseBean</display-name>
<ejb-name>Cruise</ejb-name>
<home>pCruise.CruiseHome</home>
<remote>pCruise.Cruise</remote>
<ejb-class>pCruise.CruiseBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>False</reentrant>
<cmp-field>
<description>no description</description>
<field-name>cruiseName</field-name>
</cmp-field>
<cmp-field>
<description>no description</description>
<field-name>cruiseDate</field-name>
</cmp-field>
<cmp-field>
<description>no description</description>
<field-name>cruiseShip</field-name>
</cmp-field>
<cmp-field>
<description>no description</description>
<field-name>cruiseDays</field-name>
</cmp-field>
<cmp-field>
<description>no description</description>
<field-name>cruiseId</field-name>
</cmp-field>
<cmp-field>
<description>no description</description>
<field-name>cruiseMaxPerson</field-name>
</cmp-field>
<primkey-field>cruiseId</primkey-field>
</entity>
<entity>
<description>no description</description>
<display-name>ReservationBean</display-name>
<ejb-name>Reservation</ejb-name>
<home>pCruise.ReservationHome</home>
<remote>pCruise.Reservation</remote>
<ejb-class>pCruise.ReservationBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>False</reentrant>
<cmp-field>
<description>no description</description>
<field-name>reservationId</field-name>
</cmp-field>
<cmp-field>
<description>no description</description>
<field-name>customerId</field-name>
</cmp-field>
<cmp-field>
<description>no description</description>
<field-name>cruiseId</field-name>
</cmp-field>
<primkey-field>reservationId</primkey-field>
</entity>
<entity>
<description>no description</description>
<display-name>CustomerBean</display-name>
<ejb-name>Customer</ejb-name>
<home>pCruise.CustomerHome</home>
<remote>pCruise.Customer</remote>
<ejb-class>pCruise.CustomerBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>False</reentrant>
<cmp-field>
224
225
<description>no description</description>
<field-name>customerName</field-name>
</cmp-field>
<cmp-field>
<description>no description</description>
<field-name>customerFirstname</field-name>
</cmp-field>
<cmp-field>
<description>no description</description>
<field-name>customerId</field-name>
</cmp-field>
<primkey-field>customerId</primkey-field>
</entity>
<session>
<description>no description</description>
<display-name>TravelAgentBean</display-name>
<ejb-name>TravelAgent</ejb-name>
<home>pCruise.TravelAgentHome</home>
<remote>pCruise.TravelAgent</remote>
<ejb-class>pCruise.TravelAgentBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>Cruise</ejb-name>
<method-intf>Home</method-intf>
<method-name>findAll</method-name>
<method-params />
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Cruise</ejb-name>
<method-intf>Remote</method-intf>
<method-name>setMaxPerson</method-name>
<method-params>
<method-param>int</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Cruise</ejb-name>
<method-intf>Home</method-intf>
<method-name>create</method-name>
<method-params>
<method-param>java.lang.String</method-param>
<method-param>java.lang.String</method-param>
<method-param>java.lang.String</method-param>
<method-param>java.lang.String</method-param>
<method-param>int</method-param>
<method-param>int</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Cruise</ejb-name>
<method-intf>Remote</method-intf>
<method-name>getDays</method-name>
<method-params />
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Cruise</ejb-name>
<method-intf>Remote</method-intf>
<method-name>getMaxPerson</method-name>
<method-params />
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Cruise</ejb-name>
<method-intf>Remote</method-intf>
<method-name>getName</method-name>
<method-params />
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Cruise</ejb-name>
<method-intf>Home</method-intf>
<method-name>findByPrimaryKey</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Cruise</ejb-name>
<method-intf>Remote</method-intf>
<method-name>getShip</method-name>
<method-params />
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Cruise</ejb-name>
<method-intf>Remote</method-intf>
225
226
<method-name>getDate</method-name>
<method-params />
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Cruise</ejb-name>
<method-intf>Remote</method-intf>
<method-name>setName</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Cruise</ejb-name>
<method-intf>Remote</method-intf>
<method-name>remove</method-name>
<method-params />
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Cruise</ejb-name>
<method-intf>Remote</method-intf>
<method-name>setShip</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Cruise</ejb-name>
<method-intf>Remote</method-intf>
<method-name>setDays</method-name>
<method-params>
<method-param>int</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Cruise</ejb-name>
<method-intf>Remote</method-intf>
<method-name>setDate</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Cruise</ejb-name>
<method-intf>Remote</method-intf>
<method-name>getCruiseId</method-name>
<method-params />
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Reservation</ejb-name>
<method-intf>Home</method-intf>
<method-name>findAll</method-name>
<method-params />
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Reservation</ejb-name>
<method-intf>Remote</method-intf>
<method-name>getReservationId</method-name>
<method-params />
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Reservation</ejb-name>
<method-intf>Home</method-intf>
<method-name>create</method-name>
<method-params>
<method-param>java.lang.String</method-param>
<method-param>java.lang.String</method-param>
<method-param>java.lang.String</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Reservation</ejb-name>
<method-intf>Remote</method-intf>
<method-name>getCustomerId</method-name>
<method-params />
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Reservation</ejb-name>
<method-intf>Home</method-intf>
<method-name>findByPrimaryKey</method-name>
226
227
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Reservation</ejb-name>
<method-intf>Remote</method-intf>
<method-name>getCruiseId</method-name>
<method-params />
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Customer</ejb-name>
<method-intf>Remote</method-intf>
<method-name>setName</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Customer</ejb-name>
<method-intf>Remote</method-intf>
<method-name>getFirstname</method-name>
<method-params />
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Customer</ejb-name>
<method-intf>Remote</method-intf>
<method-name>setFirstname</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Customer</ejb-name>
<method-intf>Remote</method-intf>
<method-name>getName</method-name>
<method-params />
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Customer</ejb-name>
<method-intf>Home</method-intf>
<method-name>create</method-name>
<method-params>
<method-param>java.lang.String</method-param>
<method-param>java.lang.String</method-param>
<method-param>java.lang.String</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Customer</ejb-name>
<method-intf>Home</method-intf>
<method-name>findByPrimaryKey</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>TravelAgent</ejb-name>
<method-intf>Remote</method-intf>
<method-name>getRecordReservation</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>TravelAgent</ejb-name>
<method-intf>Remote</method-intf>
<method-name>travelAgentReservation</method-name>
<method-params>
<method-param>java.lang.String</method-param>
<method-param>java.lang.String</method-param>
<method-param>java.lang.String</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
3.3.11 Deployment descriptors and JAR files
227
228
Deployment descriptors sind Klassen, die ähnlich Property files die Anpassung des
Verhaltens (SessionBean or EntityBean, security, transactions) einer enterprise bean
erlauben, ohne die bean class oder die interfaces ändern zu müssen.
Für jede bean wird ein deployment descriptor erzeugt und mit den Daten der bean gefüllt. In
der Regel setzt der Anwender die Attribute der deployment descriptor class im Rahmen des
Integrated Development Environment (IDE) oder des Deployment Tools mittels Property
Sheet. Das deployment descriptor object wird dann serialisiert. Der deployment descriptor
kann von Anwendern der bean modifiziert werden.
Das deployment descriptor file ist Bestandteil des JAR Files der bean.
Das JAR File enthält die bean class, remote interface, home interface, primary key,
manifest, serialiserter deployment descriptor.
JAR`s manifest ist das Inhaltsverzeichnis des JAR Files.
Das Element <!DOCTYPE> beschreibt unter anderem die zugehörige DTD Datei. Diese
wird zur Überprüfung der Struktur des Dokumentes verwendet.
Die übrigen Elemente sind inneinander verschachtelt und jeweils durch ein Start- und EndeTag begrenzt.
Alle Elemente müssen in <ejb-jar> enthalten sein.
Alle beans sind im Element <enterprise-beans> enthalten.
In einem <entity> Element definierte beans sind entity beans.
In einem <session> Element definierte beans sind session beans.
Die <cmp-field> führen alle container managed fields in der entity bean Klasse auf. Diese
Felder werden in der Datenbank gespeichert und vom container zur Laufzeit verwaltet.
Das <assembly-descriptor> Element beschreibt die Sicherheitsrollen und
Transaktionsattribute der beans.
Das <container-transaction> Element beschreibt, dass Transaktionen „required“ werden.
3.4
Example Cruise
nur elektronisch
3.5
Container and Bean Managed Persistence of Entity Beans
3.5.1 Container Managed Persistence
Der container weiß, wie die Attribute der bean auf die Felder der Datenbank abgebildet
werden und kümmert sich um das Einfügen, Updaten und Löschen.
Die bean kann unabhängig von der Datenbank formuliert werden. Dies erhöht die
Wiederverwendbarkeit.
3.5.2
Bean Managed Persistence
Der Entwickler der bean muss den Code zur Manipulation der Daten in der Datenbank
schreiben.
Er erhält auf diese Weise eine größere Flexibilität. Jedoch bindet ihn diese Vorgehensweise
in stärkerem Maße an die spezifische Datenbank.
3.6
Stateful and stateless session beans
3.6.1 Stateful session beans
Eine stateful session bean verwaltet einen Status in Bezug auf den client.
Z.B. könnte man bei der TravelAgent bean zunächst mittels setCustomerId(), setCruiseId()
remote Referenzen zu Customer und Cruise beans erzeugt. Der nachfolgende
parameterlose createReservation() Aufruf funktioniert dann nur, wenn die erforderlichen
Referenzen vorher gesetzt wurden. Nach dem Setzen der Customer und Cruise Referenzen
befindet sich der TravelAgent daher in einem anderen Status.
Dieser Status wird als conversational state bezeichnet, da er die Conversation zwischen
bean und client repräsentiert.
228
229
Bei einer stateful session bean ändert sich der Zustand der bean im Verlaufe der
Kommunikation mit dem Client. Damit sind die Methodenaufrufe nicht mehr voneinander
unabhängig.
Stateful session beans können nicht parallel mit verschiedenen clients kommunizieren.
Eine stateful session bean ist die Erweiterung der client application. Sie führt Aufgaben im
Auftrag des client aus. Sie arbeitet als Agent des client.
Jeder Methodenaufruf des client wird daher im Prinzip von derselben instance der bean
bedient.
Stateful session beans kapseln den conversational state und die business logic auf dem
server. Die Auslagerung der business logic auf den server führt zu einem thin client und
erleichtert die Verwaltung des Systems. Die Verwaltung des workflow im Auftrag des client
führt zu einem interface, welches z.B. direkte Datenbankzugriffe vor dem client verbirgt.
Stateful session beans sind nicht persistent. Sie sind während der gesamten Lebensdauer
einem einzigen client zugeordnet und haben eine vorgegebene timeout Zeit. Bei jedem
Aufruf einer business Methode wird der timeout zurückgesetzt. Wenn der client die bean vor
dem Ablauf des timeout nicht mehr verwendet, wird die instance zerstört und die remote
reference ungültig. Der client kann die session bean natürlich auch mittels remove() explizit
zerstören.
Stateful session beans können nicht konkurrierend verwendet werden. Im Falle einer entity
bean werden alle client Anforderungen durch dasselbe EJB object koordiniert. Im Falle von
session beans ist das EJB object genau einem client zugeordnet.
3.6.2
Stateless session beans
Stateless session beans verwalten keine eigenen conversational states (vgl. aktuelle
TravelAgent Implementierung). Die Methoden sind völlig unabhängig voneinander.
Wenige stateless session beans können daher tausende von Clients parallel bedienen.
Viele EJB objects könne wenige Instanzen von stateless session beans nutzen. Stateless
session beans könne frei zwischen verschiedenen EJB object geswapped (vgl. weiter
hinten) werden.
Die beans können sich von einem Aufruf einer Methode zum nächsten keine Informationen
merken. Jedoch bedeutet dies nicht, dass die beans keine Attribute haben dürfen. Eine
stateless bean kann z.B. eine Referenz auf ein Debugfile halten. Der Ablauf der Methoden
darf jedoch nicht von dem Wert der internen Attribute abhängen. Die internen Attribute sind
für den Client nicht sichtbar. Der client kann nicht davon ausgehen, beim nächsten Aufruf
von derselben bean bedient zu werden.
Die Eigenschaft stateless wird über eine Property der SessionDescriptor Klasse definiert.
3.7
Resource Management
3.7.1 Problem
EJBServer müssen so konstruiert sein, dass sie ggf. Millionen von beans verwalten können.
3.7.2
Pooling
Eine Technik zur Verbesserung der Performance besteht darin, einen pool der benötigten
Instanzen der beans anzulegen. Clients werden dann aus diesem Pool bedient. Es ist sehr
viel weniger aufwendig, Instanzen aus diesem Pool wiederzuverwenden als permanent
neue Instanzen zu erzeugen und wieder zu löschen. Ein EJBServer verwaltet instance pools
für jeden Typ von bean.
Da die Clients ohnehin nie direkt auf die beans zugreifen können sondern nur über wrapper
EJB objects, besteht keine Notwendigkeit, für jeden der Millionen Clients eine bean im
Speicher bereit zu halten. Der EJBServer hält in seinem Pool stattdessen eine viel kleinere
Zahl von beans zu Erledigung der von den Clients anfallenden Aufträge.
229
230
Die Größe des pools kann darüberhinaus dem aktuellen Bedarf angepasst werden.
3.7.3
Swapping
Während Pausen können bean instances an den pool zurückgegeben werden. Man spricht
in diesem Falle von instance swapping.
Im Falle von stateless session beans bereitet es keine Probleme, wenn ein Client zwischen
zwei Methodenaufrufen eine andere bean instance zugewiesen bekommt.
Bild Swapping
Entity beans partizipieren ebenfalls an dem Mechanismus des instance swapping. Sie
haben keinen conversational state, der zwischen zwei Aufrufen gemerkt werden muss. Ihr
Status in Form der Daten ist direkt in der Datenbank gespeichert.
3.7.4
3.7.4.1
The Life Cycle of an Entity Bean
States
EJB definiert die states der entity bean über die Beziehung zum instance pool.
// Einfügen
230
231
Bild Zustände der bean
3.7.4.2
Does Not Exist
Beginn und ende des life cycles
Die bean ist nicht instanziert.
Die bean ist eine Sammlung der Files, die zur Entwicklungszeit der bean erzeugt wurden,
z.B. bean, remote interface, home interface, serialiserter EntityDescriptor usw.
3.7.4.3
Pooled
Die bean ist Instanziert aber noch nicht mit einem EJBObject assoziiert.
Wenn der EJB Server gestartet wird, instanziiert er mehrere Instanzen der bean und plaziert
sie in dem instance pool.
Die Instanzen werden mittels Class.newInstance() erzeugt. newInstance() verwendet den
Default-Konstruktor ohne Argumente. Damit erhalten die persistent fields ihre DefaultWerte. Die Instanz repräsentiert in diesem Zustand keine Daten der Datenbank.
Direkt nach der Erzeugung und vor dem Einfügen in den pool wird der bean mittels
setEntityContext() ihr context zugewiesen. Die beans in dem instance pool sind inaktiv. Es
sei denn sie werden für einen find() request verwendet. find() requests erfordern keinen
definierten Zustand der bean.
3.7.4.4
Ready
Die bean ist instanziert und mit einem EJBObject assoziiert.
Die bean instance ist ready für Anforderungen über Methodenaufrufe.
3.7.4.4.1
Transition from pooled to ready via creation
// Einfügen
Bild Pool
Wenn ein client die create() Methode von EJB home aufruft, wird ein EJB object auf dem
server erzeugt, diesem EJB object eine bean des instance pools zugewiesen, die zur
gerufenen create() Methode gehörende ejbCreate() methode der bean instance gerufen, ein
primary key erzeugt und in das EJB object eingebettet. Durch den primary key erhält das
EJB Object seine Identität.
Im Falle von bean managed persistence (vgl. weiter hinten), erzeugt die bean selbst den
primary key im Rahmen von ejbCreate() und liefert ihn an den EJB Server zurück.
Nach dem Ablauf der ejbCreate() methode steht die Context Information zur Verfügung.
231
232
Anschließend wird die ejbPostCreate() Methode aufgerufen. In dieser Methode kann die
bean Initialiserungen vornehmen, welche auf dem Kontext basieren.
Schließlich übermittelt der EJB Server dem client ein stub object als remote reference.
Damit geht die zugewiesene bean instance in den ready state über. Die bean instance kann
nun requests des client und callbacks des EJBServers empfangen.
3.7.4.4.2
Transition from pooled to ready via activation
Activation ermöglicht wenigen bean instances viele clients zu bedienen. Wenn eine bean
instance zum pool zurückkehrt, hinterlässt sie das EJB object ohne instance. Das EJB
object unterhält seine Verbindung zum stub auf client Seite. Aus der Sicht des client hat sich
daher die entity bean nicht verändert. Sowie der client eine business methode aufruft, erhält
das EJB object eine neue bean instance.
Wenn also eine bean instance aktiviert wird, verlässt sie den instance pool und wird einem
EJB object zugewiesen. Nach der Zuweisung an ein EJB object ist der EntityContext in der
Lage, Informationen zu liefern, die spezifisch für das EJB object sind. Direkt nach der
Zuweisung wird vom EJB Server die ejbActivate() Callback-Methode gerufen. Im Rahmen
dieser Methode kann die bean Arbeiten verrichten, die für die Bedienung des Client
erforderlich sind.
Im Falle von container managed beans werden anschließend die persistent fields vom
container aus der Datenbank gefüllt. Anschließend wird die bean mittels ejbLoad() über
diesen Vorgang informiert.
Im Falle von bean menaged persistence hat die bean selbst im Rahmen von ejbLoad() ihre
persistent fields zu füllen.
3.7.4.4.3
Transition from ready to pooled via passivation
Passivation ist der Prozess der Trennung der bean instance von ihrem EJB object. Der
container kann die Trennung zu jeder Zeit vornehmen, es sei denn, die instance führt
gerade eine Methode aus. Als Teil des passivation process ruft der container die callbackMethode ejbPassivate() auf. Auf diese Weise erhält die bean Gelegenheit, Resourcen frei
zu geben.
Die Methode dient nicht zum Abspeichern der persistent fields im Falle von bean managed
persistence. Diese Abspeicherung erfolgt im Rahmen der Methode ejbStore(), die vor
ejbPassivate() aufgerufen wird.
3.7.4.4.4
Transition from ready to pooled via removal
Wenn der client die bean nicht mehr benötigt, ruft er eine remove() Methode für die bean.
Dies hat zur Folge, dass die bean instance von dem EJBObject disazzoziiert und an den
instance pool zurüchgegeben wird.
Eine bean instance gelangt also auch in den pooled state, wenn der client eine der remove()
Methoden aufruft.
Im Falle von entity beans werden die Daten aus der Datenbank entfernt.
Die Information bezüglich der Identität der bean ist während der Ausführung der callbackMethode ejbRemove() noch über den EntityContext erreichbar. Die ejbRemove() Methode
muss alle Resourcen frei geben, die auch im Rahmen von ejbPassivate() freizugeben sind.
Z.B. kann man die ejbPassivate() Methode aus der ejbRemove() Methode aufrufen.
3.7.4.4.5
Anmerkungen
ejbLoad() und ejbStore() können nur im ready state gerufen werden. Manche EJB Server
rufen vor jedem Methodenaufruf ejbLoad() und nach jedem Methodenaufruf ejbStore(). Die
callBack-Methode ejbLoad() wird im Falle von container managed beans direkt nach dem
232
233
Laden der persistent fields aus der Datenbank gerufen und die Methode ejbStore() direkt
vor dem Speichern der persistent fields in die Datenbank.
Die callback-Methoden ejbCreate() und ejbRemove() können verwendet werden, um Daten
in die Datenbank einzufügen bzw. zu löschen.
Das Leben einer bean instance endet, wenn der container die Instanz aus dem pool entfernt
und dem Garbage Collector übergibt. Dies geschieht z.B., wenn der container die Anzahl
der bean instances dem Bedarf anpasst, wenn der EJB Server heruntergefahren wird, wenn
eine bean sich unnormal verhält.
Mitels der callback-Methode unsetEntityContext() informiert der container die bean über die
bevorstehende Zerstörung. Vor der garbage collection wird schließlich noch wie für jedes
andere java object die finalize() Methode gerufen.
3.7.5
The Life Cycle of a Stateless Session Bean
3.7.5.1
States
Bild life cycle stateless session bean
3.7.5.2
DoesNotExist
Es existiert keine Instanz im Speicher
3.7.5.3
Method-Ready Pool
Der container wird in Abhängigkeit vom Bedarf Instanzen erzeugen oder vernichten.
3.7.5.3.1
Transition to the Method-Ready Pool
Die bean wird mittels Class.newInstance() instanziiert. Dies erfordert einen Konstruktor
ohne Argumente. Die Initialisierung findet im Rahmen von ejbCreate() statt.
Mittels setSessionContext() wird die Referenz auf den EJBContext gesetzt.
Die ejbCreate() Methode wird gerufen. Dies geschieht nur einmal während der Lebensdauer
der bean. Im Unterschied zu entity beans und stateful session beans wird ein create() Aufruf
des client nicht an die Methode ejbCreate() der bean delegiert.
Eine Socket-Verbindung kann also z.B. im Rahmen von ejbCreate() geöffnet, während der
gesamten Lebensdauer beibehalten und im Rahmen von ejbRemove() geschlossen
werden.
233
234
3.7.5.3.2
Life in the Method-Ready Pool
Wenn ein client eine business Methode ruft wird dieser Aufruf an irgend eine verfügbare
Instanz im Method-Ready Pool delegiert. Während die Instanz die Anforderung ausführt, ist
sie für keinen weiteren Aufruf verfügbar. Stateless session beans werden einem EJB object
lediglich für die Dauer eines einzigen Methodenaufrufes zugewiesen.
Es ist nicht vorhersehbar, welche instance die Anforderung des client bedient.
Die callback-Methoden ejbActivate() bzw. ejbPassivate() werden im Falle einer stateless
session bean nicht aufgerufen. Ein create() Aufruf des client führt nicht zu einem Aufruf von
ejbCreate().
3.7.5.3.3
Transition out of the Method-Ready Pool
Eine stateless session bean stirbt, wenn der Server entscheidet, die Anzahl der Instanzen
zu reduzieren.
An dieser Stelle wird die callback-Methode ejbRemove() gerufen, in der die bean ihre
Resourcen freigeben kann.
Ein remove() Aufruf des client bewirkt hingegen nur die Freigabe des stubs und des EJB
objects. Nach der ejbRemove() methode wird die bean dem garbage collector übergeben.
Im Falle einer stateless session bean wird also durch eine timeout oder remove() Operation
lediglich die remote reference zerstört jedoch nicht die bean instance. Diese wird für andere
clients weiterverwendet.
3.7.6
3.7.6.1
The Life Cycle of a Stateful Session Bean
The Activation Mechanism
Der größte Unterschied zu den anderen beans besteht darin, dass kein instance pooling
betrieben wird. Stateful session beans sind während ihres gesamten Lebens einem client
gewidmet.
An Stelle des pooling werden sie lediglich aus dem Speicher entfernt, um Resourcen zu
sparen.
Eine solche bean passivated und wieder activated.
Bild activation of stateful session beans
234
235
Zu diesem Zweck wird im Rahmen der Passivierung deren conversational state z.B. mittels
Java Serialiserung gespeichert, dem EJBObject zugeordnet und die stateful session bean
instance zerstört.
Dabei bleibt das EJB object mit dem client verbunden. Die bean instance wird jedoch dem
garbage collector übergeben.
Im Rahmen der Aktivierung wird eine neue Instanz der session bean erzeugt, dem
EJBObject zugeordnet und mit den gespeicherten Daten gefüllt.
Die callback Methoden ejbActivate() und ejbPassivate() informieren die bean über die
Vorgänge und erlauben der bean notwendige Arbeiten vor der Passivierung oder nach der
Aktivierung durchzuführen.
3.7.6.2
States
Bild States of a Stateful Session Bean
3.7.6.3
Does Not Exist
Es existiert keine Instanz im Speicher
3.7.6.4
Method Ready
3.7.6.4.1
Transition to Method Ready
Nach dem Aufruf der create() Methode erzeugt der container mittels newInstance() eine
bean instance, weist diese einem EJB object zu, setzt mittels setSessionContext() den
EntityContext und ruft ejbCreate(). Nach der Rückkehr dieser Funktion liefert er eine remote
reference an den client.
Nun ist die bean bereit, Aufrufe des client zu bedienen.
3.7.6.4.2
Transition to Does Not Exist
Der Übergang in den Does not Exist state findet statt, wenn die bean removed wird. Der
Anwender kann eine der remove() Methoden aufrufen. Der Container kann die bean
entfernen, wenn ein timeout abgelaufen ist. Die bean erhält die Gelegenheit, im Rahmen
von ejbRemove() Resourcen zu schließen oder referenzierte beans zu entfernen.
3.7.6.5
3.7.6.5.1
Passivated
Transition to Passivated
235
236
Wenn eine stateful session bean in diesen Zustand versetzt wird, werden die instance fields
der bean gelesen und auf ein sekundäres Speichermedium geschrieben, welches dem EJB
object zugeordnet ist.
Mittels der callback-Methode ejbPassivate() wird die bean darüber informiert, dass sie in
den passiven Zustand versetzt wird. Die bean sollte im Rahmen dieser Funktion alle offenen
Resourcen schließen und ihre nontransient, nonserializable fields auf 0 setzen.
Der conversational state muss aus primitve values oder serializable objects bestehen. Der
container kann dazu den Java Serialisierungsmechanismus nutzen. Referenzen auf andere
beans und die Referenz auf den SessionContext werden vom container gespeichert und bei
der Aktivierung wieder restauriert.
Nichtserialisierbare Objektreferenzen und transiente variablen bleiben nicht erhalten.
Felder, die nonserializable und nontransient sind müssen auf 0 gesetzt werden.
3.7.6.5.2
Transition to Method Ready
Wenn der client eine Methode einer passivated bean ruft, wird diese aktiviert.
SessionContext und bean referenzen werden restauriert. Anschließend wird die bean mittels
ejbActivate() über die Aktivierung informiert. Die bean kann im Rahmen dieser Methode
Resourcen öffnen und transiente Felder initialisieren. Im Gegensatz zum Java
Serialisierungsstandard werden transiente Felder nicht auf Default-Werte gesetzt. Sie
können nach der Aktivierung beliebige Werte enthalten uns sollten daher im Rahmen von
ejbActivate() sinnvoll initialisert werden.
3.8
Primary Services
3.8.1 Overview
Die OMG hat 13 frameworks definiert, die als services bezeichnet werden. 6 von diesen
werden als primary services bezeichnet. Diese sind erforderlich für einen effektiven Betrieb
des EJBServers.
Die primary services umfassen concurrency, transactions, persistence, distributed objects,
naming and security.
Einige dieser services werden im Folgenden ausführlicher dargelegt.
3.8.2
Concurrency
Session beans
Eine stateful session bean verwaltet conversational states. Daher wäre ein konkurrierender
Zugriff durchaus problematisch. Jedoch wird sie nur von demjenigen client verwendet, der
sie auch erzeugt hat. Daher muss sie nicht geschützt werden. Eine stateful session bean ist
immer genau einem client zugeordnet.
Eine stateless session bean hat keine inneren Zustände. Daher bedarf sie ohnehin keines
Schutzes.
Entity beans
Entity beans repräsentieren Daten, z.B. eine Kreuzfahrt. Diese Daten werden durchaus von
mehreren clients, z.B. Reisebüros, konkurrierend angefasst. Entity beans sind daher shared
components.
EJB verhindert den konkurrierenden Zugriff auf eine bean instance. Verschiedene clients
können sehr wohl mit ein und demselben EJBObject verbunden sein. Jedoch hat nur ein
client thread Zugriff auf die bean instance. Wenn also ein Client eine Methode der bean
aufruft, hat ein anderer client erst dann wieder die Möglichkeit für einen Zugriff, wenn die
Methode beendet wurde. Ein entsprechender Schutz existiert über einzelne
Methodenaufrufe hinaus für Transaktionen.
// Einfügen
236
237
Bild Concurrent Access
Bedingt durch diese automatische Behandlung des konkurrierenden Zugriffs müssen die
bean Methoden nicht thread-safe gemacht werden.
Die EJB Spezifikation verhindert sogar die Verwendung des Schlüsselwortes synchronized.
Achtung :
Bean instances sind nicht reentrant-fähig. Ein loopback tritt auf, wenn eine bean A eine
Methode von bean B ruft und dann B versucht einen callback zu A zu machen. Bean A
unterscheidet nämlich nicht zwischen dem callback von B (reentarnt code) und einem
Versuch eines anderen Clients, eine Methode aufzurufen (multithreaded access).
Die fehlende reentrant-fähigkeit bezieht sich nur auf die Kommunikation mit anderen beans.
// Einfügen
Bild reentrant
3.8.3
Persistence
Der Status einer entity bean wird permanent in einer Datenbank gespeichert.
Wenn der container für die Synchronisation der bean felder mit den Datenbankeinträgen
verantwortlich ist, spricht man von container managed persistence. Im anderen Falle spricht
man von bean managed persistence.
Object to relationale persistence
Die entity bean fields werden auf Tabellen und spalten einer relationalen Datenbank
abgebildet.
237
238
Bild Mapping Attributes to Relational Database
Die Anforderung an EJBHome zur Erzeugung oder Vernichtung einer entity bean hat die
Erzeugung oder Vernichtung eines Datenbank-Records zur Folge.
Java objects lassen sich nicht immer nahtlos auf relationale Datenbanktabellen abbilden.
Object database persistence
Objektorientierte Datenbanken speichern Objekt Typen und Objekt Graphen. In einer OO
Sprache geschriebene Komponenten lassen sich sauberer auf die Datenbank abbilden.
Diese Datenbanken handeln zirkulare Referenzen.
Jedoch sind Objektdatenbanken noch nicht soweit standardisiert wie relationale und es
existieren weniger third-party Produkte zur Unterstützung.
3.8.4
Distributed Objects
Beispiel für ein remote interface
public interface TravelAgent extends EJBObject {
public Reservation travelAgentReservation(String idRes, String
idCruise, String idCustomer)
throws RemoteException, DuplicateKeyException,
CreateException;
public Reservation getRecordReserv(String idRes) throws
RemoteException;
}
RMI fordert, dass alle Parameter und Returnwerte entweder primitive Jafa Typen (int,
double, byte ...) sein müssen oder aber serialisierbare Objekte. Serialisierbare Objekte
können über das Netz auf einen remote Computer kopiert werden.
Änderungen an der remote Kopie werden im Orginal nicht reflektiert.
238
239
Bild passing serializable objects
Achtung : Rechts muss stehen
Copy of serializable object
Über diese Technik kann man auch remote referenzen auf beans übertragen. Eine remote
reference ist ein Remote interface, welches durch ein EJBObject als stub implementiert
wird. Wenn eine solche remote reference als Parameter oder Returnwert einer Methode
verwendet wird, so wird der stub per kopie über das Netz transportiert. Die Kopie des
EJBObject stub zeigt auf dieselbe bean. Genaugenommen zeigt der stub auf das Wrapper
EJBObject der bean.
Bild passing remote references
3.8.5
Naming
Locating Beans with JNDI
Beispielcode für die Verwendung einer bean.
239
240
InitialContext initial = new InitialContext();
Object objref = initial.lookup("reservation");
...
record = hreservation.findByPrimaryKey(idHilfe);
...
theReservation = hreservation.create(idReservation, idCruise, idCustomer);
Die Referenz auf EJBHome für das cruise bean erhält man über die Verwendung des Java
Naming and Directory Interface (JNDI).
JNDI erlaubt einem application client, den EJBServer als eine Menge von directories wie in
einem gewöhnlichen File System zu sehen.
Der initial context ist der Startpunkt der Suche, vergleichbar mit der root eines File Systems.
Alle EJBServer müssen unabhängig vom Hersteller den JNDI Zugriff für ihren speziellen
naming und directory service unterstützen.
240
Herunterladen