Software Engineering I/II - Fakultät Informatik/Mathematik
Software Engineering I/II - Fakultät Informatik/Mathematik
Software Engineering I/II - Fakultät Informatik/Mathematik
Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.
YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.
<strong>Software</strong> <strong>Engineering</strong> I/<strong>II</strong><br />
Skript zur Lehrveranstaltung<br />
Hartmut Fritzsche<br />
Hochschule für Technik und Wirtschaft Dresden<br />
<strong>Fakultät</strong> <strong>Informatik</strong>/<strong>Mathematik</strong><br />
11. Dezember 2013
Inhaltsverzeichnis<br />
1 Einführung 4<br />
1.1 Aufbau der Lehrveranstaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . 4<br />
1.2 Ziele und Arbeitsmethoden der <strong>Software</strong>technik . . . . . . . . . . . . . . . . 4<br />
1.2.1 Was ist <strong>Software</strong>technik? . . . . . . . . . . . . . . . . . . . . . . . . . 4<br />
1.2.2 Methodologie der <strong>Software</strong>entwicklung . . . . . . . . . . . . . . . . . . 5<br />
1.3 <strong>Software</strong>entwicklungswerkzeuge . . . . . . . . . . . . . . . . . . . . . . . . . . 7<br />
1.3.1 Klassifikation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7<br />
1.3.2 Die Entwicklungsplattform Eclipse . . . . . . . . . . . . . . . . . . . . 8<br />
1.3.3 Werkzeuge zur UML-Unterstützung . . . . . . . . . . . . . . . . . . . 12<br />
1.3.4 Diagrammaustausch . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16<br />
1.3.5 Werkzeuge zur Versionsverwaltung . . . . . . . . . . . . . . . . . . . . 16<br />
1.3.6 Werkzeuge für das Build-Management . . . . . . . . . . . . . . . . . . 17<br />
2 Der <strong>Software</strong>entwicklungsprozess 18<br />
2.1 Die Phasen im <strong>Software</strong>entwicklungsprozess . . . . . . . . . . . . . . . . . . . 18<br />
2.2 Lebenszyklus-Modelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19<br />
2.3 Prozessmodellierung und Projektmanagement . . . . . . . . . . . . . . . . . . 19<br />
2.4 Qualitätssicherung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21<br />
2.4.1 Ziele und Maßnahmen . . . . . . . . . . . . . . . . . . . . . . . . . . . 21<br />
2.4.2 Testen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22<br />
2.4.3 Validation und Verifikation . . . . . . . . . . . . . . . . . . . . . . . . 22<br />
3 Anforderungsanalyse und -definition 24<br />
3.1 Das Pflichtenheft . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24<br />
3.2 Spezifikationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25<br />
3.2.1 Formale Beschreibungsmittel . . . . . . . . . . . . . . . . . . . . . . . 25<br />
3.3 Anwendungsfall-Analyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25<br />
4 Objektorientierte Modellierung 28<br />
4.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28<br />
4.2 Klassenbildung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28<br />
4.2.1 Klassen - Instanzen - Abstraktionen . . . . . . . . . . . . . . . . . . . 28<br />
1
INHALTSVERZEICHNIS 2<br />
4.2.2 Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29<br />
4.2.3 Operationen und Methoden . . . . . . . . . . . . . . . . . . . . . . . . 29<br />
4.2.4 Grafische Notation für Klassen und Objekte . . . . . . . . . . . . . . . 30<br />
4.2.5 Sichtbarkeit von Attributen und Operationen . . . . . . . . . . . . . . 31<br />
4.2.6 Klassen als Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32<br />
4.3 Assoziationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32<br />
4.3.1 Assoziation und Verknüpfung . . . . . . . . . . . . . . . . . . . . . . . 32<br />
4.3.2 Rollen und Multiplizität . . . . . . . . . . . . . . . . . . . . . . . . . . 34<br />
4.3.3 Aggregation und Komposition . . . . . . . . . . . . . . . . . . . . . . 35<br />
4.4 Generalisierung und Spezialisierung . . . . . . . . . . . . . . . . . . . . . . . . 36<br />
4.4.1 Die Inklusionsrelation . . . . . . . . . . . . . . . . . . . . . . . . . . . 36<br />
4.4.2 Vererbung und Delegation . . . . . . . . . . . . . . . . . . . . . . . . . 38<br />
4.4.3 Polymorphie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39<br />
4.4.4 Abstrakte und konkrete Klassen . . . . . . . . . . . . . . . . . . . . . 40<br />
4.4.5 Parametrisierte Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . 41<br />
4.5 Dynamische Modellierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44<br />
4.5.1 Instanzen als Laufzeitobjekte . . . . . . . . . . . . . . . . . . . . . . . 44<br />
4.5.2 Interaktionsmodelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45<br />
4.5.3 Statechart-Modelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45<br />
4.5.4 Aktivitäts-Diagramme . . . . . . . . . . . . . . . . . . . . . . . . . . . 51<br />
5 Von UML nach Java ... 56<br />
5.1 Applikationen und Applets . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56<br />
5.2 Baum-Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59<br />
5.3 Interfaces und Mehrfachvererbung . . . . . . . . . . . . . . . . . . . . . . . . 63<br />
5.4 Implementation von Aggregationen: Vektoren und Hash-Tabellen . . . . . . . 67<br />
5.4.1 Vektoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67<br />
5.4.2 Hash-Technik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67<br />
5.5 Dynamische Modelle und Event-Handling . . . . . . . . . . . . . . . . . . . . 69<br />
5.6 Fehler und Ausnahmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74<br />
5.7 Parallelität durch Multithreading . . . . . . . . . . . . . . . . . . . . . . . . 77<br />
6 Entwurf und Implementation 80<br />
6.1 Modellmanagement und Systementwurf . . . . . . . . . . . . . . . . . . . . . 80<br />
6.1.1 Packages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80<br />
6.2 Komponentendiagramme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91<br />
6.3 Verteilungsdiagramme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93<br />
6.4 Objektserialisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95<br />
6.5 Modell-/Diagrammaustausch . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
INHALTSVERZEICHNIS 3<br />
7 Konfigurations-, Test- und Dokumentationsmanagement 96<br />
7.1 Der KM-Prozess . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96<br />
7.2 Versionsverwaltungssysteme . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97<br />
7.3 Aufbau eines Build-Prozesses . . . . . . . . . . . . . . . . . . . . . . . . . . . 105<br />
7.4 Ant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105<br />
7.4.1 Nutzung von Ant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105<br />
7.4.2 Erstellung von Ant-Skripten . . . . . . . . . . . . . . . . . . . . . . . . 106<br />
7.5 Maven . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112<br />
7.6 Zustandsbasiertes Testen mit JUnit und JUnitDoclet . . . . . . . . . . . . . . 117<br />
7.7 Generieren von Dokumentationen mit Javadoc . . . . . . . . . . . . . . . . . 122<br />
7.8 Signieren von Java-Archiven . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125<br />
8 Wiederverwendung und Evolution 126<br />
8.1 Individuelle Komponenten: Module und Klassen . . . . . . . . . . . . . . . . 126<br />
8.2 Komponentenbasierte <strong>Software</strong>entwicklung - Frameworks . . . . . . . . . . . 127<br />
8.3 Entwurfsmuster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139<br />
9 Musterarchitekturen 145<br />
9.1 Die MVC-Architektur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145<br />
9.2 Grafische Benutzeroberflächen . . . . . . . . . . . . . . . . . . . . . . . . . . . 146<br />
9.2.1 Java AWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146<br />
9.2.2 Die Layout-Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147<br />
9.2.3 Swing 1.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148<br />
9.2.4 Entwurf eines Swing–GUI . . . . . . . . . . . . . . . . . . . . . . . . 149<br />
9.2.5 JTree - Anwendung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150<br />
9.2.6 SWT: Das Standard Widget Toolkit . . . . . . . . . . . . . . . . . . . 151<br />
9.2.7 Einbettung von Swing-Komponenten in SWT . . . . . . . . . . . . . . 157<br />
9.2.8 JFace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161<br />
9.3 Client-Server-Systeme mittels http und Servlets . . . . . . . . . . . . . . . . . 162<br />
9.4 Datenbankanbindungen an Java-Applikationen . . . . . . . . . . . . . . . . . 166<br />
9.5 Server2Go . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168<br />
Literaturverzeichnis 172
Kapitel 1<br />
Einführung<br />
1.1 Aufbau der Lehrveranstaltung<br />
Die Lehrveranstaltung umfasst die Module ”<br />
<strong>Software</strong> <strong>Engineering</strong> I” und ”<br />
<strong>Software</strong> <strong>Engineering</strong><br />
<strong>II</strong>” und erstreckt sich über zwei Semester. Die Vorlesung ist insgesamt in acht Kapitel<br />
gegliedert. Grundsätzlich orientiert sich die Gliederung an den Phasen des <strong>Software</strong>entwicklungsprozesses.<br />
Einen Schwerpunkt im ersten Teil bildet das Kapitel 4 über die objektorientierte<br />
Modellierung.<br />
Basis der praktischen Arbeit in der Lehrveranstaltung (in Form eines wöchentlichen Praktikums)<br />
bildet die Entwicklungsplattform Eclipse, ergänzt um mehrere Plug-ins. Einzelheiten<br />
zu den verwendeten Plug-ins und die jeweiligen Installationen werden im Abschnitt 1.3<br />
besprochen. Im Rahmen der praktischen Ausbildung arbeitet jeder Studierende in einem<br />
Gruppenprojekt an einer konkreten <strong>Software</strong>entwicklungsntwicklungsaufgabe mit.<br />
Literatur zur Lehrveranstaltung ist themenbezogen im Skript angegeben. Publikationen<br />
zu den Grundlagen der <strong>Software</strong>technik sind [Som07], [Bal05], [BES08], [Lar05] und [Dum03].<br />
1.2 Ziele und Arbeitsmethoden der <strong>Software</strong>technik<br />
1.2.1 Was ist <strong>Software</strong>technik?<br />
Die <strong>Software</strong>technik (engl. <strong>Software</strong> <strong>Engineering</strong>, deutsch auch als <strong>Software</strong>technologie bezeichnet)<br />
ist die Lehre vom Prozess der organisierten <strong>Software</strong>produktion. Sie umfasst (die<br />
Erforschung von) Theorie, Prinzipien, Methoden und Werkzeuge(n) zur Herstellung und<br />
zur Wartung (Anpassung, Weiterentwicklung) von <strong>Software</strong>. Das schliesst Maßnahmen zur<br />
analytischen und konstruktiven Qualitätssicherung, die Nachnutzung bereits vorhandener<br />
<strong>Software</strong> sowie das Projektmanagement und das Konfigurationsmanagement ein.<br />
<strong>Software</strong>entwicklung ist ein Prozess, der methodisch gestaltet werden muss und dabei<br />
entscheidend von den eingesetzten Entwicklungswerkzeugen (engl. tools) geprägt wird. Diese<br />
Werkzeuge werden zunehmend wirkungsvoller, je besser der <strong>Software</strong>entwicklungsprozess<br />
verstanden wird. Das wiederum ermöglicht die Weiterentwicklung angewendeter Methoden.<br />
4
1.2. ZIELE UND ARBEITSMETHODEN DER SOFTWARETECHNIK 5<br />
<strong>Software</strong>entwicklungsmethoden sind somit nach wie vor Gegenstand intensiver Forschung<br />
(Beispiel: ”<br />
Modellgetriebene <strong>Software</strong>entwicklung”).<br />
Die Lehrveranstaltung setzt Programmierkenntnisse voraus, letztlich geht es um die Entwicklung<br />
funktionsfähiger Anwendungssysteme. Während es in der Programmierung eher um<br />
die Entwicklung von Algorithmen geht, tritt jetzt ein ganzes Anwendungsfeld (eine ”<br />
Domäne”)<br />
in den Fokus der Betrachtung. Abbildung 1.2.1 verdeutlicht, um welche Aspekte es in<br />
der <strong>Software</strong>technik und damit auch in der Lehrveranstaltung geht. Die Abkürzung ”<br />
CASE”<br />
steht für Computer Aided <strong>Software</strong> <strong>Engineering</strong>. Es wird zwischen Lower Case- und Upper<br />
Case-werkzeugen unterschieden.<br />
Abbildung 1.2.1: Wesentliche Aspekte der <strong>Software</strong>technik<br />
1.2.2 Methodologie der <strong>Software</strong>entwicklung<br />
Es gibt nicht eine Entwicklungsmethode, sondern eine Vielzahl von Methoden. Die Lehre von<br />
den Methoden bezeichnet man allgemein als ”<br />
Methodologie”. Die in der Lehrveranstaltung<br />
schwerpunktmäßig behandelte Methodik gründet sich auf die Objekttechnologie. Von führenden<br />
<strong>Software</strong>technologen (Ivar Jacobson, James Rumbaugh und Grady Booch) wurde mit<br />
der Unified Modeling Language (UML) eine einheitliche grafische Modellierungs- und Entwurfssprache<br />
entwickelt, die in wesentlichen Teilen ”<br />
objektbasiert” ist die und eine geeignete
1.2. ZIELE UND ARBEITSMETHODEN DER SOFTWARETECHNIK 6<br />
Zahl von Modelltypen unterstützt. Die UML hat seit etwa Anfang der 1990er Verbreitung<br />
gefunden und wird weiterentwickelt. Die Objekttechnologie hat sich damit gegenüber z.B.<br />
der Structured Analysis (SA) und der Structured Analysis and Design Technique (SADT)<br />
durchgesetzt.<br />
Ein wesentlicher Aspekt bei der Einführung der Objekttechnologie in der <strong>Software</strong>technik<br />
war die Standardisierung der sprachlichen Darstellungsmittel. Es wurde also bewusst nicht<br />
die Methodik standardisiert (wie anfangs beabsichtigt), sondern die sprachlichen Mittel.<br />
Seit Anfang der 1990er Jahre bildet die UML (aktuell UML 2) einen Quasi-Standard<br />
in der <strong>Software</strong>technik. Mit der UML werden Modelle unterschiedlicher Art entwickelt, die<br />
jeweils Ausschnitte einer zu modellierenden angenommenen oder realen Welt widerspiegeln.<br />
Zur Darstellung der Modelle unterschiedlicher Art dienen jeweils Diagramme. Diagramme<br />
visualisieren und formalisieren Ausschnitte aus bzw. Sichten auf Modelle.<br />
Die UML 2 definiert die nachfolgend beschriebenen 14 Diagrammtypen.<br />
Strukturdiagramme:<br />
• Klassenstruktur-Diagramm (static structure diagram)<br />
• Objekt-Diagramm (object diagram)<br />
• Kompositionsstruktur-Diagramm (composite structure diagram)<br />
• Komponenten-Diagramm (component diagram)<br />
• Verteilungs-Diagramm (deployment diagram)<br />
• Paket-Diagramm (package diagram)<br />
• Pofil-Diagramm (profile diagram)<br />
Verhaltensdiagramme:<br />
• Anwendungsfall-Diagramm (use case diagram)<br />
• Aktivitäts-Diagramm (activity diagram)<br />
• Interaktionsübersichts-Diagramm (interaction overview diagram)<br />
• Sequenz-Diagramm (sequence diagram)<br />
• Zustands-Diagramm (state diagram)<br />
• Zeitverlaufs-Diagramm (timing diagram)<br />
• Kommunikations-Diagramm (communication diagram)<br />
Dabei ist es durchaus möglich, dass ein Diagramm grafische Elemente enthält, die per Definition<br />
zu unterschiedlichen Diagrammtypen gehören.
1.3. SOFTWAREENTWICKLUNGSWERKZEUGE 7<br />
Charakterisierung der grafischen Modellierungssprache: In der UML-Notation werden 4<br />
Arten grafischer Konstrukte verwendet: Icons, 2-dimensionale Symbole, Pfade und Strings.<br />
Im Sinne eines Graphen bilden zweidimensionale Symbole Knoten, die durch Pfade als Kanten<br />
verbunden sein können. Strings besitzen (mit Ausnahme von Kommentaren) eine Syntax,<br />
sie können singulär auftreten oder anderen Sprachelementen zugeordnet sein.<br />
Die UML ist eine formale Sprache. Die Definition der Syntax (und evtl. der Semantik)<br />
einer formalen Sprache erfolgt mit den Mitteln einer (formalen) Sprache. Diese Sprache<br />
heißt Metasprache und dient zur Definition einer Objektsprache. Die UML wird auch als<br />
Metasprache zur Modellierung von UML-Konzepten selbst eingesetzt (Metamodellierung).<br />
1.3 <strong>Software</strong>entwicklungswerkzeuge<br />
1.3.1 Klassifikation<br />
Am Anfang steht die Werkzeug-Zeug-Metapher: ”<br />
Werkzeuge” bearbeiten ”<br />
Zeug”. ”<br />
Zeug” sind<br />
Daten und ”<br />
Werkzeuge” sind Programme, die Daten verarbeiten und produzieren. So gesehen<br />
sind alle Applikationen Werkzeuge. Als ”<br />
CASE-Tools” bezeichnen wir speziell die Art<br />
von Werkzeugen, die den <strong>Software</strong>entwicklungsprozess unterstützen (andere Berufsgruppen<br />
setzen auch Werkzeuge ein).<br />
Historisch gesehen waren Text-Editoren, Compiler bzw. Assembler für Programmiersprachen<br />
und Programmverbinder die ersten <strong>Software</strong>entwicklungswerkzeuge. Dazu kamen Debugger<br />
und diverse Werkzeuge zur Unterstützung des Testens von <strong>Software</strong>, z.B. Testdatengeneratoren.<br />
Da der <strong>Software</strong>entwickler sich um die Anwendung jedes einzelnen Werkzeuges<br />
selbst kümmern muss, bezeichnen wir diese Art von Werkzeugen als Einzelwerkzeuge. Die<br />
Benutzung von Einzelwerkzeugen erfolgt in der Regel direkt im Betriebssystem über das<br />
Graphical User Interface (GUI) oder über ein Kommandozeilen-Werkzeug. Zum Teil nutzen<br />
<strong>Software</strong>entwickler noch heute Einzelwerkzeuge.<br />
Die Technologie der Herstellung von <strong>Software</strong> entwickelte sich weiter und es wurden Programmierumgebungen<br />
angeboten. Charakteristisch für Programmierumgebungen ist, dass der<br />
Quellcode der Ausgangspunkt für die Erzeugung lauffähiger Anwendungen ist. Programmierumgebungen<br />
sind typischerweise Menü-gesteuert, über Schaltflächen sind die Werkzeugfunktionen<br />
aktivierbar. Beispiele sind Racket [Rac] oder SWI-Prolog [SWI].<br />
Weiterentwicklungen von Programmierumgebungen bilden die <strong>Software</strong>entwicklungsumgebungen.<br />
Die Aufgabe einer <strong>Software</strong>entwicklungsumgebung ist es, möglichst viele Aspekte<br />
der <strong>Software</strong>entwicklung zu unterstützen. <strong>Software</strong>entwicklungsumgebungen beinhalten<br />
Werkzeuge aus Programmierumgebungen, dazu üblicherweise Modellierungswerkzeuge, Konfigurierungswerkzeuge,<br />
Werkzeuge zur Qualitätssicherung und zur Unterstützung des Projektmanagements.<br />
Im Gegensatz zu Programmierumgebungen beginnt der Build-Prozess<br />
meist bereits bei Modellen (auch grafischen Modellen, z.B. Klassendiagrammen). Diese Philosophie<br />
gipfelt bisher in der ”<br />
Modellgetriebenen <strong>Software</strong>entwicklung”. Modellierungswerkzeuge<br />
verfügen in der Regel über grafische Editoren zur Erstellung und Modifikation von<br />
Modellen und sind heute in der Lage, Programmcode automatisch zu erzeugen. Quellco-
1.3. SOFTWAREENTWICKLUNGSWERKZEUGE 8<br />
de, der von Werkzeugen generiert wird, wird getrennt von handgeschriebenem Code (z.B. in<br />
unterschiedlichen Verzeichnissen src und src-gen) verwaltet. Wichtig ist, dass bei einer Neugenerierung<br />
von Quellcode aufgrund von Modelländerungen handgeschriebener Code nicht<br />
zerstört wird. Wahlweise kann Code für unterschiedliche Zielsprachen (Java, C++, Ada, ...)<br />
generiert werden.<br />
Hervorzuheben im Bereich der <strong>Software</strong>entwicklungsumgebungen ist die Firma Rational<br />
mit einer umfangreichen Produktpalette. Ein Überblick über Case-Tools ist in der Wikipedia<br />
unter http://de.wikipedia.org/wiki/Computer-aided software engineering zu finden.<br />
Eine konkrete und auch an der HTW Dresden in der Lehre eingesetzte <strong>Software</strong>entwicklungsumgebung<br />
ist ObjectIF [Obj] von der Firma MicroTOOL.<br />
1.3.2 Die Entwicklungsplattform Eclipse<br />
In der Lehrveranstaltung wird Eclipse SDK 4.2 (Juno) als Entwicklungsplattform verwendet<br />
(vgl. [Ecl]) . Eclipse ist keine <strong>Software</strong>entwicklungsumgebung im herkömmlichen Sinne. Es ist<br />
eine Plattform, die zu einer komfortablen <strong>Software</strong>entwicklungsumgebung erweitert werden<br />
kann.<br />
Eclipse basiert auf einer ”<br />
Plug-in”-Technologie, die es erlaubt, ein System zur Laufzeit<br />
(d.h. ohne neu zu compilieren) um vorgefertigte Komponenten zu erweitern, wenn sie eine<br />
vorgegebene Schnittstellenkonvention erfüllen. Das Konzept der Plug-ins kennt Erweiterungspunkte<br />
(engl. extension points) und Erweiterungen (engl. extensions). Erweiterungen sind<br />
Applikationen, die in Erweiterungspunkte eingehängt werden können. Solche Erweiterungen<br />
können ihrerseits wieder Erweiterungspunkte anbieten.<br />
Auch Eclipse selbst besteht bereits weitgehend aus Plug-ins. Was übrig bleibt, wenn man<br />
alle Plug-ins wegnimmt, ist ein Kern. Andererseits ist auch das grafische Benutzerinterface<br />
(GUI) von Eclipse ein Plug-in.<br />
Abb. 1.3.1 zeigt die Architektur von Eclipse. Eclipse besteht aus Subsystemen, die auf<br />
einer Runtime-Maschine arbeiten. Der Begriff Workbench wird in Eclipse für die Desktop-<br />
Entwicklungsumgebung verwendet. SWT ist das (open source) Standard Widget Toolkit für<br />
Java. JFace ist ein UI-Framework und enthält Klassen, die von UI-Plug-ins benötigt werden.<br />
Team ist ein Subsystem zur Unterstützung der Versionsverwaltung. Help ist ein komfortables<br />
Hilfesystem.<br />
Die Abbildung 1.3.2 zeigt das GUI der um Plug-ins zur Unterstützung der UML erweiterten<br />
Eclipse-IDE in einer Perspektive (Topcased Modeling), in der die Herstellung und<br />
Bearbeitung von UML-Diagrammen unterstützt wird.<br />
Während des Startvorganges von Eclipse ist vom Benutzer zunächst der Ort eines Arbeitsverzeichnisses<br />
(Workspace) festzulegen (vgl. Abb. 1.3.3). Der Workspace kann später<br />
während der Laufzeit geändert werden.<br />
Eclipse bietet verschiedene Perspektiven an. Perspektiven bestehen aus Views (z.B. Package<br />
Explorer, Hierarchy, Task List, Outline, Problems und andere) und Editoren sowie speziellen,<br />
angepassten Menüs. Views sind jeweils genau einer Perspektive zugeordnet, Editoren<br />
arbeiten Perspektiven-übergreifend. Anwendungen, die in Eclipse laufen, können eigene Per-
1.3. SOFTWAREENTWICKLUNGSWERKZEUGE 9<br />
Abbildung 1.3.1: Architektur von Eclipse<br />
spektiven besitzen.<br />
Eclipse ist ein Java-Programm, enthält aber selbst kein Java Runtime Environment (JRE)<br />
zu dessen Abarbeitung. Eclipse wird mit einer separat zu installierenden JRE verbunden, so<br />
dass als Grundfunktionalität das Erstellen und Ausführen von Java-Programmen unterstützt<br />
wird. Zum Kennenlernen von Eclipse wird die über Help erreichbare Sammlung von Tutorials<br />
empfohlen (vgl. Abb. 1.3.4).<br />
Ein spezielles Thema ist die Installation von vorgefertigten Plug-ins. Es gibt zwei Möglichkeiten:<br />
1. Online aus bereitgestellten Update-Sites (z.B. der Entwicklungsumgebung des Eclipse-<br />
Projekts) über den Update-Manager. Dieser ist im Menü Help Install New <strong>Software</strong> ...<br />
→<br />
zu finden (vgl. Abb. 1.3.5), ein Wizard wird gestartet. Nach der Auswahl werden im<br />
nächsten Schritt die zu installierenden Komponenten angezeigt. Meist ist danach eine<br />
Lizenzvereinbarung zu akzeptieren (z.B. GNU Lesser Gereral Public License).<br />
2. als Download-Datei<br />
Ein Feature ist eine aus Plug-ins bestehende Funktionalitäts-Einheit, die separat downloadbar<br />
und installierbar ist. Features können hierarchisch organisiert werden.<br />
Plug-ins können in Eclipse vom Anwendungsentwickler selbst programmiert werden. Dazu<br />
kann das Plug-in Development Environment (PDE) genutzt werden.
1.3. SOFTWAREENTWICKLUNGSWERKZEUGE 10<br />
Abbildung 1.3.2: Eclipse in der Topcased Modeling-Perspektive<br />
Abbildung 1.3.3: Festlegung des ”<br />
Workspace”
1.3. SOFTWAREENTWICKLUNGSWERKZEUGE 11<br />
Abbildung 1.3.4: Eclipse Online Tutorials
1.3. SOFTWAREENTWICKLUNGSWERKZEUGE 12<br />
1.3.3 Werkzeuge zur UML-Unterstützung<br />
Es gibt eine Vielzahl von Werkzeugen, die die Erstellung grafischer Modelle für verschiedene<br />
Modelltypen der UML unterstützen. Manche dieser Werkzeuge sind selbständige Applikationen,<br />
manche sind Plug-ins zu Eclipse, und manche der Werkzeuge sind in beiden Formen<br />
verfügbar. Eine Übersicht über solche Werkzeuge bietet<br />
http://www.jeckle.de/umltools.htm.<br />
TOPCASED<br />
Wir verwenden in der Lehrveranstaltung das Entwicklungswerkzeug TOPCASED<br />
( http://www.topcased.org/) in der Version 4.0.0 zur Unterstützung der objektorientierten<br />
Modellierung (Version 4.1.0 bereits verfügbar). TOPCASED ist ein freies Werkzeug. Es arbeitet<br />
als Plug-in in der Eclipse-Umgebung (ab Eclipse 3.6, Helios) und erfordert mindestens<br />
eine JVM 1.5 . Eine Installation einer kompletten Umgebung Eclipse + TOPCASED ist<br />
mittels Topcased-RCP-win32-4.0.0.zip möglich. Die nachträgliche Installation in Eclipse<br />
ist über die Dateien und org.topcased.experimental-4.0.0.zip möglich. Die Installation<br />
in Eclipse kann online erfolgen (vgl. Abb. 1.3.5).<br />
TOPCASED unterstützt die Erstellung und Weiterbearbeitung von UML 2-Diagrammen.<br />
Zur Erstellung eines neuen Modells ist zunächst über File → ”<br />
UML Model with TOPCASED”<br />
die ”<br />
Topcased Modeling”-Perspektive auszuwählen (vgl. Abb. 1.3.6). Danach kann die Diagrammart<br />
gewählt werden.<br />
Folgende Diagrammarten sind verfügbar (vgl. Abb. 1.3.6):<br />
• Aktivitäts-Diagramm (activity diagram)<br />
• Komponenten-Diagramm (component diagram)<br />
• Kompositionsstruktur-Diagramm (composite structure diagram)<br />
• Verteilungs-Diagramm (deployment diagram)<br />
• Sequenz-Diagramm (sequence diagram)<br />
• Zustands-Diagramm (state machine diagram)<br />
• Klassen-Diagramm (class diagram)<br />
• Anwendungsfall-Diagramm (use case diagram)<br />
Die Situation in einem Projekt ”<br />
First Topcased” nach Anlegen eines ersten Klassen-Modells<br />
zeigt Abb. 1.3.8.<br />
Im Editor-Fenster ”<br />
package Class-Model-1”kann nun mit der Zeichnung eines Diagramms<br />
begonnen werden. Dazu müssen wir aber erst – im Kapitel 2 – die Diagramm- und Modellarten<br />
kennenlernen.
1.3. SOFTWAREENTWICKLUNGSWERKZEUGE 13<br />
Abbildung 1.3.5: TOPCASED-Installation<br />
Abbildung 1.3.6: In TOPCASED unterstützte Modelltypen
1.3. SOFTWAREENTWICKLUNGSWERKZEUGE 14<br />
Abbildung 1.3.7: In TOPCASED unterstützte Diagrammarten
1.3. SOFTWAREENTWICKLUNGSWERKZEUGE 15<br />
Abbildung 1.3.8: Anlegen eines ersten UML-Klassenmodells
1.3. SOFTWAREENTWICKLUNGSWERKZEUGE 16<br />
FUJABA<br />
Ein weiteres interessantes Eclipse-Plug-in ist FUJABA:<br />
ObjectIF<br />
Das Werkzeug (http://www.microtool.de/objectif/de/, ObjectiF-7.0 Personal) wird ebenfalls<br />
in der <strong>Software</strong>technik-Ausbildung an der HTW Dresden eingesetzt.<br />
1.3.4 Diagrammaustausch<br />
Obwohl die UML ein Standard ist, ist es nicht einfach, Diagramme zwischen unterschiedlichen<br />
UML-Werkzeugen auszutauschen. Ein Diagrammaustausch kann auf der Basis von XMI<br />
(XML Metadata Interchange) über Export- und Importfunktionen möglich gemacht werden.<br />
1.3.5 Werkzeuge zur Versionsverwaltung<br />
Das z.Z. wohl am häufigsten verwendete Werkzeug zur Versionsverwaltung ist Subversion<br />
(svn). In Eclipse wird Subversion wiederum durch ein Plug-in verfügbar gemacht, wir verwenden<br />
subclipse. Detailliert behandeln wir MERCURIAL im Kapitel 7. MERCURIAL ist<br />
als Kommandozeilen-orientiertes Werkzeug und ebenfalls als Plug-in für Eclipse verfügbar<br />
(vgl. Abb. 1.3.9).<br />
Mercurial ist ein schnelles und robustes Versionsverwaltungssystem, welches in Python<br />
”<br />
geschrieben ist und beispielsweise von den MoinMoin und Mozilla Entwickler-Teams verwendet<br />
wird.” Quelle: http://wiki.ubuntuusers.de/Mercurial.
1.3. SOFTWAREENTWICKLUNGSWERKZEUGE 17<br />
Abbildung 1.3.9: Bereitstellung von Mercurial<br />
1.3.6 Werkzeuge für das Build-Management<br />
Eine weitere Gruppe von Werkzeugen, die in der <strong>Software</strong>technik benötigt werden, dienen<br />
der Unterstützung des Build-Prozesses. Wir behandeln aus dieser Gruppe im Kapitel 7 die<br />
Werkzeuge Ant und Maven.
Kapitel 2<br />
Der <strong>Software</strong>entwicklungsprozess<br />
2.1 Die Phasen im <strong>Software</strong>entwicklungsprozess<br />
Der <strong>Software</strong>entwicklungsprozess – zunächst als Vorwärtsentwicklung betrachtet – lässt sich<br />
als Menge von Aktivitäten definieren, die zur Produktion eines <strong>Software</strong>systems führen (vgl.<br />
[Som07], S. 94). Dabei sind folgende Fälle zu unterscheiden:<br />
1. es handelt sich um eine komplette Neuentwicklung,<br />
2. bestehende Systeme werden erweitert, verändert,<br />
3. Systemkomponenten werden konfiguriert oder integriert.<br />
Versuche, den gesamten <strong>Software</strong>entwicklungsprozess umfassend zu automatisieren, waren<br />
bisher wenig erfolgreich. Grundlegende Abläufe, die alle <strong>Software</strong>entwicklungsprozesse gemeinsam<br />
haben, sind:<br />
• Analyse einschließlich der Spezifikation (Definition der Funktion und der Constraints)<br />
• Entwurf und Implementierung (Erstellung der <strong>Software</strong>, die der Spezifikation genügt)<br />
• Validation und Verifikation (Nachweise für Konsistenz und Äquivalenz von Modellen<br />
und Programmen auf verschiedenen Entwicklungsstufen)<br />
• Evolution (Weiterentwicklung, Anpassung an Kundenwünsche)<br />
Kernaktivitäten der Analysephase sind die Problemdefinition, die Anforderungsanalyse, die<br />
Anforderungsdefinition (mit Lastenheft und Pflichtenheft), Aufwands- und Risiko abschätzungen,<br />
Festlegung des Vorgehens. Modelle und Spezifikationen sind das Ergebnis von Analysen.<br />
Die Analysephase beinhaltet für die Entwicklung von Anwendungssystemen typischerweise<br />
eine objektorientierte Analyse.<br />
In der Entwurfsphase wird die <strong>Software</strong>architektur festgelegt. Die Entwurfsphase beinhaltet<br />
für Objekt-basierte Systeme einen objektorientierten Entwurf.<br />
18
2.2. LEBENSZYKLUS-MODELLE 19<br />
2.2 Lebenszyklus-Modelle<br />
Ein Vorgehensmodell ist eine sehr abstrakte Darstellung eines <strong>Software</strong>entwicklungsprozesses.<br />
Der <strong>Software</strong>entwicklungsprozess wurde – als industrieller Fertigungsprozess – in der Vergangenheit<br />
im Zusammenhang mit dem Lebenszyklus der <strong>Software</strong> beschrieben. Es entstanden<br />
Lebenszyklus-Modelle.<br />
Das Wasserfallmodell stellt die wesentlichen Prozessaktivitäten als eigenständige Phasen<br />
dar (vgl. Abb. 2.2.1). Es wurde vom Vorgehen von andersartigen ingenieurtechnischen<br />
Entwicklungen abgeleitet.<br />
Anforderungsdefinition<br />
System- u.<br />
<strong>Software</strong>entwurf<br />
Implementation<br />
und Unit-Test<br />
Integration und<br />
Systemtest<br />
Einsatz und<br />
Wartung<br />
Abbildung 2.2.1: Wasserfallmodell (Royce, 1970)<br />
Der Ansatz der evolutionären Entwicklung verknüpft Aktivitäten, um schnell ein erstes<br />
System zu schaffen. Dieses wird zyklisch unter Berücksichtigung von Kundeninformationen<br />
verbessert. Ein typisches evolutionäres Lebenszyklusmodell ist das Spiralmodell nach B.W.<br />
Boehm (vgl. Abb. 2.2.2).<br />
Das V-Modell – nach seiner Form mit einem absteigenden und einem aufsteigenden Ast<br />
benannt – stellt auf mehreren Abstraktionsstufen jeweils Konstruktions- und Überprüfungsaktionen<br />
optisch sichtbar gegenüber (vgl. Abb. 2.2.3). Auf der obersten Ebene, in der Anforderungsanalyse<br />
und Abnahmetest gegenübergestellt werden, spricht man bei der Überprüfung<br />
von Validation, darunter von Verifikation.<br />
Ein weiteres Modell ist die komponentenbasierte <strong>Software</strong>technik.<br />
2.3 Prozessmodellierung und Projektmanagement<br />
Ein Prozessmodell definiert den Ablauf der <strong>Software</strong>-Entwicklung als einen idealisierten<br />
Prozess unter Anwendung formaler, struktureller Beschreibungsmittel (Metamodelle). Es<br />
dient der Benennung, Beschreibung und Ordnung von Dokumenten, Aktivitäten oder Tätigkeiten<br />
innerhalb der <strong>Software</strong>-Entwicklung. Der Prozess der <strong>Software</strong>entwicklung wird modelliert<br />
und (soweit möglich) programmiert. Wesentliche Strukturelemente dabei sind Rollen,<br />
Aktivitäten, Dokumente und Ressourcen.
2.3. PROZESSMODELLIERUNG UND PROJEKTMANAGEMENT 20<br />
Abbildung 2.2.2: Spiralmodell (Boehm, 1988)<br />
Das Projektmanagement (PM) umfasst alle Maßnahmen zur Planung, Verfolgung und<br />
Qualitätssicherung einzelner Projekte und von Projektfamilien.<br />
Zur Projektplanung zählen:<br />
• Festlegung der Projektorganisation<br />
• Personalplanung<br />
• Meilenstein-/Terminplanung für alle Aktivitäten und Zuordnung zu Bearbeitern.<br />
Die Projektverfolgung umfasst alle technisch-organisatorischen Maßnahmen zur Erreichung<br />
der Projektziele. Dazu zählen die Verfolgung von Meilensteinen, Terminen und (Rest-) Aufwänden<br />
sowie die Kontrolle der Aktivitäten der Bearbeiter. Grundlage der Projektmanagement-<br />
Aktivitäten bilden Dokumente spezieller Typen, z.B. Aktivitätsgraphen, Balkendiagramme<br />
usw. Gegenstand der Prozessmodellierung ist die Formalisierung und Computerunterstützung<br />
des <strong>Software</strong>entwicklungsprozesses selbst. Neben Bausteinen und Ergebnissen müssen<br />
Aufgaben und Ressourcen verwaltet werden. Wichtig ist dabei die Konsistenzsicherung.
2.4. QUALITÄTSSICHERUNG 21<br />
2.4 Qualitätssicherung<br />
2.4.1 Ziele und Maßnahmen<br />
Abbildung 2.2.3: V-Modell<br />
Die Qualitätssicherung umfasst die Planung/Durchführung von Qualitätssicherungsmaßnahmen<br />
(QSM), die Kontrolle der Einhaltung von Standards (ISO 9000, ... ) und die Qualitätsbewertung<br />
anhand von Metriken. Wesentliche Qualitätsmerkmale sind Korrektheit und<br />
Robustheit. Die Begriffe sind relativ zur Spezifikation zu sehen: Korrekt kann ein Programm<br />
nur relativ zu seiner Spezifikation sein, d.h. Korrektheit ist eine Ja-Nein-Entscheidung. Robust<br />
kann ein Programm mehr oder weniger sein, insbesondere wird auch das Verhalten<br />
außerhalb der Spezifikation betrachtet (vgl. Abb. 2.4.1) .<br />
Abbildung 2.4.1: Korrektheit und Robustheit im Verhältnis zur Spezifikation<br />
Weitere wichtige Qualitätskriterien sind Erweiterbarkeit, Wiederverwendbarkeit, Portabilität<br />
und Verständlichkeit.
2.4. QUALITÄTSSICHERUNG 22<br />
Es werden konstruktive und analytische QSM unterschieden (vgl. Abb. 2.4.2). Zu den<br />
analytischen QSM zählen statische Analysen, der symbolische Test und dynamische Analysen<br />
(das ”<br />
eigentliche”Testen). Zu den statischen Analysen zählen vor allem die Kontrollfluss- und<br />
Datenflussanalyse sowie die verschiedenen Verfahren zur Programmverifikation.<br />
QSM<br />
konstruktive<br />
analytische<br />
statische Analysen<br />
(Compilertechnik)<br />
Kontrollfluß- / Datenflußanalyse<br />
Verifikation<br />
dynamischer Test<br />
(Testläufe in realer Umgebung)<br />
Unittest / Integrationstest<br />
Blackbox- / Whiteboxtest<br />
diversifizierende Verfahren<br />
:.<br />
symbolischer Test<br />
(Interpretation in künstlicher Umgebung)<br />
Abbildung 2.4.2: Qualitätssicherungsmaßnahmen im Überblick<br />
2.4.2 Testen<br />
Das zustandsbasierte Testen von Klassen nach Turner und Robson ist eine Technik, die<br />
auf der Modellierung von Klassen durch endliche Automaten basiert. Automaten werden zur<br />
Generierung von Testfällen herangezogen.<br />
Jeder Testfall umfasst<br />
• die Erzeugung eines bestimmten Zustandes des Testobjekts,<br />
• gefolgt vom Aufruf ( ”<br />
Invocation”) einer Operation (Methode) und<br />
• die Validierung des durch die Operation herbeigeführten Zustandes.<br />
2.4.3 Validation und Verifikation<br />
Bei der Verifikation werden formale (mathematische) Beweismethoden eingesetzt, um die<br />
Korrektheit von Programmen zu beweisen. Dabei geht es um den Nachweis bestimmter<br />
Programmeigenschaften, wobei eine Unterscheidung in sequentielle, parallele (Nutzung eines<br />
gemeinsamen Speichers) und verteilte (lokale Speicher und Austausch von Nachrichten)<br />
Programme vorgenommen wird. Es handelt sich um folgende Eigenschaften:
2.4. QUALITÄTSSICHERUNG 23<br />
• Für sequentielle Programme:<br />
– partielle Korrektheit: Wenn ein Ergebnis geliefert wird, erfüllt es die Spezifikation.<br />
Das Programm muss nicht terminieren<br />
– Nachweis der Termination (die totale Korrektheit eines Programms ist gegeben,<br />
wenn ein partiell korrektes Programm immer terminiert)<br />
– Es gibt keine Exceptions (unerwartete Programmabbrüche)<br />
• Für parallele Programme:<br />
– Interferenz-Freiheit:<br />
– Deadlock-Freiheit<br />
– Korrekt unter Fairness-Annahmen<br />
Teile des Verifikationsprozesses sind automatisierbar.<br />
Betrachten wir zunächst Verifikationsverfahren. Es gibt verschiedene Ansätze zur Verifikation:<br />
• die operationale Methode (nur für sequentielle Programme sinnvoll)<br />
• die axiomatische Methode
Kapitel 3<br />
Anforderungsanalyse und<br />
-definition<br />
In der Phase der Anforderungsanalyse (Requirements <strong>Engineering</strong>) geht es um die Ermittlung<br />
der<br />
• Anforderungen der Benutzer,<br />
• Anforderungen an das zu entwickelnde System,<br />
• prozessbezogenen Anforderungen.<br />
Während der Anforderungsanalyse muss die fachliche Konzeption erarbeitet werden, Implementierungsaspekte<br />
spielen keine Rolle. Es geht auch (noch) nicht um die Verteilung der<br />
<strong>Software</strong> auf verschiedene Computersysteme. Problematisch dabei ist, dass sich der Systemanalytiker<br />
dem Auftraggeber verständlich machen kann.<br />
Der Entwickler muss auch entscheiden, welche Art von System entwickelt werden soll:<br />
Man unterscheidet transformative Systeme und reaktive Systeme (vgl. Abb. 3.0.1).<br />
3.1 Das Pflichtenheft<br />
Das Pflichtenheft umfasst (vgl. [Som07], S.168)<br />
• Benutzeranforderungen an das zu entwickelnde System<br />
• detaillierte Spezifikation der Systemanforderungen.<br />
Benutzeranforderungen werden zunächst abstrakt und vorwiegend verbal beschrieben. Bei<br />
den Systemanforderungen werden funktionale und nichtfunktionale Anforderungen spezifiziert.<br />
Spezifikationen gelten als Vertrag zwischen AG und AN. Den Aufbau eines Pflichtenheftes<br />
zeigt Abb. 3.1.1.Das Pflichtenheft wird verwendet von<br />
• Systemkunden<br />
24
3.2. SPEZIFIKATIONEN 25<br />
Abbildung 3.0.1: Klassifizierung von Systemen aus Entwicklersicht<br />
• dem höheren Management des Unternehmens<br />
• den Systementwicklern<br />
• Systemtestern<br />
• der Systemwartung<br />
In der LV müssen wir (Sudenten) uns wechselweise in die verschiedenen Rollen versetzen.<br />
3.2 Spezifikationen<br />
Anforderungsspezifikationen in natürlicher Sprache sind anfällig gegen Missverständnisse<br />
( ”<br />
Er winkt dem Mann mit dem Hut”).<br />
3.2.1 Formale Beschreibungsmittel<br />
Es gibt grafische Notationen.<br />
3.3 Anwendungsfall-Analyse<br />
Ein Anwendungsfall bzw. Use-Case (use case) beschreibt die Interaktionen zwischen Anwendern<br />
und dem (zu entwickelnden) Anwendungssystem, die notwendig sind, um einen
3.3. ANWENDUNGSFALL-ANALYSE 26<br />
Abbildung 3.1.1: Aufbau eines Pflichtenheftes<br />
Arbeitsgang durchzuführen. Im Wesentlichen sind Anwendungsfälle und Akteure zu identifizieren.<br />
Das Darstellungsmittel der UML für Anwendungsfälle sind Use-Case-Diagramme. Ein<br />
Use-Case-Diagramm zeigt die Beziehungen zwischen Akteuren (actors) und Anwendungsfällen<br />
in einem System. Das System wird durch ein Rechteck dargestellt, das die Systemgrenzen<br />
zeigt. Ein Use-Case wird durch eine Ellipse dargestellt. Der Name des Anwendungsfalles wird<br />
innerhalb oder unterhalb der Ellipse dargestellt. Ein Use-Case wird immer von einem Akteur<br />
ausgelöst ( ”<br />
getriggert”).<br />
Im Use-Case-Diagramm können auch Beziehungen zwischen Anwendungsfällen gezeigt<br />
werden.<br />
Die -Beziehung definiert, dass ein Use-CAse das in einem anderen Use-Case<br />
beschriebene Verhalten einschließt. Der Pfeil zeigt auf den Use-CAse, dessen Verhalten eingeschlossen<br />
(importiert) wird. Zyklen bzgl. der -Beziehungen sind nicht erlaubt.<br />
Die -Beziehung zwischen Use-Cases zeigt an, dass ein Use-Case durch einen<br />
anderen Use-Case erweitert werden kann (aber nicht muss). Der Pfeil zeigt zu dem erweiternden<br />
Use Case.<br />
Generalisierungen/Spezialisierungen zwischen Use-Cases sind ebenfalls erlaubt.
3.3. ANWENDUNGSFALL-ANALYSE 27<br />
Abbildung 3.3.1: UseCase-Diagramm für Aktivitäten einer Autovermietung
Kapitel 4<br />
Objektorientierte Modellierung<br />
4.1 Einführung<br />
Die objektorientierte Modellierung umfasst die Entwicklungsphasen<br />
• objektorientierte Analyse<br />
• objektorientierter Entwurf<br />
4.2 Klassenbildung<br />
4.2.1 Klassen - Instanzen - Abstraktionen<br />
Ein Objekt ist ein (abstrakter) Gegenstand oder ein Konzept mit klarer Abgrenzung und<br />
präziser Bedeutung.<br />
Beispiele: Kommissar Rex, das Fenster im Haus oben links, Herr Lutze, die Fa.<br />
Mediatec GmbH, ...<br />
Eine Klasse umfasst eine Menge von Objekten, die<br />
• ähnliche Struktur und ähnliche Merkmale (sog. Attribute) besitzen,<br />
• ähnliches Verhalten zeigen (Operationen bzw. ”<br />
Methoden”),<br />
• in Relationen zu Objekten anderer Klassen oder der gleichen Klasse stehen.<br />
Eine Klasse kann durch Aufzählung der zu ihr gehörenden Objekte oder durch die Definition<br />
der relevanten Eigenschaften der Objekte beschrieben werden. Zu einer Klasse gehörige<br />
Objekte werden als Instanzen, Instanzobjekte oder auch Exemplare der Klasse bezeichnet.<br />
Klassen werden benannt.<br />
Beispiele: Hund, Fenster, Person, Firma, ...<br />
Feststellungen<br />
• Die Identifizierung und Benennung von Objekten hilft, die zu modellierende Welt zu<br />
verstehen. Modellierte Objekte werden bei der programmtechnischen Realisierung typischerweise<br />
zu Implementierungseinheiten, die zur Laufzeit existieren.<br />
28
4.2. KLASSENBILDUNG 29<br />
• Klassenbildung ist Abstraktion. Unter allen denkbaren bzw. tatsächlichen Eigenschaften<br />
betrachteter Objekte wird von den für den Zweck der Betrachtung unwesentlichen<br />
Eigenschaften abstrahiert (Abstraktion bedeutet das Weglassen von Unwesentlichem).<br />
Abstraktion ist ein mächtiges Mittel zur Bewältigung von Komplexität.<br />
• Objekte besitzen eine Identität und (insbesondere bzgl. der technischen Realisierung<br />
auf einem Rechner) eine Lebensdauer. Mit der Klassendefinition wird zugleich eine<br />
Vorschrift zur Bildung von Instanzen festgelegt. Der Vorgang der Erzeugung einer<br />
Instanz wird Instanziierung genannt.<br />
• Jedes Objekt gehört zu genau einer Klasse und ”<br />
kennt” seine Klasse.<br />
• Die konkrete Dekomposition eines Problems bzw. einer Domäne in Klassen und Objekte<br />
ist subjektiv.<br />
4.2.2 Attribute<br />
Attribute beschreiben für eine Klasse die betrachteten strukturellen Merkmale, die alle zu<br />
der Klasse gehörenden Instanzen in einer gewissen spezifischen Ausprägung besitzen.<br />
Jedes einzelne Objekt der Klasse besitzt für jedes Attribut (Merkmal) eine individuelle<br />
Ausprägung (Merke: Objekt - Merkmal - Ausprägung), die sich allerdings für ein Objekt im<br />
Laufe der Zeit ändern kann. Ausprägungen sind Datenwerte eines in der Klassendefinition<br />
vorbestimmten Datentyps. Der Wertebereich ist für jedes Attribut (eventuell implizit) in der<br />
Klassendefinition festgelegt.<br />
Jedes Objekt einer Klasse kann für ein bestimmtes Attribut zum selben Zeitpunkt einen<br />
anderen Wert aus dem Wertebereich besitzen.<br />
Die Gesamtheit der Werte (d.h. der Ausprägungen) der Attribute bestimmt den Zustand<br />
eines Instanzobjektes zu einem bestimmten Zeitpunkt.<br />
4.2.3 Operationen und Methoden<br />
Operationen definieren das Verhalten von Objekten. Alle einer Klasse zugeordneten Objekte<br />
verhalten sich in einer gewissen Weise gleichartig. Das Verhalten eines Objektes wird dabei<br />
von individuellen Ausprägungen der Attribute mitbestimmt.<br />
Die Verhaltensbeschreibung wird der Klassendefinition hinzugefügt, und nicht jedem Objekt<br />
einzeln. Sie besteht aus einer Menge von Operationen und existiert damit einmal unabhängig<br />
von der Anzahl vorhandener Instanzen, was bei der Implementation der Operationen<br />
in einer objektorientierten Programmiersprache genauso umgesetzt wird.<br />
Auf alle Objekte einer Klasse können die gleichen Operationen angewendet werden.<br />
Die Funktionalität von Operationen wird in objektorientierten Sprachen durch Methoden<br />
implementiert. Methoden können Parameter verarbeiten und liefern ein Objekt eines<br />
bestimmten Objekttyps als Wert. Methoden können Zustandsänderungen eines Objektes<br />
( ”<br />
Seiteneffekte”) durch Überschreiben von Attributwerten bewirken. Operationen, die den
4.2. KLASSENBILDUNG 30<br />
Zustand eines Objektes nicht verändern, heißen ”<br />
Anfragen”. Ein ”<br />
abgeleitetes Attribut” ist<br />
eine Anfrage, die keine Argumente (d. h. Parameter) besitzt.<br />
4.2.4 Grafische Notation für Klassen und Objekte<br />
Die UML unterstützt eine grafische Notation zur Definition von Klassen und zur Spezifikation<br />
ihrer Eigenschaften. Klassen werden innerhalb eines Klassendiagramms deklariert und<br />
können in anderen Diagrammen verwendet werden.<br />
Für die Darstellung von Klassen und Instanzen wird das Rechteck als geometrisches<br />
Symbol verwendet. Eine waagerechte Linie separiert den Namen der Klasse von den Attributbeschreibungen<br />
sowie diese von den Operationsbeschreibungen:<br />
klassenname<br />
visibility attributname : datentypname = defaultwert<br />
.<br />
visibility operationsname (argumentliste) : ergebnistyp<br />
.<br />
Die Argumentliste einer Operationsbeschreibung enthält durch Komma getrennte Parameterangaben.<br />
Eine Parameterangabe wird als<br />
〈 formaler Parameter〉:〈Parametertyp〉<br />
notiert.<br />
Die Reihenfolge der Einträge in den Feldern der Attributbeschreibungen und Operationsbeschreibungen<br />
ist von Bedeutung. Innerhalb einer Liste können Einträge gruppiert werden.<br />
Einer solchen Gruppierung kann ein in 〈〈 〉〉 eingeschlossenes Schlüsselwort vorangestellt<br />
werden.<br />
Die Felder zur Spezifikation von Attributen und Operationen können leer sein (dann<br />
können auch die Trennlinien entsprechend entfallen). Empfehlungen zur Notation:<br />
• Klassenname fett und zentriert im Feld notieren, Klassennamen beginnen mit Großbuchstaben<br />
• Attribut- und Operationsnamen beginnen mit Kleinbuchstaben<br />
Instanzobjekte werden wie Klassen durch Rechtecke repräsentiert. Anstelle des Klassennamens<br />
wird im Rechteck der Instanzname (Benennung der Instanz) notiert, gefolgt von ”<br />
:”,<br />
gefolgt vom Klassennamen. Klassenname und Instanzname sind optional. Fehlt der Instanzname,<br />
spricht man von einer ”<br />
anonymen Instanz”. Die so gebildete Zeichenkette wird gemäß<br />
UML 2 unterstrichen (siehe Abbildung 4.2.2). 1<br />
Klassifizierungen bzw. Instanziierungen können innerhalb eines Klassendiagrammes notiert<br />
werden (siehe Abbildung 4.2.3).<br />
Diagramme, die ausschließlich Instanzobjekte darstellen, werden Objektdiagramme genannt.<br />
1 In Topcased fehlt die Unterstreichung.
4.2. KLASSENBILDUNG 31<br />
Window<br />
Window<br />
size : Area<br />
visibility : Boolean<br />
display ()<br />
hide ()<br />
Window<br />
+ size : Area = (100,100)<br />
# visibility : Boolean = invisible<br />
+ default-size : Rectangle<br />
# maximum-size : Rectangle<br />
+ display ()<br />
+ hide ()<br />
+ create ()<br />
Abbildung 4.2.1: unterschiedliche Ausprägungen der Darstellung einer Klasse<br />
insttanzname<br />
attributname =<br />
...<br />
: klassenname<br />
wert<br />
Abbildung 4.2.2: Darstellung eines Instanzobjektes<br />
4.2.5 Sichtbarkeit von Attributen und Operationen<br />
Benennungen ordnen Objekten und Operationen Namen zu. Namen sind immer innerhalb<br />
einer gewissen Umgebung (Enviroment) sichtbar. Einem Attribut- oder Operationsnamen<br />
kann in der Darstellung ein Kennzeichen zur Beschreibung der Sichtbarkeit (visibility) vorangestellt<br />
werden:<br />
+ public Es kann von außerhalb der Klasse (auch von<br />
Nachfahren der Klasse) zugegriffen werden.<br />
# protected Java: Zugriff ist grundsätzlich innerhalb des die<br />
Klasse enthaltenden Paketes möglich. Von anderen<br />
Paketen ist es nur Nachfahren möglich, auf ein<br />
protected-Attribut oder eine protected-Methode<br />
zuzugreifen.C++: Zugriff ist nur innerhalb der Klasse<br />
und in allen Unterklassen möglich. protected<br />
entspricht in Java private protected.<br />
- private Java: Es ist von außerhalb des Objektes und von<br />
Nachfahren des Objektes nicht möglich zuzugreifen.
4.3. ASSOZIATIONEN 32<br />
klassenname<br />
instanzname : klassenname<br />
Abbildung 4.2.3: Instanziierungsrelation<br />
Fehlt das Kennzeichen, wird damit angezeigt, dass die Sichtbarkeit nicht gezeigt wird. Es<br />
bedeutet nicht, dass die Sichtbarkeit public oder undefiniert ist.<br />
4.2.6 Klassen als Objekte<br />
Attribute und Operationen, deren Scope-Bereich die Klasse (und nicht eine Instanz) ist, werden<br />
als Klassen-Attribute bzw. Klassen-Operationen bezeichnet. In der grafischen Notation<br />
im Klassensymbol werden die entsprechenden Einträge unterstrichen.<br />
4.3 Assoziationen<br />
4.3.1 Assoziation und Verknüpfung<br />
Eine Assoziation ist eine abstrakte, d.h. nicht näher charakterisierte Beziehung zwischen zwei<br />
(binäre A.) oder mehr Klassen. In der grafischen Darstellung werden die Symbole der beteiligten<br />
Klassen durch eine durchgezogene Linie verbunden. Die Enden einer Assoziationslinie<br />
können mit demselben Klassensymbol verbunden sein.<br />
Assoziationen höherer Ordnung (ternäre, ... ) werden durch einen Rhombus dargestellt,<br />
der durch Linien mit den Klassensymbolen verbunden ist. Assoziationen höherer Ordnung<br />
sollten nach Möglichkeit vermieden werden. Sie sind schwerer zu verstehen, darzustellen und<br />
zu implementieren als binäre Assoziationen.<br />
Eine Verknüpfung (Link) ist ein Element einer Assoziation. Sie setzt eine Anzahl von<br />
Objekten entsprechend der Assoziation in Beziehung. Ist durch eine binäre Assoziation eine<br />
Klasse mit sich selbst verbunden, können unterschiedliche Objekte dieser Klasse verknüpft<br />
sein oder auch ein Objekt mit sich selbst.<br />
Der Pfad einer Assoziation kann (in seinem Hauptteil) beschriftet sein. Die Beschriftung<br />
ist optional. Sie zeigt die Eigenschaften der Assoziation als Ganzes. Es gibt folgende Arten:<br />
1. Assoziations-Namen<br />
(string + ausgefülltes Dreieck)<br />
Das Dreieck zeigt (falls vorhanden) mit seiner Spitze<br />
die Richtung an, in der der Name zu lesen ist.
4.3. ASSOZIATIONEN 33<br />
2. Assoziations-Klassensymbol<br />
Eine Assoziation kann wie Klassen Eigenschaften besitzen.<br />
Dargestellt wird das durch ein Klassensymbol, das mit<br />
dem Pfad der Assoziation durch eine gestrichelte Linie<br />
verbunden ist.<br />
Falls ein Assoziations-Name und eine Assoziations-Klasse angegeben sind, müssen die Namen<br />
übereinstimmen.<br />
Beispiele:<br />
1. Abb. 4.3.1 zeigt eine binäre Assoziation.<br />
Person<br />
name<br />
vorname<br />
hat_Mutter<br />
Person<br />
name<br />
vorname<br />
Person<br />
name<br />
vorname<br />
hat_Mutter<br />
Abbildung 4.3.1: binäre Assoziation<br />
Eine binäre Assoziationn in Topcased zeigt Abb. 4.3.2 .<br />
Abbildung 4.3.2: binäre Assoziation in TOPCASED<br />
2. Abb. 4.3.3 zeigt eine Verknüpfung (Link) zwischen Instanzen der Klasse Person.
4.3. ASSOZIATIONEN 34<br />
:Person<br />
name = Müller<br />
vorname = Martin<br />
hat_Mutter<br />
:Person<br />
name = Müller<br />
vorname = Martina<br />
Abbildung 4.3.3: Verknüpfung<br />
3. In Topcased können Objekte und Klassen gemeinsam in einem Diagramm dargestellt<br />
werden. Die Zugehörigkeit der Objekte zu Klassen ist aus dem Klassennamen im Objektdiagramm<br />
ersichtlich, kann aber auch durch eine Abhängigkeitsbeziehung (dependency)<br />
kenntlich gemacht werden (vgl. Abb. 4.3.4).<br />
Abbildung 4.3.4: Klassen und Objekte in einem Klassendiagramm dargestellt<br />
4. Abb. 4.3.5 zeigt eine ternäre Assoziation: Personen (die in der Rolle eines Programmierers<br />
tätig sind) verwenden in Projekten Programmiersprachen.<br />
4.3.2 Rollen und Multiplizität<br />
In einer Assoziation können die beteiligten Klassen Rollen spielen. An den Enden des die<br />
Assoziation kennzeichnenden Pfades können jeweils der Rollenname der Klasse und eine<br />
Kardinalität notiert werden.<br />
Durch die Angabe einer Kardinalität wird die Multiplizität beschrieben. Die Angabe<br />
erfolgt durch die Aufzählung von Bereichen, die durch Komma getrennt werden. Eine Bereichsangabe<br />
hat die allgemeine Form untere-grenze .. obere-grenze. Ein * kann für eine nicht
4.3. ASSOZIATIONEN 35<br />
Projekt<br />
Programmiersprache<br />
Person<br />
Abbildung 4.3.5: ternäre Assoziation<br />
negative ganze Zahl zur Kennzeichnung einer nach oben offenen Grenze gesetzt werden.<br />
Beispiele<br />
7, 10, 1..3<br />
0..∗<br />
∗<br />
1..∗<br />
Ein Klassendiagramm, mit Rollen (Arbeitgeber, Arbeitnehmer, Vorgesetzter, Unterstellter),<br />
Multiplizität und einer Assoziationsklasse zeigt Abbildung 4.3.6.<br />
Firma<br />
Job<br />
* 1..* Person<br />
AG<br />
AN<br />
Unterstellter<br />
Job<br />
gehalt<br />
*<br />
Vorgesetzter<br />
0..1<br />
Leitet<br />
Abbildung 4.3.6: Klassendiagramm mit Assoziationsklasse<br />
Abbildung 4.3.7 zeigt zusammengefasst das Metamodell für binäre Assoziationen.<br />
klasse<br />
card(<br />
R2 ) assoziationsname card( R1 )<br />
rollenname ( R2 )<br />
rollenname ( R1 )<br />
Abbildung 4.3.7: Metamodell für Assoziationen<br />
klasse<br />
4.3.3 Aggregation und Komposition<br />
Aggregation Eine Aggregation ist eine Sonderform der Assoziation mit zusätzlicher Semantik<br />
und bedeutet eine Teil-Ganzes”-Beziehung. Objekte, die Komponenten einer Sache<br />
”
4.4. GENERALISIERUNG UND SPEZIALISIERUNG 36<br />
repräsentieren, sind mit einem Objekt verknüpft, das die Komponentengruppe (das Aggregat)<br />
repräsentiert. Aggregation ist über eine beliebige Anzahl von Ebenen hinweg möglich:<br />
Eine Komponente kann ihrerseits Aggregat weiterer Komponenten sein. Die Relation ”<br />
Aggregation”<br />
ist transitiv und antisymmetrisch.<br />
Darstellung: Hohle Raute an dem Ende der Assoziation, das dem Aggregat zugeordnet<br />
ist.<br />
Komposition Die Komposition wird als enge” Aggregation verstanden, die eine Verbindung<br />
der Teile mit dem Ganzen auch hinsichtlich der Lebenslinie” bedeutet. Attribute<br />
”<br />
”<br />
einer Klasse können als Komposition zwischen der Klasse und den Klassen (bzw. Typen) der<br />
Attribute verstanden werden.<br />
Darstellung: Ausgefüllte Raute anstelle der hohlen Raute.<br />
Beispiel (Abbildung 4.3.8): Die Punkte eines Polygons können auch ohne Polygon existieren<br />
(Aggregation), die Textur hat nur im Zusammenhang mit dem Polygon einen Sinn<br />
(Komposition).<br />
Polygon<br />
1<br />
1<br />
hat<br />
+ Eckpunkte<br />
3..*<br />
{ordered}<br />
Punkt<br />
x : real<br />
y : real<br />
1<br />
- Verbund<br />
Grafik-Verbund<br />
farbe<br />
texture<br />
schwärzung<br />
Abbildung 4.3.8: Aggregation und Komposition<br />
Aggregationen (und Kompositionen) können ggf. in Form eines Aggregationsbaumes dargestellt<br />
werden.<br />
4.4 Generalisierung und Spezialisierung<br />
4.4.1 Die Inklusionsrelation<br />
Generalisierung und Spezialisierung sind Vorgänge die eine Taxonomie-Relation (Inklusionsrelation)<br />
zwischen (mindestens) einer allgemeineren und (mindestens) einer spezielleren Entität<br />
etablieren. Eine speziellere Entität besitzt alle Eigenschaften der allgemeineren Entität<br />
(Inklusion) sowie zusätzliche und/oder anders ausgeprägte Eigenschaften.<br />
Generalisierung und Spezialisierung werden zunächst auf Klassen angewendet (Anwendung<br />
für Pakete und Use-Cases werden separat behandelt).
4.4. GENERALISIERUNG UND SPEZIALISIERUNG 37<br />
Die Inklusionsrelation ist gerichtet. Sie ist transitiv und antisymmetrisch. Eine an der<br />
Relation beteiligte allgemeinere Klasse wird als Superklasse (Oberklasse), eine speziellere<br />
Klasse als Subklasse (Unterklasse) bezeichnet.<br />
Sprechweise:<br />
Ein Hund ist ein” (is-a) Säugetier. Die Klasse Hund ist Subklasse zur<br />
”<br />
Klasse Säugetier. Die Klasse<br />
Säugetier ist Superklasse zur<br />
Klasse Hund.<br />
Ein Student ist eine” Person. Die Klasse Student ist Subklasse<br />
”<br />
zur Klasse Person. Die Klasse<br />
Person ist Superklasse zur Klasse<br />
Student.<br />
Eine Subklasse hat gegenüber der zugeordneten Superklasse veränderte Eigenschaften.<br />
Dabei kann es sich um<br />
• zusätzliche Attribute,<br />
• zusätzliche Operationen,<br />
• Modifikationen bzw. spezielle Ausprägungen von Operationen,<br />
• Modifikationen von Festlegungen für Anfangswerte von Attributen<br />
handeln.<br />
Durch Generalisierung bzw. Spezialisierung entstehen bzgl. der Inklusionsrelation zyklenfreie,<br />
gerichtete Graphen.<br />
In einer Klassenhierarchie (Darstellung siehe Abbildung 4.4.1) kann eine Klasse nur eine<br />
Superklasse haben. In einer Klassenheterarchie kann eine Klasse mehrere Superklassen haben.<br />
Eine Klasse mit mehr als einer Superklasse heisst Vereinigungsklasse.<br />
klassenname<br />
(Superklasse)<br />
klassenname klassenname (Subklassen)<br />
Abbildung 4.4.1: Klassenhierarchie<br />
Ein Dreieck stellt im Klassendiagramm die Generalisierungs-/Spezialisierungsbeziehung dar.<br />
Die Spitze des Dreiecks zeigt zur Superklasse.<br />
Constraints Für eine Generalisierung können Constraints angegeben werden (neben dem<br />
Dreieck in geschweiften Klammern notiert). Vordefinierte alternativ verwendbare Angaben<br />
sind overlapping vs. disjoint sowie complete vs. incomplete.
4.4. GENERALISIERUNG UND SPEZIALISIERUNG 38<br />
Sind alle Subklassen angegeben (die Anordnung ist vollständig, es werden keine weiteren<br />
Subklassen erwartet) wird complete angegeben, andernfalls incomplete. Der implizit angenommene<br />
Standardwert (d.h. falls die Angabe fehlt) ist incomplete. Kann ein Nachkomme<br />
von mehr als einer Subklasse abgeleitet werden, kann dies durch overlapping gekennzeichnet<br />
werden. Das Gegenteil kann durch disjoint gekennzeichnet werden.<br />
Beispiel (Abbildung 4.4.2): Fahrzeuge können sowohl nach der Antriebsart als auch nach<br />
dem Medium, auf bzw. in dem sie sich bewegen, spezialisiert werden. Ein Lastkraftwagen<br />
(LKW) ist demnach ein Fahrzeug, das motorgetrieben ist und sich auf dem Land bewegt.<br />
{overlapping}<br />
Antrieb<br />
Antrieb<br />
Fahrzeug<br />
Medium<br />
Medium<br />
{overlapping}<br />
Windgetrieben<br />
F.<br />
Motorgetrieben<br />
F.<br />
Land-<br />
F.<br />
Wasser-<br />
F.<br />
LKW<br />
Segelboot<br />
Amphibienf.<br />
Abbildung 4.4.2: Mehrfachvererbung<br />
4.4.2 Vererbung und Delegation<br />
Als Vererbung (inheritance) wird die Weitergabe der Beschreibung von Eigenschaften (Attribute,<br />
Operationen, Standardinitialwerte) einer Klasse an eine Subklasse bezeichnet. Die<br />
Eigenschaften bzgl. der Inst. der Klasse werden entlang der Inklusionsrelation (an die Subklasse)<br />
vererbt. Tatsächlich müssen die Eigenschaften einer Superklasse bei der Subklasse<br />
nicht noch einmal beschrieben werden, sie gelten implizit. Von besonderem Interesse ist,<br />
dass das auch bzgl. der Implementation gilt.<br />
Durch Mehrfachvererbung ist es möglich, dass eine Klasse, die mehrere Oberklassen hat,<br />
Merkmale von allen Oberklassen erbt.<br />
Ein Merkmal aus der gleichen Vorfahrenklasse, das in mehr als einem Pfad gefunden<br />
wird, wird nur einmal geerbt, es handelt sich um das gleiche Merkmal.<br />
Konflikte zwischen parallelen Definitionen führen zu Mehrdeutigkeiten, die bei der programmtechnischen<br />
Realisierung gelöst werden müssen.<br />
Delegation ist ein Implementierungsmechanismus, mit dessen Hilfe ein Objekt eine Operation<br />
auffängt und an ein anderes Objekt zur Ausführung sendet.<br />
Mittels Delegation kann fehlende Mehrfachvererbung umgangen werden.
4.4. GENERALISIERUNG UND SPEZIALISIERUNG 39<br />
4.4.3 Polymorphie<br />
Wörtlich: Vielgestaltigkeit. Eine durch Namen und Signatur spezifizierte Operation kann<br />
in unterschiedlichen (abgeleiteten) Klassen durch unterschiedliche Methoden implementiert<br />
sein.<br />
Signatur: Form eines Merkmales, d.h. Anzahl und Typen der Argumente einer<br />
Operation sowie ggf. der Typ des Rückgabewertes.<br />
Beispiel:<br />
Wir betrachten Unruhen (mobiles) und möchten mit einer Operation balancedp() bestimmen,<br />
ob sich eine Unruhe im Gleichgewicht befindet. Abstrahiert man von den physikalischen<br />
Gegebenheiten, handelt es sich bei den Unruhen um eine spezielle Art gewichtsbalancierter<br />
Bäume (im Unterschied zu höhenbalancierten Bäumen). Wir modellieren eine Klasse<br />
Mobile mit den Merkmalen ”<br />
linker Nachfolger” (lsucc) und ”<br />
rechter Nachfolger” (rsucc).<br />
Die Merkmale sind vom Typ Mobile, d.h. entsprechende Variablen können Referenzen auf<br />
Objekte vom Typ Mobile aufnehmen. Die Armlängen (links bzw. rechts von der Aufhängung)<br />
spielen zunächst keine Rolle. Es wird angenommen, sie sind für jede Unruhe links und<br />
rechts gleich groß.<br />
Abbildung 4.4.3: Ein Mobile (Unruhe)<br />
Jede Unruhe verwaltet ein Objekt vom Typ Node. Das Attribut key der Klasse Node ist<br />
vom Typ int (ganze Zahl) und dient zur Identifikation einer Unruhe (die Werte entsprechen<br />
den blauen Zahlen in Abb. 4.4.3). Für Unruhen ohne Nachfolger (d.h. für Blattknoten im<br />
Baum der Unruhen) enthält das Attribut weight das Gewicht (die Werte entsprechen den
4.4. GENERALISIERUNG UND SPEZIALISIERUNG 40<br />
grünen Zahlen in Abb. 4.4.3). Unruhen mit Nachfolgern besitzen kein Gewicht. Bei Unruhen<br />
mit Nachfolgern (inneren Knoten) wird weight der Wert 0 zugewiesen.<br />
Abbildung 4.4.4: Mobile mit unterschiedlichen Stablängen<br />
Unruhen mit Nachfolgern senden Nachrichten an die Nachfolger, um derenGewicht zu<br />
erhalten, und addiert diese.<br />
Die Klasse MobileA spezialisiert die Klasse Mobile in der Weise, dass die Arme einer Unruhe<br />
unterschiedlich lang sein können. Zur Speicherung der Armlängen dienen die Attribute<br />
larm und rarm. Die Attribute lsucc und rsucc werden von Mobile an MobileA vererbt. Die<br />
Methode balancedp() kann auf Instanzen von Mobile (binärer gewichtsbalancierter Baum)<br />
und von MobileA angewendet werden. Für jeden dieser Fälle wird in Abhängigkeit vom Typ<br />
des aktuellen Objekts allerdings eine andere Methode ausgeführt (siehe Abbildung 4.4.5). Da<br />
in diesem Fall zur Laufzeit entschieden wird, welche Methode tatsächlich ausgeführt wird,<br />
heißt diese Art der Polymorphie ”<br />
dynamische Polymorphie”.<br />
4.4.4 Abstrakte und konkrete Klassen<br />
abstrakte Klasse: Klasse, die selbst keine direkten Instanzen besitzt, deren Nachkommen<br />
aber direkte Instanzen besitzen.<br />
konkrete Klasse: Klasse, die direkte Instanzen besitzen kann ( ”<br />
instanziierbare Klasse“).<br />
Nur konkrete Klassen können Blattklassen im Vererbungsbaum sein (siehe Abbildung<br />
4.4.6)!<br />
Eine abstrakte Klasse kann sowohl abstrakte Operationen als auch nicht abstrakte Operationen<br />
definieren.<br />
Eine Operation kann abstrakt sein. Das ist der Fall, wenn die Klasse, die die Operation<br />
deklariert, diese Operation nicht als Methode implementiert. Eine Kennzeichnung im Klassensymbol<br />
erfolgt entweder durch den Zusatz {abstract} oder dadurch, dass die Signatur
4.4. GENERALISIERUNG UND SPEZIALISIERUNG 41<br />
Abbildung 4.4.5: Dynamische Polymorphie<br />
der Operation in italics gesetzt wird. Ein Auftreten der Signatur der Operation in einer abgeleiteten<br />
Klasse (ohne Kennzeichnung als abstrakte Operation) zeigt an, dass die abgeleitete<br />
Klasse eine Methode für die Operation definiert.<br />
4.4.5 Parametrisierte Klassen<br />
Eine parametrisierte Klasse ist ein Template, d.h. eine Beschreibung für eine Klasse mit einem<br />
oder mehreren formalen Parametern. Ein solches Template definiert eine Familie von<br />
Klassen, wobei eine Klasse durch Bindung aller formalen Parameter an aktuelle Werte spezifiziert<br />
wird. Eine parametrisierte Klasse ist also keine direkt nutzbare Klasse. Sie besitzt<br />
ungebundene Parameter, und erst durch deren Bindung entsteht eine Klasse.<br />
Ein kleines gestricheltes Rechteck wird dem die Klasse symbolisierenden Rechteck auf<br />
der rechten oberen Ecke überlagert. Das Rechteck enthält eine Liste der formalen Parameter<br />
der Klasse und gegebenenfalls deren Implementationstypen.<br />
Name, Attribute und Operationen der parametrisierten Klasse erscheinen normal im<br />
Klassensymbol und können Vorkommen der formalen Parameter enthalten.<br />
Beispiel: Keller von Elementen eines beliebigen Typs T (siehe Abbildung 4.4.7)<br />
Durch Bindung der formalen Parameter eines Templates für eine parametrisierte Klasse<br />
an aktuelle Parameter entsteht eine Klasse.<br />
Beispiel: (siehe Abbildung 4.4.8)<br />
Im Beispiel kann T an Integer gebunden werden, um einen Stack für Integer-Werte<br />
zu realisieren. Die UML sieht für die Bindung folgende Notation vor:<br />
< 〈parametername〉→〈aktuellerparameter〉 > vor.
4.4. GENERALISIERUNG UND SPEZIALISIERUNG 42<br />
Hat_Subklassen<br />
Subklasse<br />
Subklasse<br />
Klasse<br />
1..* 1..*<br />
Hat_Subklassen<br />
Konkrete<br />
Klasse<br />
Abstrakte<br />
Klasse<br />
1<br />
Superklasse<br />
1<br />
Superklasse<br />
Konkrete<br />
Nicht-Blattklasse<br />
Konkrete<br />
Blattklasse<br />
Abbildung 4.4.6: Metamodell ”<br />
abstrakte u. konkrete Klassen”<br />
Stack<br />
T<br />
empty () : boolean<br />
push (x : T)<br />
pop ()<br />
top () : T<br />
Abbildung 4.4.7: Parametrisierte Klasse Stack
4.4. GENERALISIERUNG UND SPEZIALISIERUNG 43<br />
Stack < Integer ><br />
oder alternativ<br />
Stack<br />
T<br />
empty () : boolean<br />
push (x : T)<br />
pop ()<br />
top () : T<br />
><br />
Stack<br />
Abbildung 4.4.8: Bindung für eine parametrisierte Klasse
4.5. DYNAMISCHE MODELLIERUNG 44<br />
4.5 Dynamische Modellierung<br />
4.5.1 Instanzen als Laufzeitobjekte<br />
Ein Objekt ist als Instanz einer Klasse ein Laufzeitobjekt (in der Ebene M0 ). Es belegt bei<br />
der programmtechnischen Realisierung Speicherplatz und hat eine gewisse Lebensdauer.<br />
Auch Klassen können als Laufzeitobjekte behandelt werden, man spricht dann von Klassenobjekten.<br />
Objekte können benannt werden. Das bedeutet, dass eine Assoziation zwischen einem<br />
Namen und einer Objektreferenz aufgebaut wird. Namen sind einem Sichtbarkeitsbereich<br />
(engl. scope) zugeordnet. Wir unterscheiden verschiedene Sichtbarkeitsbereiche:<br />
• global<br />
• Paket-lokal<br />
• Klassen-lokal<br />
• Methoden-lokal<br />
Im Laufe der Zeit können sich Assoziationen ändern, d.h. ein Name kann mit einer Referenz<br />
auf ein anderes Objekt assoziiert werden (z.B. Wertveränderung einer Variablen).<br />
Typkompatibilität bedeutet, dass einem Bezeichner, der mit einer Referenz auf ein Objekt<br />
einer Klasse assoziiert ist, ein Objekt einer anderen Klasse ( ”<br />
Referenzklasse”) zugeordnet<br />
werden darf. Typkompatibilität wird in der UML nicht speziell unterstützt. Welche Typen<br />
kompatibel sind, hängt von der verwendeten objektorientierten Programmiersprache ab.<br />
Die Erzeugung eines Objektes kann auf zweierlei Weise erfolgen:<br />
• statisch, mit der Initialisierung des Anwendungssystems<br />
• dynamisch, durch Anwendung eines Konstruktors der Klasse während der Ausführung<br />
einer Methode.<br />
Das Erzeugen und Löschen von Objekten wird in Sequenzendiagrammen dargestellt. Das<br />
Sequenzendiagramm zeigt den Programmablauf einer Methode und berücksichtigt dabei die<br />
Interaktion mit anderen Objekten.<br />
In objektorientierten Programmiersprachen kann der Zeitpunkt zum Löschen eines Objektes<br />
selbst bestimmt werden (z.B. Java).<br />
Objektzustände<br />
vordefinierte Ausprägungen für Zustände von Objekten sind:<br />
• nicht existent (eigentlich ein Widerspruch!)<br />
• existent: Für ein Objekt wurde Speicher erfolgreich angefordert, die Zuordnung<br />
der Methoden aus der Klassendefinition war erfolgreich.<br />
• initialisiert
4.5. DYNAMISCHE MODELLIERUNG 45<br />
• deinitialisiert<br />
• gelöscht<br />
Für persistente Objekte werden zusätzliche Zustände erklärt: ausgelagert, im-<br />
Speicher.<br />
4.5.2 Interaktionsmodelle<br />
Interaktionsmodelle werden in Interaktionsdiagrammen dargestellt. Die UML stellt zur Darstellung<br />
von Interaktionsmodellen folgende Diagrammarten bereit:<br />
• Sequezdiagramm<br />
• Kommunikationsdiagramm<br />
• Timing-Diagramm<br />
• Interaktionsübersichtsdiagramm<br />
Sequenzdiagramm Das Sequenzdiagramm dient der Visualisierung des Interaktionsmodells.<br />
Es beschreibt Interaktionen zwischen einer ausgewählten Menge von Objekten durch<br />
den Austausch von Nachrichten. Die Interaktionen zwischen Objekten gehen stets von Objekten<br />
oder Prozessen aus. Das Sequenzdiagramm berücksichtigt den zeitlichen Ablauf und<br />
zeigt die zeitliche Abfolge von Interaktionen in einer zeitlich begrenzten Situation. Dargestellt<br />
werden: Objekte, Zeitachse (jedem Objekt wird eine vertikale Lebenslinie zugeordnet),<br />
Nachrichtenaustausch in Form von Methodenaufrufen. Zeitaspekte werden durch zusätzliche<br />
Bedingungen dargestellt.<br />
Symbol zum Erzeugen eines Objektes: (siehe Abbildung 4.5.1)<br />
new Klassenname()<br />
neuObjekt : Klassenname<br />
Abbildung 4.5.1: Erzeugen eines Objektes<br />
4.5.3 Statechart-Modelle<br />
In der UML werden Zustandsautomaten” zur Modellierung des Verhaltens von Objekten des<br />
”<br />
Typs Classifier eingesetzt. Zustandsautomaten basieren auf den von D. Harel eingeführten<br />
Statecharts”. Ein Zustandsautomat (das ist ein endlicher Automat) ist ein Graph, bestehend<br />
aus Zuständen und Transitionen, der die Reaktion eines Objektes einer Klasse auf den<br />
”<br />
Empfang von äußeren Stimuli” beschreibt. Ein Zustandsautomat ist einer Klasse oder einer<br />
”<br />
Methode zugeordnet.
4.5. DYNAMISCHE MODELLIERUNG 46<br />
Ein Zustand (engl. state) ist die Verfassung (im Verlaufe des Lebens) eines Objektes, in<br />
der es einer Bedingung genügt, eine Aktivität ausführt (andauernd) oder auf ein Ereignis<br />
wartet. Ein Objekt verweilt eine endliche Zeit in einem Zustand. Nicht jede Änderung eines<br />
Wertes einer Instanzvariablen wird als Zustandsänderung betrachtet, es gibt sogenannte<br />
verallgemeinerte Zustände”.<br />
”<br />
Eine Transition ist eine durch ein Ereignis verursachte Zustandsänderung eines Objektes.<br />
Ereignisse (engl. events) sind bemerkenswerte Vorkommnisse”. In Bezug auf Statecharts<br />
”<br />
ist ein Ereignis ein Vorkommnis, das eine Transition auslöst. Es hat keine Dauer. Der Zustand<br />
eines Objektes bestimmt die Reaktion des Objektes auf ankommende Ereignisse. Reaktionen<br />
können Aktionen und/oder Zustandsänderungen sein. Wenn ein Ereignis empfangen wird,<br />
hängt der nächste Zustand des Objekts sowohl vom aktuellen Zustand als auch vom empfangenen<br />
Ereignis ab. Ein neu erzeugtes Objekt befindet sich zunächst in einem Initialzustand.<br />
Aktionen (engl. actions) sind atomar und nicht unterbrechbar. Ein Zustand kann mit<br />
einer andauernden Aktivität verbunden sein. Eine solche Aktivität wird selbst als Zustandsautomat<br />
ausgedrückt. Eine andauernde Aktivität kann als Paar von Aktionen ausgedrückt<br />
werden.<br />
Notation: Ein Statechart (Zustands-Diagramm) repräsentiert einen Zustandsautomaten.<br />
Zustände werden durch Zustandsymbole (Rechteck mit abgerundeten Ecken) repräsentiert.<br />
Sie können einen Namen beinhalten und optional durch horizontale Linien in bis zu drei<br />
Bereiche geteilt werden. Spezielle Symbole existieren für den Startzustand und Endzustände.<br />
Transitionen werden durch Pfeile dargestellt, die Zustandssymbole verbinden (vgl. Abb.<br />
4.5.2). Ereignisse, die Zustandsübergänge auslösen, werden als Beschriftung an die zugehörigen<br />
Pfeile geschrieben. Übergänge ohne Ereignisbeschriftung werden automatisch ausgelöst,<br />
sobald die mit dem Zustand verbundenen Aktionen/Aktivitäten abgeschlossen sind.<br />
Zustandsübergänge können an Bedingungen geknüpft sein. Eine Bedingung muss erfüllt sein,<br />
damit ein Ereignis tatsächlich einen Zustandswechsel bewirkt.<br />
Syntax: event(argument ...) [ bedingung ]/ operation(argument ...)<br />
zustand<br />
< transition ><br />
Abbildung 4.5.2: Transition<br />
zustand<br />
Alle von einem Zustand ausgehenden Transitionen müssen unterschiedlichen Ereignissen entsprechen!<br />
Es darf keine von Endzuständen ausgehenden Transitionen geben.<br />
Komposite Zustände<br />
Ein Zustand kann mittels<br />
• and-Relation in parallele Teilzustände und/oder<br />
• or-Relation in sich gegenseitig ausschließende sequentielle Teilzustände<br />
dekomponiert werden. Eine Verfeinerung eines Zustandes ist nur auf einem dieser beiden<br />
Wege möglich. Bei parallelen, konkurrierenden Teilzuständen wird das Zustandssymbol durch
4.5. DYNAMISCHE MODELLIERUNG 47<br />
stornieren()<br />
[reserveirtePlaetze>1]<br />
OhneReservierung<br />
entry /ruecksetzen()<br />
reservieren()<br />
stornieren()<br />
[reserveirtePlaetze=1]<br />
Teilweise-<br />
Reserviert<br />
reservieren()<br />
[freiePlaetze>1]<br />
flug<br />
Einrichten()<br />
flugStreichen()<br />
stornieren()<br />
reservieren()<br />
[freiePlaetze=1]<br />
schliessen()<br />
Ausgebucht<br />
Geschlossen<br />
schliessen()<br />
Abbildung 4.5.3: Flugreservierungs-System<br />
gestrichelte Linien in weitere Abschnitte unterteilt.<br />
Pseudozustände<br />
Es gibt verschiedene Arten von Pseudozuständen:<br />
Symbol Zustand Beschreibung<br />
Startzustand<br />
(Initial)<br />
Entscheidung<br />
(Choice)<br />
Betreten des Zustandsautomaten, kein Trigger,<br />
keine Bedingung<br />
Kreuzung<br />
(Junction)<br />
Terminator<br />
(Terminate)<br />
Austrittspunkt<br />
(Exit Point)<br />
mindestens je eine eingehende und ausgehende<br />
Transition, dient der Vereinfachung<br />
bricht die Ausführung des Zustandsautomaten<br />
ab, Ende der Lebensdauer<br />
Bei der Realisierung von Zustandsdiagrammen mittels Topcased werden unterschiedliche<br />
Arten von Events unterschieden (vgl. Abb. 4.5.6). Der Übergang von einem Zustand über<br />
einen Pseudozustand zu einem weiteren Zustand muss ohne Zeitverbrauch erfolgen. Das<br />
System darf sich nicht in einem Pseudozustand aufhalten. Der Startzustand ist ebenfalls ein<br />
Pseudozustand.
4.5. DYNAMISCHE MODELLIERUNG 48<br />
Abbildung 4.5.4: Modell einer Klimaanlage
4.5. DYNAMISCHE MODELLIERUNG 49<br />
Abbildung 4.5.5: Unterzustände
4.5. DYNAMISCHE MODELLIERUNG 50<br />
Abbildung 4.5.6: Zustandsdiagramme in Topcased
4.5. DYNAMISCHE MODELLIERUNG 51<br />
4.5.4 Aktivitäts-Diagramme<br />
Das Aktivitäts-Diagramm ist eine spezielle Art des Zustandsdiagramms, das hauptsächlich<br />
Aktivitäten/Aktionen und Übergänge des Steuerflusses zwischen diesen zeigt. Eine Aktivität<br />
ist einem Zustand zugeordnet und repräsentiert eine andauernde interne Aktion.<br />
Eine Aktivität wird durch einen gerichteten Graphen modelliert. Knoten können Aktionsknoten,<br />
Kontrollknoten oder Objektknoten sein. Durch Kanten wird das Steuerflussmodell<br />
repräsentiert.<br />
Aktionen sind die kleinsten ausführbaren Funktionseinheiten innerhalb einer Aktivität.<br />
Eine Aktion kann<br />
• ein elementarer Verarbeitungschritt oder<br />
• ein Aktivitätsaufruf<br />
sein.<br />
Eine Aktivität kann Kontrollknoten enthalten:<br />
• Start- und Endknoten<br />
Start- und Endzustand werden wie im Zustandsdiagramm dargestellt.<br />
• Entscheidung und Zusammenführung<br />
• Splitting und Synchronisation<br />
Transitionen können geteilt und synchronisiert werden (Parallelität).<br />
Abbildung 4.5.7: Darstellung von Aktionen (oben) und Aktivitäten (unten) in einem Aktivitätsdiagramm<br />
(Topcased)<br />
Symbole zur Darstellung von Aktivitäten (siehe Abbildung 4.5.7) enthalten eine Aktionsbeschreibung<br />
in Form eines Namens oder eines Freitextes. Dabei kann auch eine Algorithmenbeschreibung<br />
in Form von Pseudo- oder Programmcode vorliegen. Bedingungen werden<br />
in Form boolescher Ausdrücke notiert. Sie werden an die die Aktivität verlassenden Transitionen<br />
geschrieben. Es können auch explizit Verzweigungspunkte in Form kleiner Rauten<br />
verwendet werden, von denen die durch Bedingungen unterschiedenen Transitionen ausgehen.
4.5. DYNAMISCHE MODELLIERUNG 52<br />
[x 0] [x 0]<br />
oder<br />
[x 0]<br />
[x 0]<br />
Abbildung 4.5.8: Entscheidung, Kontrollknoten Entscheidung<br />
Teilung<br />
Synchronisation<br />
Abbildung 4.5.9: Aufsplitten des Kontrollflusses und Synchronisation<br />
Aktivitäten können sich auf Objektzustände beziehen (Abbildung 4.5.10). Diese Möglichkeit<br />
gibt es allerdings in UML 2 nicht mehr.<br />
aktivität1()<br />
aktivität2()<br />
object<br />
[ zustand ]<br />
Abbildung 4.5.10: Einbeziehung von Objektzuständen<br />
Ein gestrichelter Pfeil, der von einer Aktivität zu einem Objekt führt, kennzeichnet einen<br />
aus der Aktivität resultierenden Objektzustand. Ein gestrichelter Pfeil, der von einem Objekt<br />
zu einer Aktivität führt, kennzeichnet, dass die Aktivität den Objektzustand voraussetzt.<br />
Beispiel: Kfz-Rücknahme innerhalb einer Anwendung Kfz-Vermietung (siehe Abbildung<br />
4.5.11).<br />
Als Beispiel für ein in Topcased erstelltes Aktivitätsdiagramm wird die Einladung zu<br />
einer Feier betrachtet (vgl. Abb. 4.5.12). Die Einladung ist ein Eingangsparameter.<br />
Ein Aktivitäsdiagramm kann einem Use-Case zugeordnet werden. Wie das in Topcased<br />
erfolgt, zeigt Abb. 4.5.13.
4.5. DYNAMISCHE MODELLIERUNG 53<br />
Kunde<br />
auswählen<br />
Kunde<br />
Vertrag<br />
eines Kunden<br />
auswählen<br />
Vertrag<br />
auswählen<br />
Vertragseinhaltung<br />
prüfen<br />
Vertrag<br />
[Kfz übergeben]<br />
Vertrag<br />
[Kfz zurück]<br />
Kfz zurücknehmen<br />
Abrechnungsdaten<br />
erfassen<br />
Abrechnungs<br />
daten<br />
Rechnung<br />
erstellen<br />
Vertrag<br />
[abgeschlossen]<br />
Rechnung<br />
Abbildung 4.5.11: Aktivitätendiagramm zum Vorgang ”<br />
Kfz-Rücknahme”
4.5. DYNAMISCHE MODELLIERUNG 54<br />
Abbildung 4.5.12: Aktivitätsdiagramm in Topcased
4.5. DYNAMISCHE MODELLIERUNG 55<br />
Abbildung 4.5.13: Zuordnung eines Aktivitätsdiagramms zu einem UseCase in Topcased
Kapitel 5<br />
Von UML nach Java ...<br />
In diesem Kapitel wird untersucht, wie man technologisch von einer Anzahl unterschiedlicher<br />
erstellter Modelle zu einer ausführbaren Anwendung gelangt. Dabei wird besonders auf<br />
automatisierbare Vorgänge geachtet, die in den Build-Prozess verlagert werden können. In<br />
Abhängigkeit der zur Modellierung verwendeten Werzeuge ist bereits während der Modellierung<br />
Quellcode entstanden. d.h. generiert worden. Nun kommt es darauf an, Code-Gerüste<br />
zu schaffen oder bestehende Code-Gerüste um weiteren Code anzureichern. Dabei bietet eine<br />
Zielprogrammiersprache mehr oder weniger gut geeignete Konstukte an. Schwierig wird es<br />
etwa, wenn UML-Modelle (Klassen) auf eine nicht objektbasierte Sprache abgebildet werden<br />
sollen. Die von uns betrachtete Abbildung von UML-Modellen auf Java, also eine objektbasierte<br />
Sprache ist zwar deutlich einfacher, aber auch nicht ganz ohne Probleme.<br />
Bei der Umsetzung von Klassen-Modellen in Java-Anwendungen gibt es zwei Möglichkeiten:<br />
• Nutzung der mit der Entwicklungsumgebung bereitgestellten Frameworks, Pakete oder<br />
Klassen für die Implementierung (z.B. Hashtable).<br />
• Implementation neuer Klassen, die jedoch bestehende Klassen erweitern können und/oder<br />
Interfaces implementieren.<br />
Java-Anwendungen können Applets und Applikationen sein.<br />
5.1 Applikationen und Applets<br />
Eine Applikation ist ein Programm in einer virtuellen Maschinensprache (Virtual Machine<br />
Specification). Ein solches Programm wird interpretativ durch den Java-Interpreter java<br />
verarbeitet:<br />
java MyApplication<br />
Die Applikation wid durch Compilation aus einem Quellprogramm MyApplication.java<br />
erzeugt und in MyApplikation.class gespeichert. Die Datei MyApplication bildet eine<br />
Übersetzungseinheit.<br />
56
5.1. APPLIKATIONEN UND APPLETS 57<br />
javac MyApplication.java<br />
Durch diese Art der Verarbeitung wird Plattformunabhängigkeit gewährleistet, die Verfügbarkeit<br />
eines Interpreters auf jeder Betriebssystem-Plattform vorausgesetzt.<br />
Java-Applikationen werden von einer Klassenstruktur gebildet.<br />
Beispiel:<br />
public class MyApplication{<br />
public static void main (String args[]){<br />
System.out.println("Hello world");<br />
for (int i = args.length - 1; i>=0; i--)<br />
System.out.println(args[i]);<br />
}<br />
}<br />
Die Hauptklasse einer Anwendung (z.B. MyApplication) muss die Methode main() implementieren.<br />
Mit static gekennzeichnete Methoden oder Variablen beziehen sich auf eine<br />
Klasse als Objekt, und nicht auf ein Instanzobjekt. Sie werden Klassenmethoden bzw. Klassenvariablen<br />
genannt. Für jede Methode ist ein Ergebnistyp festzulegen. Wird dieser mit<br />
void angegeben, liefert die Methode keinen Ergebniswert. Beim Programmaufruf können<br />
Kommandozeilen–Parameter an eine Applikation übergeben werden. Im Beispiel kann über<br />
das Array args auf solche Parameter zugegriffen werden. Zur Verwaltung der Länge eines<br />
Arrays besitzt jedes Array das Feld length. Dieses Feld wird wie eine Instanzvariable eines<br />
Objektes zugegriffen. Strings sind immer Objekte, also keine Character-Arrays.<br />
Bei der Erzeugung von String-Objekten kann eine Initialisierung mittels String-Literaten<br />
vorgenommen werden:<br />
String s = new String("Hello world")<br />
Beispiel für einen Aufruf:<br />
java MyApplication huhu haha hoho<br />
Hello world!<br />
hoho<br />
haha<br />
huhu<br />
Applets<br />
Applets sind Java- Anwendungen, die in HTML-Dokumente mittels eines Applet-Tags eingebunden<br />
werden können.<br />
Beispiel:<br />
<br />
5.1. APPLIKATIONEN UND APPLETS 58<br />
Applet-Demonstration<br />
<br />
<br />
<br />
Lehrveranstaltung "<strong>Software</strong> <strong>Engineering</strong>"<br />
<br />
The Browser doesn’t know the applet-tag.<br />
<br />
<br />
<br />
Ein Browser, der das Applet-Tag nicht kennt, überliest es. Alternative HTML-Elemente<br />
können in die Applet-Umgebung eingefügt werden. Kennt ein Browser das Applet-Tag im<br />
obigen Beispiel nicht, erscheint stattdessen ”<br />
The Browser doesn’t know the applet-tag”.<br />
Das Attribut ALT wird verwendet, um bei Textbrowsern anstelle des Applets alternativen<br />
Text zu zeigen.<br />
Mit dem Attribut CODE wird die Datei spezifiziert, in der die Hauptklasse des Applets<br />
liegt.<br />
Jedes Applet besitzt die Methoden init(), start(), stop() und destroy(), die standardmäßig<br />
keinen Code enthalten.<br />
Beispiel:<br />
import java.applet.Applet;<br />
import java.awt.*;<br />
public class AppletDemo extends Applet{<br />
String text;<br />
public void init(){<br />
System.out.println("init");<br />
if ((text = getParameter("text")) == null)<br />
text = "kein text-Parameter vorhanden";<br />
System.out.println(text);<br />
}<br />
public void start(){<br />
System.out.println("start");<br />
}<br />
public void stop(){<br />
System.out.println("stop");<br />
}<br />
public void destroy(){<br />
System.out.println("destroy");
5.2. BAUM-KLASSEN 59<br />
}<br />
public void paint(Graphics g){<br />
g.drawString("Hello world !",90,25);<br />
}<br />
}<br />
Der Browser gibt Ausgaben auf der Standardausgabe über die ”<br />
Java-Console” aus. Zur<br />
Präsentation von Applets kann auch der Appletviewer verwendet werden. Ist das Applet<br />
während der Präsentation geändert worden, d.h. es ist ein neues .class-File erzeugt worden,<br />
muss der Appletviewer neu gestartet werden, um das geänderte Applet zu präsentieren.<br />
5.2 Baum-Klassen<br />
Als Beispiel sollen die im Abschnitt über Polymorphie betrachteten und modellierten Unruhen<br />
nun implementiert werden. Getreu dem Klassenmodell (vgl. Abbildung 4.4.5) programmieren<br />
wir die Klassen Mobile, MobileA und Node.<br />
public class Mobile{<br />
Node root = new Node();<br />
Mobile lsucc,rsucc;<br />
void insKeyWeight(int k,int w){<br />
root.setKey(k);<br />
root.setWeight(w);<br />
}<br />
void insLsucc(Mobile s){<br />
lsucc = s;<br />
root.setWeight(0);<br />
}<br />
void insRsucc(Mobile s){<br />
rsucc = s;<br />
root.setWeight(0);<br />
}<br />
int weight(){<br />
if (root.getWeight() == 0)<br />
return(lsucc.weight() + rsucc.weight());<br />
else<br />
return( root.getWeight());<br />
}<br />
boolean balancedp(){<br />
if (root.getWeight() == 0)<br />
return(lsucc.balancedp() &&
5.2. BAUM-KLASSEN 60<br />
}<br />
rsucc.balancedp() &&<br />
(lsucc.weight() == rsucc.weight()));<br />
else<br />
return(true);<br />
}<br />
void makebalance(){<br />
}<br />
class Node{<br />
private int key,weight;<br />
}<br />
int getWeight(){<br />
return(weight);<br />
}<br />
int getKey(){<br />
return(key);<br />
}<br />
void setWeight(int weight){<br />
this.weight = weight;<br />
}<br />
void setKey(int key){<br />
this.key = key;<br />
}<br />
public class MobileA extends Mobile{<br />
int larm,rarm;<br />
void insLarm(int a){<br />
larm = a;<br />
}<br />
void insRarm(int a){<br />
rarm = a;<br />
}<br />
boolean balancedp(){<br />
if (root.getWeight() == 0)<br />
return( lsucc.balancedp() &&<br />
rsucc.balancedp() &&<br />
(larm * lsucc.weight() == rarm * rsucc.weight()));<br />
else
5.2. BAUM-KLASSEN 61<br />
}<br />
}<br />
return(true);<br />
Unruhen vom Typ Mobile befinden sich im Gleichgewicht, wenn es sich um Gewichte<br />
handelt, oder andernfalls, wenn links und rechts Unruhen hängen, die sich selbst im Gleichgewicht<br />
befinden, und die Gewichte links und rechts gleich sind.<br />
Die Instanzvariablen lsucc und rsucc können auch auf Instanzen von Subklassen von<br />
Mobile verweisen. Die Methode weight() kann so unmittelbar auch auf Unruhen vom Typ<br />
MobileA angewendet werden. Bei Anwendung der Methode weight auf die in lsucc und<br />
rsucc verwalteten Objekte wird der Typ nicht zur Compilezeit festgelegt! Die Methode kann<br />
laufzeitabhängig auf eine Instanz der Klasse Mobile oder der Klasse MobileA angewendet<br />
werden (auch keine Deklaration als ”<br />
virtual” notwendig, wie etwa in Modula-2).<br />
Innerhalb der Applikation UseMobiles werden zwei Unruhen konstruiert. Mit den Konstruktoren<br />
werden Objekte erzeugt, die dann mit den Methoden insKeyWeight, insLsucc<br />
und insRsucc bewertet und in Beziehung zueinander gesetzt werden:<br />
public class UseMobiles{<br />
public static void main(String argv[]){<br />
System.out.println("Konstruktion von Mobiles");<br />
Mobile tree1, tree2;<br />
MobileA tree3, tree4;<br />
// Beispiel1<br />
tree1 = new Mobile();<br />
tree1.insKeyWeight(5,1);<br />
tree2 = new Mobile();<br />
tree2.insKeyWeight(4,0);<br />
tree2.insLsucc(tree1);<br />
tree1 = new Mobile();<br />
tree1.insKeyWeight(6,1);<br />
tree2.insRsucc(tree1);<br />
tree1 = new Mobile();<br />
tree1.insKeyWeight(3,0);<br />
tree1.insLsucc(tree2);<br />
tree2 = new Mobile();<br />
tree2.insKeyWeight(7,2);<br />
tree1.insRsucc(tree2);<br />
tree2 = new Mobile();<br />
tree2.insKeyWeight(1,0);<br />
tree2.insRsucc(tree1);<br />
tree1 = new Mobile();
5.2. BAUM-KLASSEN 62<br />
tree1.insKeyWeight(2,4);<br />
tree2.insLsucc(tree1);<br />
System.out.println(tree2.weight());<br />
if (tree2.balancedp())<br />
System.out.println("balanced");<br />
else<br />
System.out.println("not balanced");<br />
// Beispiel2<br />
tree3 = new MobileA();<br />
tree3.insKeyWeight(5,2);<br />
tree4 = new MobileA();<br />
tree4.insKeyWeight(4,0);<br />
tree4.insLsucc(tree3);<br />
tree4.insLarm(1);<br />
tree4.insRarm(2);<br />
tree3 = new MobileA();<br />
tree3.insKeyWeight(6,1);<br />
tree4.insRight(tree3);<br />
tree3 = new MobileA();<br />
tree3.insKeyWeight(3,0);<br />
tree3.insLsucc(tree4);<br />
tree3.insLarm(1);<br />
tree3.insRarm(3);<br />
tree4 = new MobileA();<br />
tree4.insKeyWeight(7,1);<br />
tree3.insRight(tree4);<br />
tree4 = new MobileA();<br />
tree4.insKeyWeight(1,0);<br />
tree4.insRsucc(tree3);<br />
tree4.insLarm(4);<br />
tree4.insRarm(3);<br />
tree3 = new MobileA();<br />
tree3.insKeyWeight(2,3);<br />
tree4.insLsucc(tree3);<br />
System.out.println(tree4.weight());<br />
if (tree4.balancedp())<br />
System.out.println("balanced");<br />
else<br />
System.out.println("not balanced");<br />
System.out.println("Bye Mobiles!");<br />
}
5.3. INTERFACES UND MEHRFACHVERERBUNG 63<br />
}<br />
Mobile() ist ein Konstruktor der Klasse Mobile. Bei Aufruf von new wird Speicherplatz<br />
ausgefasst und eine Instanz von Mobile angelegt. Alle Klassen, die selbst keinen Konstruktor<br />
definieren, besitzen implizit einen Konstruktor, der keine Parameter erwartet. Ein Konstruktor<br />
hat den Namen der Klasse und keinen Ergebnistyp.<br />
5.3 Interfaces und Mehrfachvererbung<br />
In Java gibt es im Unterschied zu C++ keine Mehrfachvererbung. Statt dessen gibt es sog.<br />
Interfaces, die reine Schnittstellen darstellen und keinerlei Implementierung enthalten.<br />
Interfaces definieren ausschließlich abstrakte Methoden und Konstanten.<br />
Eine Klasse kann ein oder auch mehrere Interfaces ”<br />
erben”, in dem sie die Interfaces<br />
implementiert. Die Klasse erbt damit alle abstrakten Methoden und alle Konstanten.<br />
Um geerbte Methoden anwenden zu können, müssen sie zuvor geeignet überschrieben, d.<br />
h. definiert werden.<br />
Die Eigenschaft, ein Interface zu implementieren, vererbt eine Klasse an ihre Nachfahren.<br />
Deklaration von Interfaces in Java<br />
Die Deklaration von Interfaces erfolgt analog der von Klassen, wobei anstelle des Schlüsselwortes<br />
class das Schlüsselwort interface verwendet wird.<br />
Festlegungen:<br />
• alle Elemente eines Interfaces sind implizit final static. Eine explizite gemeinsame<br />
oder einzelne Angabe der Modifier final und static ist auch möglich. Ein Initialwert<br />
muss angegeben werden.<br />
• ein Interface muss als public vereinbart werden, wenn es außerhalb des Paketes, zu<br />
dem es definiert wurde, verwendet werden soll. Ein public-Interface muss in einer<br />
Datei stehen, die genauso heisst, wie das Interface.
5.3. INTERFACES UND MEHRFACHVERERBUNG 64<br />
Beispiel:<br />
LKWs sind motorgetriebene Fahrzeuge und gleichzeitig Landfahrzeuge (vgl.<br />
Abbildung 4.4.2). Wegen der fehlenden Mehrfachvererbung in Java werden<br />
Landfahrzeuge bzw. Wasserfahrzeuge jeweils als Interface LandF bzw.<br />
WasserF definiert.<br />
public interface LandF{<br />
String medium = "Land";<br />
void rollen(String untergrund);<br />
}<br />
public interface WasserF{<br />
String medium = "Wasser";<br />
void schwimmen(String untergrund);<br />
}
5.3. INTERFACES UND MEHRFACHVERERBUNG 65<br />
Fahrzeuge werden wie folgt definiert:<br />
class Fahrz{<br />
void start(){};<br />
void bewegen(){};<br />
void stop(){};<br />
}<br />
class MotorF extends Fahrz{<br />
void start(){<br />
System.out.println("Motor gestartet");<br />
}<br />
void stop(){<br />
System.out.println("Motor gestoppt");<br />
}<br />
void bewegen(String untergrund, LandF fz ){<br />
System.out.println("Fahrwerk_aktivieren");<br />
fz.rollen(untergrund);<br />
}<br />
void bewegen(String untergrund, WasserF fz ){<br />
System.out.println("Fahrwerk_aktivieren");<br />
fz.schwimmen(untergrund);<br />
}<br />
}<br />
class LKW extends MotorF implements LandF{<br />
public void rollen(String untergrund){<br />
if (untergrund.equals(medium))<br />
System.out.println("Fahrt des LKW prolemlos");<br />
else<br />
System.out.println("LKW im Wasser oder in der Luft!");<br />
}<br />
public void fortbewegen(String untergrund){<br />
this.start();<br />
this.bewegen(untergrund,this);<br />
stop();<br />
}<br />
}
5.3. INTERFACES UND MEHRFACHVERERBUNG 66<br />
Die Anwendung zeigt die Klasse InterfaceDemo:<br />
public class InterfaceDemo {<br />
public static void main(String args[]){<br />
LKW myF = new LKW();<br />
String under = "Land";<br />
myF.fortbewegen(under);<br />
String undern = "Wasser";<br />
myF.fortbewegen(undern);<br />
}<br />
}<br />
Der Test:<br />
/* Test:<br />
~> java InterfaceDemo<br />
Motor gestartet<br />
Fahrwerk_aktivieren<br />
Fahrt des LKW prolemlos<br />
Motor gestoppt<br />
Motor gestartet<br />
Fahrwerk_aktivieren LKW im Wasser oder in der Luft!<br />
Motor gestoppt<br />
~><br />
*/<br />
Implementierung eines Interface Damit ein Interface wirksam wird, muss es von einer<br />
Klasse implementiert werden. Anstelle des für die Vererbung von Klassen verwendeten<br />
Schlüsselworts extends wird hier das Schlüsselwort implements verwendet.<br />
Es ist nicht zwingend, dass die Klasse, die ein Interface implementiert, dessen Methoden<br />
tatsächlich eine Funktionalität gibt. Die Methodenkörper können auch leer sein, allerdings<br />
muss die Klasse alle Methoden des Interface überschreiben.<br />
Eine Klasse kann ein oder mehrere Interfaces implementieren. Die Interface-Bezeichner<br />
werden in diesem Fall nach dem Schlüsselwort implements durch Kommata getrennt in der<br />
Klassendefinition aufgeführt. Eine Kombination von extends und implements ist möglich.<br />
Die Benutzung von Interfaces erfolgt analog der von Klassen. An eine Variable von einem<br />
Interface-typ können Verweise auf Objekte aller Klassen zugewiesen werden, die das Interface<br />
implementieren. Eine Instanz einer Klasse, die mehrere Interfaces implementiert, kann an<br />
Verweise auf alle Interfaces zugewiesen werden, die diese Klasse implementiert.<br />
Durch das Interface-Konzept in Verbindung mit dem Konzept der einfachen Vererbung<br />
wird sichergestellt, dass eine Klasse nur von einer Klasse gebrauchsfertige” Methoden und<br />
”
5.4. IMPLEMENTATION VON AGGREGATIONEN: VEKTOREN UND<br />
HASH-TABELLEN 67<br />
Instanzvariablen erben kann.<br />
5.4 Implementation von Aggregationen: Vektoren und Hash-<br />
Tabellen<br />
5.4.1 Vektoren<br />
Ein Vektor ist in Java (als Instanz der Klasse Vector) ein eindimensionales dynamisches<br />
Array, das als Elemente Instanzen von Object und allen Nachfahren aufnehmen kann. Die<br />
tatsächliche Größe eines Vektors wird stufenweise automatisch an die benötigte Größe angepasst.<br />
Es existieren u.a. folgende Methoden:<br />
public final synchronized void addElement (Object elem)<br />
fügt das Element elem nach allen vorhandenen Elementen (d.h. nach<br />
dem Element mit dem bisher größten Index) ein, wobei die Kapazität<br />
des Vektors ggfs. vergrößert wird.<br />
public final synchronized void insertElement (Object elem, int index)<br />
fügt das Element elem an der Position index ein, ggf. wird dazu die<br />
Kapazität des Vektors erhöht. Der Index ist 0-basiert.<br />
public final synchronized void setElementAt (Object elem, int index)<br />
falls index ein im Vektor vorhandener Index ist, wird das Element an<br />
dieser Stelle durch elem ersetzt.<br />
public final boolean contains (Object elem)<br />
liefert true, wenn elem ein Element des Vektors ist und sonst false.<br />
5.4.2 Hash-Technik<br />
Hash–Verfahren ermöglichen einen effizienten Zugriff auf Datenelemente in veränderlichen<br />
Datenbeständen (Elemente suchen, einfügen, löschen). Es wird zwischen direkter und indirekter<br />
Hash–Technik unterschieden.<br />
Direkte Hash-Technik<br />
Es existiert eine eineindeutige Abbildung der Menge aller möglichen Schlüssel U (Universum)<br />
auf die Menge der Adressen einer Tabelle.<br />
Sei K die Menge der tatsächlich benutzten Schlüssel K ⊆ U. Falls card(U) ≫ card(K)<br />
bzw. card(U ) sehr groß ist, ist dieses Verfahren ineffizient, die meisten Tabellenplätze bleiben<br />
unbenutzt. In diesem Fall geht man zur indirekten Hashtechnik über.<br />
Indirekte Hash-Technik
5.4. IMPLEMENTATION VON AGGREGATIONEN: VEKTOREN UND<br />
HASH-TABELLEN 68<br />
Mittels einer Hashfunktion h wird eine eindeutige (nicht eineindeutige!) Abbildung einer<br />
Menge von Schlüsseln auf die Menge der Indizees einer Hashtabelle definiert (siehe Abbildung<br />
5.4.1).<br />
Tabelle<br />
6<br />
1<br />
Universum der SchlüsselU<br />
5<br />
Benutzte SchlüsselK<br />
2<br />
nil<br />
nil<br />
nil<br />
1<br />
2<br />
3<br />
4<br />
5<br />
3<br />
4<br />
data<br />
data<br />
4<br />
3 8<br />
7<br />
9<br />
nil<br />
nil<br />
6<br />
7<br />
8<br />
9<br />
7<br />
9<br />
data<br />
data<br />
Abbildung 5.4.1: Hashtabelle mit indirekter Adressierung<br />
Beispiel: Divisionsrest-Methode<br />
h(k) = n(k) MOD m<br />
Darin sind m die Anzahl der Tabellenelemente und k ein (meist alphanumerischer)<br />
Schlüssel. MOD bezeichnet die Modulo-Operation. Die Funktion n bildet die<br />
Menge der Schlüssel auf die Menge der natürlichen Zahlen ab.<br />
Verschiedene k ∈ K können ein gleiches h(k) besitzen. Durch h wird K in disjunkte<br />
Klassen zerlegt (d.h. ”<br />
zerhackt” = hash).<br />
Beispiel:<br />
n(k) = 10 · k, K = { 1, 2, . . . , 10}, m = 13 (Empfehlung: m ist Primzahl)<br />
h(1) = 10, h(2) = 7, h(3) = 4, h(4) = 1, h(5) = 11, h(6) = 8, h(7) = 5, h(8) =<br />
2, h(9) = 12, h(10) = 9<br />
Ein Schlüssel k wird gemeinsam mit der Nutzinformation an der Tabellenposition T[h(k)]<br />
abgespeichert. Eine Kollision tritt auf, wenn<br />
h(k 1 ) = h(k 2 ) für k 1 , k 2 ∈K und k 1 ≠ k 2 .<br />
Strategien zur Kollisionsauflösung<br />
Ein mögliches Verfahren zur Kollisionsauflösung ist die offene Hash–Technik. Im Konfliktfall<br />
wird durch sequentielles Suchen ein freier Tabellenplatz gesucht. Bei diesem Verfahren<br />
muss mindestens ein Restplatz in der Tabelle frei bleiben.<br />
Das Java Development Kit bietet Unterstützung für die Hashtabellen-Technik. Im Package<br />
java.util wird die Klasse Hashtable bereitgestellt. Es existieren u.a. folgende Methoden:
5.5. DYNAMISCHE MODELLE UND EVENT-HANDLING 69<br />
• public synchronized Object get (Object key)<br />
liefert das dem Schlüssel-Objekt key in der Hash-Tabelle zugeordnete Datenobjekt<br />
zurück, falls der Schlüssel key in der Tabelle gefunden wird, und sonst null.<br />
• public synchronized Object put (Object key, Object value)<br />
trägt das Schlüssel-Objekt key und das Datenobjekt value in die Tabelle ein, falls der<br />
Schlüssel in der Tabelle noch nicht existiert. In diesem Fall liefert put den Wert null.<br />
Falls der Schlüssel in der Tabelle vorhanden war, wird der zugeordnete Wert durch das<br />
Objekt value ersetzt. In diesem Fall liefert put den alten Wert.<br />
Der Platz in der Hashtabelle wird mit Hilfe der Methode hashCode() ermittelt.<br />
• public synchronized Object remove (Object key)<br />
entfernt das Schlüssel-Objekt key und das zugehörige Datenobjekt aus der Tabelle, falls<br />
key in der Tabelle gefunden wird. In diesem Fall wird das zugeordnete Datenobjekt<br />
zurückgegeben. Wird der Schlüssel nicht gefunden, ist der Rückgabewert null.<br />
• public synchronized Enumeration elements()<br />
liefert eine Instanz der Klasse Enumeration, die eine Zusammenstellung aller Datenobjekte<br />
enthält.<br />
Falls durch fortgesetztes Eintragen in die Hashtabelle (mittels put) die Reorganisationsschwelle<br />
überschritten wird, erfolgt automatisch ein Aufruf der geschützten Methode rehash().<br />
Dabei wird die Hash-Tabelle automatisch vergrößert.<br />
Alle Klassen, deren Instanzobjekte als Schlüssel dienen sollen, müssen die Methoden<br />
hashCode() und equals(...) implementieren.<br />
Die Standardklassen in Java implementieren diese Methoden bereits, d.h. alle von Object<br />
abgeleiteten Klassen. Die Methoden müssen also vom Nutzer nur dann neu definiert werden,<br />
wenn eine spezielle Berechnungsvorschrift gewünscht wird bzw. wenn für Schlüssel ein Objekttyp<br />
verwendet wird, der von keinem Standard-Java-Objekttyp abgeleitet ist.<br />
5.5 Dynamische Modelle und Event-Handling<br />
Ein Problem ist die Umsetzung von Zustandsdiagrammen in Java-Programme.<br />
Bei jeder Mausbetätigung oder Tastatureingabe wird ein Objekt der Klasse Event bzw.<br />
einer Subklasse dieser Klasse erzeugt. Den Programmkomponenten, die auf Events reagieren<br />
sollen, werden zur jeweiligen Event-Art passende Listener hinzugefügt. Die Programmierung<br />
der gewünschten Aktionen bei Eintreten bestimmter Ereignisse erfolgt in sog. Handlern.<br />
Listener-Interfaces definieren solche Handler zur Behandlung von Events. Ein Listener kann<br />
mehrere Event-Quellen haben. Zu einer Event-Quelle kann es andererseits auch mehrere<br />
Listener geben.<br />
Die nachfolgenden Aussagen beziehen sich zunächst auf das Event-Handling im JDK<br />
1.0.x.
5.5. DYNAMISCHE MODELLE UND EVENT-HANDLING 70<br />
Alle Komponenten des Abstract Window Toolkit (AWT) besitzen eine Methode handleEvent(Event)<br />
als ”<br />
Handler”.<br />
handleEvent(Event) ruft in Abhängigkeit von der Art des Events unterschiedliche Hilfsmethoden<br />
auf. Die Information über die Art eines Events ist im Event-Objekt in der Komponente<br />
(der Instanzvariablen) id enthalten. Alle Handler haben den Ergebnistyp boolean.<br />
Zunächst wird bei dem Objekt, in dem ein Event erzeugt wurde, begonnen, nach einem<br />
Handler zu suchen.<br />
Beispiel: Applet zur Ausgabe des Labels eines gedrückten Buttons auf der Standard-Ausgabe.<br />
Die Ausgabe erscheint auf der Java-Console.<br />
import java.applet.*;<br />
import java.awt.*;<br />
public class AcEvDemo extends Applet{<br />
public void init() {<br />
System.out.println("Hello world!");<br />
add(new Button("open"));<br />
add(new Button("close"));<br />
System.out.println("I am waiting for events.");<br />
}<br />
public boolean action(Event evt,Object what){<br />
if ("open".equals(what)){<br />
System.out.println("action in open");<br />
return true;<br />
}<br />
if ("close".equals(what)){<br />
System.out.println("action in close");<br />
return true;<br />
}<br />
return false;<br />
}<br />
}<br />
Ab Java 1.1 hat sich das Event-Handling geändert (wobei Kompatibilität gewahrt wurde,<br />
d.h. Programme mit dem Event-Handling von Java 1.0.x lassen sich auch unter Java 1.1 und<br />
höheren Versionen übersetzen u. interpretieren). Es gibt verschiedene Klassen von Events.<br />
Alle Event-Klassen erben von EventObject. Ein Event ist ein unveränderliches Objekt, das<br />
Informationen über den Typ und die Event-Quelle enthält.<br />
Ein Event tritt bei einem Quellobjekt auf und wird an einen ”<br />
Hörer” (einen Listener)<br />
weitergeleitet. Den Komponenten, in denen Events ausgelöst werden können (die also die<br />
Event-Quelle bilden), werden ein oder mehrere zur jeweiligen Event-Klasse passende Listener<br />
hinzugefügt.
5.5. DYNAMISCHE MODELLE UND EVENT-HANDLING 71<br />
Listener sind Interfaces. Die Klasse, in der die Behandlung eines Events vorgenommen<br />
werden soll, muss das entsprechende Listener-Interface implementieren, oder von einer Klasse<br />
abgeleitet sein, die dieses Interface implementiert. Die Behandlung von Events innerhalb einer<br />
Klasse wird in Handlern programmiert. Ein Objekt der Klasse, die Handler bereitstellt, wird<br />
dann als Listener registriert.<br />
Beispiel:<br />
import java.applet.*;<br />
import java.awt.event.*;<br />
import java.awt.*;<br />
public class AcEvDemo2 extends Applet implements ActionListener{<br />
public void init() {<br />
System.out.println("Hello world!");<br />
Button bopen = new Button("open");<br />
Button bclose = new Button("close");<br />
bopen.addActionListener(this);<br />
bclose.addActionListener(this);<br />
add(bopen);<br />
add(bclose);<br />
System.out.println("I am waiting for events.");<br />
}<br />
public void actionPerformed(ActionEvent evt){<br />
if (evt.getActionCommand().equals("open")){<br />
System.out.println("action in open");<br />
return true;<br />
}<br />
if (evt.getActionCommand().equals("close")){<br />
System.out.println("action in close");<br />
}<br />
}<br />
}<br />
Die Anwendungsklasse implementiert das Interface ActionListener und definiert die<br />
Methode ActionPerformed(), der ein Action-Event als Parameter übergeben wird. Den<br />
Buttons bopen und bclose wird mit der Methode addActionListener ein Listener hinzugefügt.<br />
Beispiel:<br />
Applet zur Demonstration von Maus-Events:<br />
import java.applet.*;<br />
import java.awt.event.*;<br />
import java.awt.*;
5.5. DYNAMISCHE MODELLE UND EVENT-HANDLING 72<br />
public class EventDemoNeu extends Applet implements ActionListener {<br />
public void init() {<br />
System.out.println("Hello world!");<br />
setLayout(new BorderLayout());<br />
setBackground((Color.green).darker());<br />
Button bopen = new Button("open");<br />
bopen.addActionListener(this);<br />
add("West",bopen);<br />
Button bclose = new Button("close");<br />
bclose.addActionListener(this);<br />
add("East",bclose);<br />
System.out.println("I am waiting for events.");<br />
Label myLabel = new Label();<br />
myLabel.setAlignment(Label.CENTER);<br />
add("South",myLabel);<br />
GraphicCanvas grcanv = new GraphicCanvas(myLabel);<br />
grcanv.addMouseListener(grcanv);<br />
Panel p1 = new Panel();<br />
p1.add("Center",grcanv );<br />
add("Center",p1);<br />
}<br />
public void actionPerformed(ActionEvent evt){<br />
if (evt.getActionCommand().equals("open")){<br />
System.out.println("action in open");<br />
}<br />
if (evt.getActionCommand().equals("close")){<br />
System.out.println("action in close");<br />
}<br />
}<br />
}<br />
class GraphicCanvas extends Canvas implements MouseListener {<br />
Label positionLabel;<br />
Rectangle rect;<br />
public GraphicCanvas(Label positionLabel){<br />
this.positionLabel = positionLabel;<br />
setBackground(Color.green);<br />
rect = new Rectangle(80,40,60,70);<br />
}<br />
public void paint(Graphics g){<br />
g.setColor(Color.blue);
5.5. DYNAMISCHE MODELLE UND EVENT-HANDLING 73<br />
}<br />
g.fillRect(rect.x,rect.y,rect.width,rect.height);<br />
}<br />
public Dimension preferredSize(){<br />
return new Dimension(200,200);<br />
}<br />
public Dimension minimumSize(){<br />
return new Dimension(200,200);<br />
}<br />
public void mouseEntered(MouseEvent evt){<br />
positionLabel.setText("Mouse is in");<br />
}<br />
public void mouseExited(MouseEvent evt){<br />
positionLabel.setText("Mouse is out");<br />
}<br />
public void mouseClicked(MouseEvent evt){<br />
}<br />
public void mousePressed(MouseEvent evt){<br />
}<br />
public void mouseReleased(MouseEvent evt){<br />
}<br />
Abschließend noch ein Beispiel, das zeigt, wie eine über ein Frame dargestellte Anwendung<br />
durch das Schließen des Fensters beendet wird:<br />
import java.awt.event.*;<br />
import java.awt.*;<br />
public class ApplicationTest extends Frame implements WindowListener{<br />
public static void main(String args[]){<br />
ApplicationTest myFrame = new ApplicationTest("Neues Frame");<br />
myFrame.addWindowListener(myFrame);<br />
myFrame.resize(400,400); myFrame.show();<br />
}<br />
public ApplicationTest(String s){<br />
super(s);<br />
}<br />
public void windowClosing(WindowEvent evt){<br />
System.exit(0); dispose();<br />
}<br />
public void windowActivated(WindowEvent evt){<br />
}<br />
public void windowClosed(WindowEvent evt){
5.6. FEHLER UND AUSNAHMEN 74<br />
}<br />
}<br />
public void windowDeactivated(WindowEvent evt){<br />
}<br />
public void windowDeiconified(WindowEvent evt){<br />
}<br />
public void windowIconified(WindowEvent evt){<br />
}<br />
public void windowOpened(WindowEvent evt){ }<br />
5.6 Fehler und Ausnahmen<br />
Im Sprachgebrauch des <strong>Software</strong> <strong>Engineering</strong> wird zwischen Fehlern und Ausnahmen unterschieden.<br />
Ein wesentlicher Beitrag zur Kärung der Begriffe und zur Sicherheitsproblematik<br />
ist von B. Meyer mit dem Vertragskonzept geleistet worden [Mey90].<br />
Als Fehler bezeichnet man danach eine Nicht-Übereinstimmung eines Programmes mit<br />
seiner Spezifikation.<br />
Ausnahmen (exceptions) bedeuten Ausnahmesituationen, d.h. abnormale Bedingungen<br />
während der Ausführung von <strong>Software</strong>-Elementen, die normalerweise den Abbruch eines<br />
Programmes zur Folge haben.<br />
Fehler können, müssen aber nicht Ausnahmen zur Folge haben.<br />
Das Exception-Konzept in Java ermöglicht es, bestimmte Arten von Ausnahmen zu behandeln,<br />
so dass Programme in jedem Fall sicher weiterlaufen.<br />
Die Bedingungen, unter denen in Java eine Exception ausgelöst wird, ist entweder im<br />
Interpreter verankert, oder sie wird vom Programmierer definiert.<br />
Neben Exceptions kennt Java noch Ausnahmen, die ”<br />
schwere Fehler” darstellen, und die<br />
in jedem Fall zur Beendigung des Interpreters führen. Diese Art von Ausnahmen heisst in<br />
Java Error.<br />
Alle Errors und Exceptions sind Objekte, die von der Klasse Throwable abstammen.<br />
Ein Programmierer kann eigene Exceptions definieren, indem er eine Unterklasse von einer<br />
vorhandenen Exception ableitet.<br />
Konstruktoren für Exceptions können optional mit einem Parameter aufgerufen werden.<br />
public class NullPointerException extends RuntimeException{<br />
public NullPointerException(){<br />
super();<br />
}<br />
public NullPointerException(String s){<br />
super(s);<br />
}<br />
}
5.6. FEHLER UND AUSNAHMEN 75<br />
Throwable<br />
Error<br />
Exception<br />
IndexOutOfBoundsException<br />
. . .<br />
RuntimeException<br />
ArrayIndexOutOfBoundsException<br />
NullPointerException<br />
Abbildung 5.6.1: Errors und Exceptions<br />
Der Parameter s vom Typ String repräsentiert eine Fehlernachricht.<br />
Auftrefende Exceptions können durch eine try/catch-Kombination abgefangen werden.<br />
Ist das nicht der Fall, erfolgt eine Übergabe an den nächsten Programmblock bzw. eine übergeordnete<br />
Methode. Außer bei RuntimeExceptions ist für Methoden eine throws-Klausel<br />
erforderlich, sonst meldet bereits der Compiler einen Fehler.<br />
Der Interpreter unterbricht letztlich das Programm, falls nirgends eine Behandlung einer<br />
Exception erfolgt.<br />
Behandlung von Exceptions<br />
1. Kombinierte Anwendung der Anweisungen try, catch und finally<br />
try {<br />
// Programmtext, der eine Exception ausloesen kann<br />
}<br />
.<br />
catch (FirstException e){<br />
// Programmtext, der beim Auftreten einer Exception e<br />
// vom Typ FirstException ausgefuehrt wird<br />
}<br />
catch (SecondException e){<br />
// Programmtext, der beim Auftreten einer Exception e<br />
// vom Typ SecondException ausgefuehrt wird<br />
}<br />
.
5.6. FEHLER UND AUSNAHMEN 76<br />
finally {<br />
// Programmtext, der grundsaetzlich ausgefuehrt wird<br />
}<br />
Falls bei der Ausführung der Anweisungen des try-Blockes eine Exception ausgelöst<br />
wird, wird die Verarbeitung sofort abgebrochen und zu den catch-Anweisungen verzweigt.<br />
Es gibt keine Möglichkeit mehr, in den try-Block zurückzuspringen. Die catch-<br />
Anweisungen sind optional. Falls zu einer Exception eine passende catch-Anweisung<br />
existiert, wird dieser die Exception übergeben und es werden die in der catch-Anweisung<br />
enthaltenen Anweisungen ausgeführt. Falls eine Exception auftritt, zu der keine catch-<br />
Anweisung existiert, wird die Exception an den nächst höheren Programmblock weitergeleitet.<br />
Beispiel:<br />
import java.io.*;<br />
import java.awt.*;<br />
import java.lang.*;<br />
public class ExceptionDemo {<br />
public static void main(String args[]){<br />
DataInputStream dataInput = new DataInputStream(System.in);<br />
int a,b,c;<br />
try{<br />
b = Integer.valueOf(dataInput.readLine().trim()).intValue();<br />
c = Integer.valueOf(dataInput.readLine().trim()).intValue();<br />
a = b/c;<br />
System.out.println(a);<br />
}<br />
catch(ArithmeticException e){<br />
System.out.println("Error: "+e.getMessage());<br />
}<br />
catch(IOException e){<br />
System.out.println("Error: "+e.getMessage());<br />
}<br />
}<br />
}<br />
Im Beispiel entfernt die Methode trim() im eingelesenen String vorstehende und nachfolgende<br />
Leerzeichen. Erhält c den Wert 0, wird bei der Division eine ArithmeticException<br />
ausgelöst. Die Methode getMessage() wird von Throwable definiert und ist damit bei<br />
allen Exception-Objekten verfügbar.
5.7. PARALLELITÄT DURCH MULTITHREADING 77<br />
2. Die Verwendung der throws-Klausel im Kopf einer Methode kennzeichnet den Verzicht<br />
auf eine lokale Fehlerbetrachtung, z.B.<br />
... main(...) throws IOException ...<br />
Es können - durch Komma getrennt - auch mehrere Exceptions angegeben werden.<br />
Das explizite Auslösen einer Exception signalisiert einen Fehlerzustand und erfolgt mittels<br />
throw.<br />
Im Programm folgt auf das Schlüsselwort throw eine Referenz auf eine Instanz der Art<br />
von Exception, die ausgelöst werden soll, z.B.<br />
throw new NullPointerException("Cdr einer leeren Liste").<br />
Benutzerdefinierte Exceptions Vom Programmierer können eigene Exception-Klassen<br />
definiert werden, indem sie von einer existierenden Exception-Klasse abgeleitet werden.<br />
5.7 Parallelität durch Multithreading<br />
Multitasking und Multiprocessing beziehen sich auf Eigenschaften eines Betriebssystems<br />
oder der Hardware. Auf einem Multitasking-System können mehrere Programme quasiparallel<br />
ablaufen. Soll ein Programm in diesem Kontext Teile parallel abarbeiten, so kann<br />
das Programm Prozesse erzeugen, die jeweils in einer eigenen Umgebung mit einem eigenen<br />
Speicherbereich arbeiten und über die üblichen Interprozesskommunikationsprimitiven<br />
miteinander Kommunizieren (child-Prozesse).<br />
Eine Programmiersprache mit der Multithreading-Eigenschaft bietet die Möglichkeit, auf<br />
einfache und ressourcenschonende Weise Teile eines Programmes innerhalb eines einzigen<br />
Prozesses parallel ablaufen zu lassen.<br />
Ein Thread ist eine sequentielle Folge von Anweisungen die parallel zu den Anweisungsfolgen<br />
anderer Threads abgearbeitet wird.<br />
Um einen Thread zu erzeugen, kann man eine Subklasse der Klasse Thread bilden, in<br />
dieser die Methode run() überschreiben und dann ein Objekt der Subklasse instanziieren.<br />
Die Methode start() muss auf die neue Instanz angewendet werden und ruft dann run()<br />
auf.<br />
Beispiel:<br />
Die Methode run() der Klasse DemoThread gibt aller 5 Sekunden einen Text aus.<br />
public class DemoThread extends Thread {<br />
public void run() {<br />
for (int i=0; i
5.7. PARALLELITÄT DURCH MULTITHREADING 78<br />
}<br />
}<br />
}<br />
System.out.println("Demo-Thread");<br />
Mit sleep(5000) wird die Ausführung der Methode 5000 ms angehalten. Java erlaubt<br />
die Unterbrechung der Methode sleep() durch einen anderen Thread. Wird der Thread<br />
während der Ausführung von sleep() unterbrochen, wird ihm die Unterbrechung durch<br />
Auslösen einer InterruptedException signalisiert.<br />
In der main-Methode der Klasse UseDemoThread wird nun ein Thread erzeugt und gestartet:<br />
public class UseDemoThread {<br />
public static void main(String argv[]){<br />
System.out.println(Thread.MAX_PRIORITY);<br />
System.out.println(Thread.MIN_PRIORITY);<br />
System.out.println(Thread.NORM_PRIORITY);<br />
DemoThread thread1 = new DemoThread();<br />
Thread.currentThread().setPriority(4);<br />
thread1.start();<br />
System.out.println("--- 1 ---");<br />
System.out.println(Thread.currentThread().getPriority());<br />
thread1.suspend();<br />
try {<br />
Thread.currentThread().sleep(5000);<br />
}<br />
catch(InterruptedException e){<br />
};<br />
System.out.println("--- 2 ---");<br />
thread1.resume();<br />
System.out.println("--- 3 ---");<br />
}<br />
}<br />
/* Test:<br />
~>java UseDemoThread<br />
10<br />
1<br />
5<br />
5<br />
--- 1 ---<br />
4<br />
--- 2 ---<br />
DemoThread
5.7. PARALLELITÄT DURCH MULTITHREADING 79<br />
--- 3 ---<br />
DemoThread<br />
DemoThread<br />
~><br />
*/<br />
Threads können eine Priorität zwischen MIN_PRIORITY und MAX_PRIORITY erhalten.
Kapitel 6<br />
Entwurf und Implementation<br />
6.1 Modellmanagement und Systementwurf<br />
In diesem Abschnitt werden zunächst UML-Konzepte vorgestellt, die allgemein auf Modelle<br />
anwendbar sind. Modellelemente können in Modelle, Pakete und Subsysteme organisiert sein.<br />
Es kann ein Metamodell des Modell-Managements angegeben werden.<br />
6.1.1 Packages<br />
Ein Paket (engl. package) gruppiert eine Menge von Modellelementen. Ein Paketdiagramm<br />
bietet die Möglichkeit, die Struktur von Systemen logisch zu gliedern und Sichten in verschiedenen<br />
Abstraktionsebenen zu beschreiben. Dadurch wird es z.B. möglich, in einem großen<br />
System - z.B. von über 100 Klassen - den Überblick zu behalten.<br />
In einem Paket können paketierbare Elemente gruppiert werden. Classifier - d.h. Instanzen<br />
von Classifier - sind paketierbare Elemente. Classifier ist eine Metaklasse, sie beschreibt<br />
Notationselemente mit bestimmten gleichen Eigenschaften. Classifier können<br />
• Features haben,<br />
• abstrakt sein,<br />
• in Generalisierungs-/Spezialisierungsbeziehungen zueinander stehen,<br />
• als Typ interpretiert werden.<br />
Die Klasse ist der wichtigste Classifier in der UML. Klassen unterscheiden sich von anderen<br />
Classifiern z.B. durch das Vorhandensein von Operationen (Methoden).<br />
Pakete können hierarchisch geschachtelt werden. Ein Paket kann sowohl untergeordnete<br />
Pakete als auch gewöhnliche Modellelemente enthalten. Alle Arten von UML - Modellelementen<br />
und Diagrammen können in Paketen organisiert sein.<br />
Der Paket-Import ist eine gerichtete Beziehung zwischen zwei Paketen. Ein Quellpaket<br />
möchte Zugriff auf die Elemente eines anderen (Ziel-)Pakets haben. Der Zugriff auf die Elemente<br />
aus dem Zielpaket erfolgt ”<br />
unqualifiziert“.<br />
80
6.1. MODELLMANAGEMENT UND SYSTEMENTWURF 81<br />
Der Element-Import ist eine gerichtete Beziehung zwischen einem Paket und einem paketierbaren<br />
Element. Der Element-Import erlaubt es, in einem Paket mittels eines nicht<br />
qualifizierten Namens auf ein Element in einem anderen Paket zuzugreifen.<br />
Ein Paket-Merge definiert, wie der Inhalt eines Pakets durch den Inhalt eines anderen<br />
Pakets erweitert wird. Es werden neue, spezialisierte Classifier angelegt. Die ”<br />
gemergten“<br />
Classifier werden direkt im Paket angelegt und können dort verändert werden. Es entsteht<br />
eine Spezialisierungshierarchie.<br />
Notation<br />
Ein Paket wird als großes Rechteck mit einem kleinen Rechteck an einer Ecke (meist links<br />
oben) dargestellt. Damit wird ein Aktenordner symbolisiert.<br />
Der Inhalt des Pakets kann im großen Rechteck gezeigt werden. Wenn der Inhalt des<br />
Paketes nicht gezeigt wird, wird der Name in das große Rechteck geschrieben. Wenn der<br />
Inhalt des Paketes gezeigt wird, wird der Name in das kleine Rechteck geschrieben. Über<br />
dem Package-Namen kann ein Schlüsselwort notiert werden.<br />
Die Sichtbarkeit von Paketelementen nach außen kann durch Angabe von + oder - vor<br />
dem Elementnamen gekennzeichnet werden.<br />
Ein Package-Import/Element-Import wird durch einen gestrichelten Pfeil dargestellt. Das<br />
Schlüsselwort import am Pfeil beschreibt einen öffentlichen Import.<br />
Namensraum<br />
Durch ein UML-Paket wird ein Namensraum definiert, dem alle Elemente des Pakets angehören.<br />
Packages in Topcased<br />
In Topcased können Packages in Klassendiagrammen oder in Komponentendiagrammen verwendet<br />
werden. Klassen können ebenso wie Komponenten in Paketen gruppiert werden. Das<br />
nachfolgende Beispiel demonstriert die Verwendung von Paketen in Klassendiagrammen.<br />
Beispiel: Im Anwendungsbeispiel wird eine Paket-Struktur im Topcased-Projekt Vorlesungsbeispiele2<br />
unter Models neu erzeugt. Als Diagramm-Typ für ein neues UML-Modell<br />
wir Class Diagram gewählt. Danach kann die Paket-Struktur erarbeitet werden, wie in Abb.<br />
6.1.1 gezeigt.<br />
Die Klasse UseP1 soll die Klassen C1, C3 und C5 nutzen. Die Klasse C2 kann von UseP1<br />
nicht direkt genutzt werden, sie soll nur Paket-weit sichtbar sein und ist außerhalb des Pakets<br />
p2 nicht sichtbar.<br />
Am Beispiel soll gezeigt werden, wie<br />
1. die Package-Struktur bei der Code-Generierung in eine Verzeichnisstruktur abgebildet<br />
wird,<br />
2. im Build-Pozess eine ausführbare Anwendung automatisch erzeugt wird (in einem bin-<br />
Verzeichnis),
6.1. MODELLMANAGEMENT UND SYSTEMENTWURF 82<br />
Abbildung 6.1.1: Entwicklung einer Paket-Struktur in Topcased<br />
3. die Anwendung unmittelbar ausgeführt wird.<br />
Das entwickelte UML-Modell (vgl. Abb. 6.1.1) wird - wie in der Navigator-Darstellung gezeigt<br />
- gespeichert (vgl. Abb. 6.1.2).<br />
Für die Code-Generierung sind zunächst die Eigenschaften festzulegen. Dies erfolgt über<br />
das Kontextmenü des Modells Packages.uml.<br />
Der Quellcode wird in ein Projekt Vorlesungsbsp mit der Java-Perspektive hinein generiert.<br />
Die Erzeugung des Java-Quellcodes wird über das Kontextmenü des Pakets (vgl. Abb.<br />
6.1.4) initiiert.<br />
Es ergibt sich die in Abb. 6.1.5 gezeigte Stuktur.<br />
Jetzt müssen die Quellcode-Dateien per Hand angepasst werden. Folgende Aktivitäten<br />
sind notwendig:<br />
• Methodenkörper in allen Methoden ergänzen,<br />
• evtl. package-Anweisungen korrigieren (im Normalfall sind die package-Anweisungen<br />
richtig erzeugt),
6.1. MODELLMANAGEMENT UND SYSTEMENTWURF 83<br />
Abbildung 6.1.2: Navigatordarstellung<br />
• import-Anweisungen ergänzen (bei Modellierung von import-Beziehungen werden import-<br />
Anweisungen generiert).<br />
Im nächsten Schritt wird der Bild-Prozess vorbereitet (vgl. Abb. 6.1.6 und Abb. 6.1.7).<br />
Nachfolgend wird die Konfiguration eingestellt. Die Main class wird per Hand eingestellt<br />
(vgl. Abb. 6.1.8). Die Konfiguration wird als PackageBeispiel gespeichert.<br />
Die Ausführung ist mit einem Build-Prozess verbunden. Über den Menüpunkt Run As<br />
... ist noch die gewünschte Anwendung auszuwählen (vgl. Abb. 6.1.9).<br />
Abb. 6.1.10 zeigt das Protokoll der Ausführung.<br />
Einem Package können auch in Komponenten-Modellen existierende Klassen zugeordnet<br />
werden. Dies erfolgt über die outline-Darstellung (vgl. Abb. 6.1.11). Importierte Klassen<br />
müssen noch per drag and drop der Diagrammdarstellung zugeordnet werden.<br />
Packages in Java<br />
Das Konzept der Pakete der UML entspricht genau dem package-Konzept in Java. Der Name<br />
einer Klasse ist innerhalb des Paketes sichtbar (scope), in dem die Klasse deklariert ist. Der<br />
Name einer Klasse muss sich von allen anderen Klassennamen des Pakets unterscheiden. In<br />
Java umfasst ein Paket eine beliebige Anzahl von Compilationseinheiten.<br />
Die Übersetzungseinheit C1.java enthält zwei Klassen.<br />
Die package-Anweisung muss die erste Anweisung in einer Quelldatei sein. Fehlt die<br />
package-Anweisung, werden die Klassen einem voreingestellten Standardpaket ohne Namen<br />
zugeordnet. Die Namen dieser Klassen können von allen Klassen verwendet werden, die sich<br />
im gleichen Verzeichnis befinden. Pakete können hierarchisch gegliedert sein. Paketnamen<br />
korrespondieren mit Pfad-/Dateinamen. Punkte der package-Anweisung werden dabei durch<br />
File-Separatoren ersetzt.
6.1. MODELLMANAGEMENT UND SYSTEMENTWURF 84<br />
Abbildung 6.1.3: Eigenschaften des Pakets für die Code-Geenerierung festlegen<br />
Die Übersetzung und Ausführung von Java-Anwendungen, die in Packages organisiert<br />
sind, kann jeweils durch einen Aufruf erfolgen. Abhängige Übersetzungseinheiten werden<br />
automatisch mit übersetzt.
6.1. MODELLMANAGEMENT UND SYSTEMENTWURF 85<br />
Abbildung 6.1.4: Starten der Code-Generierung<br />
Abbildung 6.1.5: Bei der Codegenerierung erzeugte Struktur
6.1. MODELLMANAGEMENT UND SYSTEMENTWURF 86<br />
Abbildung 6.1.6: Einstellungen des Java Build Path - Ort der Quelldateien<br />
Abbildung 6.1.7: Bereitstellung der Bibliotheken<br />
Abbildung 6.1.8: Einstellung der Run Configuration
6.1. MODELLMANAGEMENT UND SYSTEMENTWURF 87<br />
Abbildung 6.1.9: Auswahl der auszuführenden Applikation<br />
Abbildung 6.1.10: Ausgaben der Applikation auf der Standardausgabe<br />
Abbildung 6.1.11: Importieren von Klassen in Packages
6.1. MODELLMANAGEMENT UND SYSTEMENTWURF 88<br />
P1<br />
P2<br />
C1.java<br />
C3.java<br />
P3<br />
C5.java<br />
Abbildung 6.1.12: Verzeichnisstruktur als Package-Struktur<br />
Abbildung 6.1.13: Benutzung der Packages
6.1. MODELLMANAGEMENT UND SYSTEMENTWURF 89<br />
C1.java<br />
package P1.P2;<br />
public class C1 {<br />
...<br />
}<br />
class C2 extends C1 {<br />
...<br />
}<br />
C3.java<br />
package P1.P2;<br />
class C3 extends C1 {<br />
...<br />
}<br />
class C4 {<br />
...<br />
}<br />
C5.java<br />
package P1.P3;<br />
public class C5 extends C1 {<br />
...<br />
}<br />
class C6 {<br />
...<br />
}<br />
Abbildung 6.1.14: package-Anweisungen zur Realisierung der Package-Struktur<br />
Abbildung 6.1.15: Beispiel, Übersetzen mit ”<br />
Gesprächigkeit”
6.1. MODELLMANAGEMENT UND SYSTEMENTWURF 90<br />
Abbildung 6.1.16: Beispiel, Ausführung
6.2. KOMPONENTENDIAGRAMME 91<br />
6.2 Komponentendiagramme<br />
Betrachtet man die logische Struktur eines objektbasierten Systems, so sieht man Klassenstrukturen.<br />
Bei der Systemarchitektur wird dagegen häufig eine nicht objekt-basierte Komponentenstruktur<br />
zugrunde gelegt.<br />
Zur Darstellung der Komponentenstruktur werden Komponentenmodelle verwendet. Komponentenmodelle<br />
werden in der UML mit Komponentendiagrammen repräsentiert. Komponentendiagramme<br />
beschreiben die Struktur eines Systems zur Laufzeit.<br />
Eine Komponente stellt eine physikalische Programmeinheit dar, die als Quellcode, Objektcode<br />
oder ausführba-res Programm vorliegen kann. Komponenten werden in Subsystemen<br />
(Paketen) organisiert.<br />
Abbildung 6.2.1: Komponentendiagramm<br />
Abbildung 6.2.2: Komponenten mit Schnittstellen<br />
Diagrammelemente:<br />
• in Klassen- und Objektdiagrammen vewendete Modellelemente<br />
• Sichten auf Sachverhalte, die in anderen Modellen beschrieben sind
6.2. KOMPONENTENDIAGRAMME 92<br />
• eigene Modellelemente:<br />
– Komponente<br />
– Schnittstelle (Klassendiagramm)<br />
– Realisierungs-/Implementierungs-/Verwendungs-Beziehung<br />
– Artefakt<br />
– Port
6.3. VERTEILUNGSDIAGRAMME 93<br />
6.3 Verteilungsdiagramme<br />
Das Verteilungsdiagramm (deployment diagram) ermöglicht die Modellierung expliziter physischer<br />
Strukturen auf Instanzenebene. Es zeigt die Zuordnung von Artefakten (<strong>Software</strong>komponenten)<br />
zu Hardware-Einheiten (als Knoten bezeichnet), d.h. es enthält Knoten (node),<br />
auf denen Prozesse (im Sinne von Betriebssystemprozessen) ablaufen.<br />
Ein Knoten (Node) ist ein zur Laufzeit physisch vorhandenes Objekt, auf das Artefakte<br />
für die Ausführung verteilt (deployes) werden können. Knoten sind (strukturierte) Classifier<br />
und lassen sich schachteln. Knoten können sowohl auf der Ebene M1 (Typebene) als auch auf<br />
der Ebene M0 (Instanzen-Ebene) definiert werden. Gerät und Ausführungsumgebung sind<br />
in der UML definierte Spezialisierungen von Knoten.<br />
Ein Gerät (Device) ist eine Ressource, die die Fähigkeit besitzt, Berechnungen auszuführen.<br />
Ein Gerät besitzt Rechenkapazität. Geräte können komplex sein, d.h. sie können aus<br />
anderen Geräten bestehen.<br />
Eine Ausführungsumgebung (Execution Environment) ist ein Knoten, der eine Ausführungsumgebung<br />
für spezielle Typen von Komponenten bereitstellt. Ausführungsumgebungen<br />
können geschachtelt werden. Zum Beispiel kann die Ausführungsumgebung eines<br />
Datenbanksystems in die Ausführungsumgebung eines Betriebssystems eingebettet sein.<br />
Abbildung 6.3.1: Elemente eines Deploymentdiagramms
6.3. VERTEILUNGSDIAGRAMME 94<br />
Notation<br />
Knoten werden (dreidimensional) durch Quader dargestellt (vgl. Abb. 6.3.1). Falls es sich<br />
um Knoteninstanzen handelt, wird der Name unterstrichen dargestellt. Knoten können durch<br />
Kanten (connection) verbunden werden. Kanten repräsentieren Kommunikationspfade (communication<br />
path).<br />
Geräte werden als Knoten dargestellt und mit dem Schlüsselwort ≪device≫ gekennzeichnet.<br />
Zur Darstellung von Geräten können in der UML auch frei definierte Symbole<br />
verwendet werden (z.B. Workstation, Lautsprecher).<br />
In einer Ausführungsumgebung können bestimmte <strong>Software</strong>komponenten eines Systems<br />
ausgeführt werden. Voraussetzung ist, dass die Komponenten als ablauffähige Artefakte vorliegen.<br />
Ausführungsumgebungen werden als Knoten dargestellt und mit dem Schlüsselwort<br />
≪ExecutionEnvironment≫ gekennzeichnet.<br />
Verteilungsaspekte können programmtechnisch mit RMI (Remote Method Invocation)<br />
oder CORBA umgesetzt werden.
6.4. OBJEKTSERIALISIERUNG 95<br />
6.4 Objektserialisierung<br />
Die Objektserialisierung dient der persistenten, d.h. dauerhaften Speicherung von Laufzeitobjekten<br />
einer Java-Applikation zu einem bestimmten Zeitpunkt. Durch eine Deserialisierung<br />
kann die serialisierte Objektwelt zu einem späteren Zeitpunkt wieder hergestellt werden, wobei<br />
alle Objekte den Zustand annehmen, den sie zum Zeitpunkt der Serialisierung hatten.<br />
Grundgedanke für die Realisierung der Serialisierung ist, dass Klassen eines Anwendungsmodells<br />
selbst kein Wissen über externe Formate und Schnittstellen für Speicherung und<br />
Transport haben sollen.<br />
Um Objekte serialisieren zu können, muss die zugehörige Klasse das Interface Serializable<br />
implementieren oder von einer Klasse abgeleitet sein, die dieses Interface implementiert. Das<br />
Interface definiert keine Methoden, es dient nur zur Markierung der bei der Serialisierung zu<br />
berücksichtigenden Klassen.<br />
Die Serialisierung erfolgt, indem zunächst eine Instanz von ObjectOutputStream (Klasse<br />
im Package java.io) erzeugt wird. Auf dieses Objekt wird die Methode writeObject(Object)<br />
angewendet. writeObject(Object) bekommt als Parameter ein Objekt übergeben, von dem<br />
aus beginnend die Serialisierung rekursiv erfolgen soll.<br />
Beim Serialisieren werden die Werte aller Instanzvariablen eines Objekts in den spezifizierten<br />
ObjectOutputStream geschrieben. Das Schreiben erfolgt rekursiv, d.h. für Werte, die<br />
Objekte darstellen, werden auch deren Instanzvariablen geschrieben usw. Objekte werden<br />
allerdings nur einmal geschrieben, es gibt keine Endlosrekursionen. Die Werte von Instanzvariablen,<br />
die als transient deklariert sind, werden nicht ausgelagert.<br />
Die Deserialisierung erfolgt, indem zunächst eine Instanz von ObjectInputStream (Klasse<br />
im Package java.io erzeugt wird. Auf dieses Objekt wird die Methode readObject()<br />
angewendet. Die von dieser Methode gelieferte Instanz vom Typ Object ist noch an den<br />
gewünschten Typ anzupassen.<br />
6.5 Modell-/Diagrammaustausch<br />
Für den Austausch von UML2-Diagrammen bzw. Modellen zwischen unterschiedlichen UML-<br />
Werkzeugen kann das von der OMG entwickelte Austauschformat XMI (XML Metadata<br />
Interchanche) genutzt werden. Bisher wird XMI hauptsächlich für den UML- Austausch<br />
eingesetzt, d.h. UML-Modelle werden in XMI (und damit auch in XML) repräsentiert. Der<br />
Datenaustausch von Objekten basiert auf der MOF (Meta Object Facility).
Kapitel 7<br />
Konfigurations-, Test- und<br />
Dokumentationsmanagement<br />
Das Konfigurationsmanagement hat das Ziel, die im Prozess der <strong>Software</strong>entwicklung als Ergebnisse<br />
entstehenden Artefakte sicher zu verwalten, verteilt arbeitenden Entwicklern Zugriff<br />
darauf zu gewähren und die Kontrolle über die im Laufe der Zeit anfallenden Änderungen an<br />
Artefakten zu behalten. Der Fokus liegt dabei auf den Elementen des Produktes und nicht<br />
auf Artefakten, die den Entwicklungsprozess beschreiben (z.B. Projektpläne).<br />
Als Grundlage für dieses Kapitel dient u.a. das Buch ”<br />
Konfigurationsmanagement - mit<br />
Subversion, Ant und Maven” von Popp [Pop08].<br />
7.1 Der KM-Prozess<br />
Konfigurationsmanagement ist ein Regelwerk (bzw. eine Sammlung von Richtlinien), mit dem<br />
ein Prozess - der sog. KM-Prozess - beschrieben wird. Es regelt und optimiert insbesondere<br />
die Zusammenarbeit im Team.<br />
Das Konfigurationsmanagement umfasst Aufgaben wie:<br />
• die Kontrolle der Quellprogramme<br />
• Bereitstellung von ”<br />
Build”-Funktionalität<br />
• Release <strong>Engineering</strong><br />
Der erste Standard für das Konfigurationsmanagement (AFSCM-375-1) wurde 1963 vom<br />
US-Militär entwickelt und war auf die Entwicklung von Hardware bezogen. Popp erwähnt<br />
unter Bezug auf Literaturquellen, dass es inzwischen mehr als 200 KM-Standards gibt.<br />
Auswahl der Konfigurationselemente<br />
Eine zentrale Rolle in jedem KM-Prozess spielt die Verwaltung der Konfigurationselemente.<br />
Als Konfigurationselemente bezeichnen wir bestimmte Typen (Klassen) von Artefakten:<br />
96
7.2. VERSIONSVERWALTUNGSSYSTEME 97<br />
UseCases, Klassendiagramme, Quelltexte, Build-Skripte, Installationsanleitungen usw. Für<br />
die Verwaltung stellt das KM Konzepte und Werkzeuge zur Verfügung.<br />
Instanzen der Konfigurationselemente - das sind Verzeichnisse und Dateien - werden<br />
in einem Repository gespeichert. Das Repository ist eine zentrale (oder bei Mercurial: auch<br />
dezentrale) Ablage, auf die alle Teammitglieder zugreifen können. Repositorien sollten sowohl<br />
Text- als auch Binärdateien verwalten können.<br />
Werkzeuge, die Repositorien verwalten, setzen intern Versionierung ein, um die Nachvollziehbarkeit<br />
aller Änderungen und die Datenintegrität zu sichern. Das Konfikurationsmanagement<br />
ist dadurch eng verzahnt mit einer Versionskontrolle.<br />
Verwaltung der Konfigurationselemente<br />
Der Aufbau eines Repository entspricht prinzipiell der Projektstruktur, aber ohne Einbeziehung<br />
temporärer Ordner. Eine effektive Arbeit mit Werkzeugen ist nur möglich, wenn<br />
herstellerspezifische Formate im Repository verwaltet werden können. Repositorien werden<br />
deshalb oft über eine Schnittstelle direkt in die Werkzeuge integriert.<br />
Werkzeuge für die Arbeit mit Repositorien setzen intern die Versionierung ein: Nach jeder<br />
Änderung an einer Datei wird eine neue Revision im Repository gespeichert. Revisionen, die<br />
in der Zukunft parallel weiter gepflegt werden sollen, werden oft als Versionen bezeichnet.<br />
Nach und nach entsteht eine Revisions-/Versionshistorie, alle jemals vorgenommenen Änderungen<br />
werden dokumentiert (auch Löschen als Spezialfall einer Änderung). Dieser Prozess<br />
heisst Versionskontrolle - Revisionen einer Datei werden vom Repository durchnummeriert.<br />
7.2 Versionsverwaltungssysteme<br />
Versionsverwaltungssysteme stellen dem KM-Prozess ein Repository zur Verfügung.<br />
Das Concurrent Versions System (CVS) ist ein verbreitetes System, das das Versionsmanagement<br />
in Projekten auf der Ebene der Quellprogramme unterstützt (keine Deltas auf<br />
der Ebene der Binaries). Ein Schwerpunkt ist die Unterstützung der Gruppenarbeit. Jeder<br />
Entwickler arbeitet in seinem eigenen Arbeits-Verzeichnis. CVS mischt die Arbeiten<br />
und verwaltet ein zentrales Repository. Eine Schwäche von CVS ist, dass keine Verzeichnisse<br />
versioniert werden können (nur gewöhnliche Dateien). Außerdem unterstützt CVS keine<br />
Transaktionen. Beim Check-in mehrerer Dateien werden die Dateien einzeln übertragen. Bei<br />
Netzwerkproblemen kann es daher zu inkonsistenten Zuständen im Repository kommen.<br />
Subversion<br />
Subversion ist ein Nachfolgesystem zu CVS. Auch Subversion stellt ein Repository für einen<br />
KM-Prozess zur Verfügung. Das Repository verwaltet eine zentrale Kopie der Historie.<br />
Unter Windows kann TortoiseSVN als Subversion-Client genutzt werden ([Tor], Erweiterung<br />
zum Explorer von Windows).<br />
Um ein in der Arbeitskopie erstelltes neues Dokument dem Repository hinzuzufügen,<br />
sind zwei Schritte nötig (vgl. Abb. 7.2.1):
7.2. VERSIONSVERWALTUNGSSYSTEME 98<br />
1. TortoiseSVN → Add ...<br />
2. SVN Commit...<br />
Abbildung 7.2.1: SVN-Client TortoiseSVN im Windows-Explorer<br />
Subversion kann ebenso wie CVS in Eclipse als Client genutzt werden, dazu muss Subversion<br />
als Plug-in (subclipse) bereitgestellt werden.<br />
Beispiel 1. Es wird ein Java-Projekt im workspace angelegt. Mit Team share ... wird es<br />
dem Repository hinzugefügt.
7.2. VERSIONSVERWALTUNGSSYSTEME 99<br />
Mercurial<br />
Als ein neueres Versionsverwaltungssystem soll nachfolgend Mercurial behandelt werden.<br />
Für Mercurial steht ein Kommandozeilen-orientiertes Werkzeug zur Verfügung (alle Kommandos<br />
beginnen mit hg, vgl. http://mercurial.selenic.com/, Distributionen sind plattformspezifisch).<br />
Dieses System wird in einem online verfügbaren Buch von Bryan O’Sullivan<br />
beschrieben [O’S]. Eine integrierte Webschnittstelle steht ebenfalls zur Verfügung. Drittanbieter<br />
stellen grafische Frontends oder Plug-ins für Entwicklungsumgebungen zur Verfügung.<br />
In Eclipse wird Mercurial durch ein Plug-in unterstützt (zur Installation vgl. Abb. 1.3.9).<br />
Das Plug-in wird auf javaforge gepflegt (http://www.javaforge.com/project/HGE). Eine<br />
Beschreibung ist dort zu finden [HGE].<br />
Mercurial bietet die von Subversion bekannten Funktionen an und unterstützt zusätzlich<br />
eine dezentrale Entwicklung. Dazu können bei einer verteilten Entwicklung dezentral eigenständige<br />
Repositorien gepflegt werden. Ein Repository verwaltet in einem Speicher die vollständige<br />
Historie der Entwicklung eines Projekts (alle jemals durchgeführten Änderungen).<br />
Das Arbeitsverzeichnis eines Projektbearbeiters enthält eine Kopie der Verzeichnisse/Dateien<br />
des Projekts zu einem bestimmten Zeitpunkt und verwaltet eine eigene private Kopie dieser<br />
Historie (in einem Verzeichnis .hg).<br />
Beim Klonen (clone) eines Repositorys entsteht ein neues Repository, das eine exakte<br />
Kopie des alten Repositorys zum Zeitpunkt des Klonens darstellt. Es umfasst den Inhalt des<br />
Arbeitsspeichers einschließlich der gesamten Historie.<br />
Aufeinanderfolgende Änderungen an verschiedenen Dateien können jeweils zu einem atomaren<br />
Changeset zusammengefasst werden. Changesets entsprechen den Revisionen des Gesamtprojekts.<br />
Changesets erhalten aufeinanderfolgende Nummern. Mercurial weist außerdem<br />
jeder Revision eine globale Changeset-ID zu. Eine ChangesetID ist eine 40-stellige Hexadezimalzahl.<br />
Zweige (branches) und Zusammenführungen (merges) können in der Revisions-Historie<br />
an jedem Punkt auftreten. Jeder nicht zusammengeführte Zweig erzeugt einen neuen Head<br />
der Revisionshistorie. Der Head mit der höchsten Revisionsnummer heißt Tip (tip).<br />
Beispiel. Ein Projekt wird von Alice und Bob bearbeitet. Im Repository von Alice existieren<br />
vier Revisionen:<br />
Bob klont das Repository von Alice, es sieht danach wie folgt aus:<br />
Bob führt Änderungen in seinem Arbeitsverzeichnis durch und führt ein Commit durch:
7.2. VERSIONSVERWALTUNGSSYSTEME 100<br />
Parallel dazu führt Alice ihre eigenen Änderungen durch:<br />
Mittels Pull kann Bob nun alle Änderungen von Alice in sein Repository übernehmen<br />
(mittels Push kann Alice dasselbe tun).<br />
Weil Alices g der jüngste Head in Bobs Repository ist, wird er als Tip gekennzeichnet.<br />
Bob führt ein Merge durch, das seine letzte Änderung mit dem Tip verbindet. Für das<br />
Ergebnis wird ein Commit durchgeführt:<br />
Nutzung von Mercurial unter Eclipse<br />
Es wird zunächst ein Repository benötigt. Dieses liegt typischerweise auf dem lokalen Rechner.<br />
Es gibt zwei Wege:<br />
1. Man kreiert ein Mercurial-Repository durch Klonen eines bereits vorhandenen Repositorys<br />
oder durch Anlegen eines neuen Repositorys (vgl. Abb. 7.2.2).<br />
Bei Auswahl Create New Mercurial Repository wird ein neues Mercurial-Repository<br />
erstellt. Dabei wird man aufgefordert, den Speicherort für das Repository festzulegen<br />
(vgl. Abb. 7.2.3). Ein neues, lokales Repository wird bei Bestätigung (Finish) eingerichtet.<br />
Damit existiert aber noch kein Java- bzw. Topcased-Projekt. Dies kann jetzt<br />
nachträglich erfolgen, der Projektname entspricht dem Repository-Namen.<br />
2. Ein bestehendes Java- oder Topcased-Projekt wird für Mercurial erweitert (share).<br />
Dabei kann ein bestehendes Repository referenziert oder ein neues angelegt werden.<br />
Nachdem ein Java-Projekt unter Mercurial-Verwaltung gestellt wurde, muss das Repository<br />
”<br />
befüllt” werden (vgl. Abb. 7.2.6).<br />
Ein Repository kann als Bundle exportiert und importiert werden.
7.2. VERSIONSVERWALTUNGSSYSTEME 101<br />
Abbildung 7.2.2: Wizard zum Erzeugen eines Mercurial-Repositorys<br />
Das Klonen eines Repositorys erfolgt von der Kommandozeile:<br />
ilap124:workspace fritzsch$ hg clone XXX YYY<br />
Aktualisiere auf Zweig default<br />
6 Dateien aktualisiert, 0 Dateien zusammengeführt, 0 Dateien entfernt, 0<br />
Dateien ungelöst<br />
ilap124:workspace fritzsch$
7.2. VERSIONSVERWALTUNGSSYSTEME 102<br />
Abbildung 7.2.3: Speicherort für das Mercurial-Repository festlegen<br />
Abbildung 7.2.4: Java-Projekt erweitern für Versionsverwaltung mit Mercurial
7.2. VERSIONSVERWALTUNGSSYSTEME 103<br />
Abbildung 7.2.5: Repository im Projekt-Verzeichnis anlegen
7.2. VERSIONSVERWALTUNGSSYSTEME 104<br />
Abbildung 7.2.6: Repository mit Konfigurationselementen befüllen<br />
Abbildung 7.2.7: Bundle
7.3. AUFBAU EINES BUILD-PROZESSES 105<br />
7.3 Aufbau eines Build-Prozesses<br />
Die Schritte zur Erstellung eines Produkts sollen jederzeit wiederholt werden können. Sie<br />
sollen deshalb automatisiert werden.<br />
Obwohl der Build-Prozess abhängig von der Basistechnologie ist, gibt es Schritte, die<br />
nahezu in jedem Build-Prozess berücksichtigt werden müssen (vgl. [Pop08]):<br />
1. Quellelemente ermitteln<br />
2. Produkt aus den Quellelementen erstellen<br />
3. Produkt prüfen<br />
4. Produkt ausliefern<br />
5. Ergebnisse dokumentieren<br />
Die Basis für jede Automatisierung bilden Skripte, die von einem Interpreter ausgeführt<br />
werden. Beispiele für entsprehende Systeme sind Ant und Maven. Auch Shell-Skripte können<br />
verwendet werden, die allerdings nicht plattformunabhängig sind.<br />
Wird eine Version des <strong>Software</strong>produktes an die Anwender ausgeliefert, bezeichnet man<br />
dies als Release.<br />
Die Werkzeuge Ant und Maven unterstützen die Projektautomatisierung.<br />
7.4 Ant<br />
7.4.1 Nutzung von Ant<br />
Das Werkzeug Ant dient zur Umsetzung eines Build-Prozesses. Es ist ein Nachfolger von<br />
make, aber nicht wie dieses an eine Plattform gebunden.<br />
Ant ist ein Produkt der Apache <strong>Software</strong> Foundation und wird unter<br />
http://ant.apache.org/bindownload.cgi u.a. als zip-Archiv bereitgestellt. Aktuell ist die<br />
Version Ant 1.8.2. Das System ist nach der Installation von der Kommandozeile zu benutzen.<br />
Zur Installation wird das Archiv heruntergeladen und in einem beliebigen Verzeichnis des lokalen<br />
Dateisystems entpackt. Anschließend wird die Umgebungsvariable ANT_HOME definiert.<br />
Sie soll auf das Homeverzeichnis des zuvor entpackten Ant-Archives verweisen.<br />
Zur Ausführung von Ant wird ein JDK ab Version 1.5 benötigt. Der Pfad zum JDK sollte<br />
Wert der Umgebungsvariablen $JAVA_HOME sein.<br />
Die Umgebungsvariable PATH wird zweckmäßigerweise um $ANT_HOME/bin ergänzt.
7.4. ANT 106<br />
Beispiel 2.<br />
Vorbereitung der Nutzung von Ant an der Kommandozeile: ilap124:Downloads fritzsch$<br />
export ANT_HOME=/Users/fritzsch/Downloads/apache-ant-1.8.2<br />
ilap124:Downloads fritzsch$ export PATH=$Ant_HOME/bin:$PATH<br />
ilap124:Downloads fritzsch$ ant -version<br />
Apache Ant(TM) version 1.8.2 compiled on December 20 2010<br />
ilap124:Downloads fritzsch$<br />
Ant sucht im aktuellen Arbeitsverzeichnis nach einem Build-Skript mit dem Namen<br />
build.xml.<br />
Die Dokumentation ist im Unterverzeichnis $ANT_HOME/docs zu finden.<br />
Ant kann in Eclipse-Java-Projekten genutzt werden (vgl. Abb. 7.4.1). Eine Auswahl<br />
der auszuführenden Build-Ziele ist über einen Wizard möglich (Kontextmenü der Datei<br />
build.xml → Run As ... → Ant ... , vgl. Abb. 7.4.3). Die Ziele wurden im Build-Skript<br />
definiert. Wie Ziele definiert werden, wird im nächsten Abschnitt beschrieben.<br />
7.4.2 Erstellung von Ant-Skripten<br />
Ant-Skripte werden als XML-Dateien geschrieben. Auf der obersten Ebene eines Ant-Build-<br />
Skripts ist das Project-Element angeordnet. Bei der Implementierung eines Build-Skriptes<br />
sollten fest kodierte Werte vermieden werden. Alle Konstanten werden deshalb als Properties<br />
definiert. Darunter werden Schritte in Form von Zielen (Targets) festgelegt.<br />
Eine von mehreren Möglichkeiten für die Angabe von Properties ist<br />
<br />
Properties gelten unabhängig vom Ort ihres Auftretens global für das gesamte Skript. Ein<br />
einmal zugewiesener Wert kann nachfolgend nicht mehr geändert werden. Auf den Wert einer<br />
Property kann nach der Definition an beliebiger Stelle im Skript mittels ${name} zugegriffen<br />
werden. Folgende Properties sind vordefiniert:<br />
${basedir}<br />
${ant.file}<br />
${ant.version}<br />
${ant.project.name}<br />
${ant.java.version}<br />
Wert des basedir-Attributs im project-Element<br />
Pfad des build-Skripts<br />
Version von Ant<br />
Name des Projektes entsprechend name-Attribut im<br />
project-Element<br />
Version der JVM, die Ant ausführt
7.4. ANT 107<br />
Abbildung 7.4.1: Ant-Aufruf in Eclipse für das Projekt Jamus1.11
7.4. ANT 108<br />
Abbildung 7.4.2: Auswahl der auszuführenden build-Ziele
7.4. ANT 109<br />
Beispiel 3. Ant-Skript mit den Zielen<br />
• Anlegen von Zielverzeichnissen (prepare),<br />
• Generieren der API (gen-api),<br />
• Übersetzen von Java-Klassen (compile)<br />
Zeile 1 erklärt die Datei zu einer XML-Datei. Darauf folgt die Definition des Projekts. Mittels<br />
des Attributs name wird der Name des Projektes festgelegt. Das Attribut basedir bestimmt<br />
das Bezugsverzeichnis für Verzeichnisangaben in Zielen und Funktionen. Die Angabe „..”<br />
ist sinnvoll, wenn das Skript selbst in einem Verzeichnis auf derselben Stufe mit src und<br />
bin liegt.<br />
Die bei Ausführung des Zieles prepare (Zeile 12) angelegten Verzeichnisse (sie werden erzeugt,<br />
wenn sie noch nicht existieren) sind in Eclipse in der Projekt-Explorer-View oder der<br />
Navigator-View erst nach einem refresh (F5) sichtbar.<br />
Zeile 19 enthält die Definition eines Filesets zur Festlegung der Quelldateien, die bei Javadoc<br />
einbezogen werden sollen.<br />
Beim Aufruf von Ant wird der Name des auszuführenden Ziels als Parameter überge-
7.4. ANT 110<br />
ben. Jedes Ziel besteht aus einer oder mehreren Funktionen (Tasks). Target-Namen legt<br />
der Anwender fest, Task-Namen werden von Ant bereitgestellt. Die Funktionen bilden den<br />
Befehlsumfang der Skriptsprache. Datentypen stellen die Variablen der Sprache dar.<br />
Häufig muss nach dem Übersetzen auch ein Produkt erstellt werden.<br />
Abbildung 7.4.3: Aufruf von Ant mit dem Ziel prepare<br />
An der Console wird die Verarbeitung protokolliert (vgl. Abb. 7.4.4).<br />
Zum Aufruf des Java-Compilers dient die Javac-Funktion. Eine Minimalversion verwendet<br />
Quell- und Zielverzeichnis als Parameter.<br />
Die Erzeugung eines Java-Archives aus .class-Dateien kann mittels der Jar-Funktion erfolgen.<br />
Abb. 7.4.5 zeigt ein entsprechendes Ziel im Ant-Skript. Das erzeugte Archiv unixkunst.jar<br />
hat (nach dem Entpacken) die im Verzeichnis unixkunst dargestellte Struktur (Abb. 7.4.6).
7.4. ANT 111<br />
Abbildung 7.4.4: Protokollierung der Ant-Verarbeitung<br />
Abbildung 7.4.5: Ziel zur Erzeugung eines Jar-Archives im Ant-Skript.<br />
Abbildung 7.4.6: im Workspace erzeugtes jar-Archiv
7.5. MAVEN 112<br />
7.5 Maven<br />
Maven ist ein Produkt der Apache <strong>Software</strong> Foundation und wird unter<br />
http://maven.apache.org/download u.a. als zip-Archiv bereitgestellt. Aktuell ist die Version<br />
Maven 3.0.3. Das System ist nach der Installation von der Kommandozeile zu benutzen.<br />
Zur Installation wird das Archiv heruntergeladen und in einem beliebigen Verzeichnis des<br />
lokalen Dateisystems entpackt. Anschließend wird die Umgebungsvariable MAVEN_HOME definiert.<br />
Sie soll auf das Homeverzeichnis des zuvor entpackten Maven-Archivs verweisen. Die<br />
Umgebungsvariable PATH muss um $MAVEN_HOME/bin ergänzt werden. Weiterhin muss die<br />
Umgebungsvariable JAVA_HOME gesetzt sein und es wird ein JDK ab Version 1.5 vorausgesetzt.<br />
Mit dem Befehl mvn -version kann überprüft werden, ob die Variablen korrekt gesetzt<br />
sind.<br />
Beispiel 4.<br />
ilap124:~ fritzsch$ export MAVEN_HOME=/Users/fritzsch/Downloads/apache-maven-3.0.3<br />
ilap124:~ fritzsch$ export PATH=$MAVEN_HOME/bin:$PATH<br />
ilap124:~ fritzsch$ mvn -version<br />
Apache Maven 3.0.3 (r1075438; 2011-02-28 18:31:09+0100)<br />
Maven home: /Users/fritzsch/Downloads/apache-maven-3.0.3<br />
Java version: 1.6.0_15, vendor: Apple Inc.<br />
Java home: /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home<br />
Default locale: de_DE, platform encoding: MacRoman<br />
OS name: "mac os x", version: "10.6", arch: "x86_64", family: "mac"<br />
ilap124:~ fritzsch$<br />
Maven basiert auf einem deklarativen, Modell-basierten Ansatz. Man beschreibt in einem<br />
Projektmodell (POM genannt) die Metadaten des Projektes. Anhand der Metadaten legt<br />
Maven den Build-Prozess fest und führt ihn aus. Maven kann nur deswegen automatisch den<br />
Build-Prozess festlegen, weil es gewisse Annahmen über den Ablauf eines Build-Prozesses<br />
macht.<br />
Der Funktionsumfang von Maven kann unterteilt werden in:<br />
1. Durchführung des Build-Prozesses<br />
2. Verwaltung der Abhängigkeiten zu externen Bibliotheken<br />
3. Erstellung der Projektdokumentation<br />
Maven geht mit Abhängigkeiten im Build-Prozess vorbildlich um. Maven kümmert sich um<br />
Verwaltung der externen Bibliotheken in Maven-Repositories (keine Versionierung!).<br />
Maven bietet umfangreiche Unterstützung zur Erstellung einer Projekt-Homepage an.<br />
Maven ist eine Java-Anwendung. Sie besteht aus einem kompakten Kern und verschiedenen<br />
Plug-ins.<br />
Der Kern ist in der Lage,
7.5. MAVEN 113<br />
Abbildung 7.5.1: Maven Architektur nach Popp<br />
• ein Projektmodell zu interpretieren und<br />
• die zur Erstellung des Produktes notwendigen Schritte festzulegen (Phasen).<br />
Jede einzelne Phase wird von einem Build-Ziel ausgeführt. Benötigte Build-Ziele müssen<br />
als Plug-ins nachinstalliert werden. Die Nachinstallation erfolgt vollautomatisch über eine<br />
Internet-Verbindung. Der Maven-Kern sucht plug-ins zunächst im lokalen Repository. Dieses<br />
wird standardmäßig im Benutzerverzeichnis des Anwenders unter .m2\repository angelegt.<br />
Ist ein Plug-in dort nicht vorhanden, wird das Plug-in-Repository durchsucht. Dieses befindet<br />
sich standardmäßig unter<br />
http://repo1.maven.org/maven2.<br />
Das Projektmodell wird in einer XML-Datei erfasst, die standardmäßig pom.xml heisst.<br />
Sie wird zweckmäßigerweise im Wurzelverzeichnis des Entwicklungsprojekts angelegt.<br />
Das Projektmodell enthält:<br />
... zur Beschreibung des zu erstellenden Artefakts. Weiterhin sind<br />
die Pfade zu den Quellelementen festzulegen.<br />
In Tabelle 7.1 sind wichtige Basisfunktionen von Maven zusammengestellt, die in der
7.5. MAVEN 114<br />
Kommandozeile aufgerufen werden können. Die Syntax für den Aufruf lautet:<br />
mvn basisfunktion [ basisfunktion ... ]<br />
Basisfunktion<br />
compile<br />
test<br />
package<br />
install<br />
clean<br />
site<br />
deploy<br />
Verwendung<br />
Compilieren der Quellcodes<br />
Ausführen von Unit-Tests<br />
Java-Archiv im target-Verzeichnis erzeugen<br />
Kopieren des Java-Archivs in lokale Repository<br />
Aufräumen des Projektverzeichnisses target<br />
Erstellen der Dokumentation<br />
Kopieren des Java-Archivs in ein angegebenes Repository<br />
Das Compilieren zeigt Abb. 7.5.2.<br />
Tabelle 7.1: Phasen<br />
Installation des Maven-Plug-ins in Eclipse<br />
Die ”<br />
update site” für Maven muss dem Wizard zur Installation neuer <strong>Software</strong> in Eclipse<br />
hinzugefügt werden.<br />
Nach dem Installieren ist Maven im Kontextmenü eines Projektes verfügbar.
7.5. MAVEN 115<br />
Abbildung 7.5.2: Compilieren
7.5. MAVEN 116<br />
Abbildung 7.5.3: Maven für Eclipse<br />
Abbildung 7.5.4: Maven-Auswahl
7.6. ZUSTANDSBASIERTES TESTEN MIT JUNIT UND JUNITDOCLET 117<br />
7.6 Zustandsbasiertes Testen mit JUnit und JUnitDoclet<br />
Im Zusammenhang mit dem Build-Prozess soll nachfolgend die Unterstützung des Testens<br />
mit den Werkzeugen JUnit und JUnitDoclet behandelt werden. JUnit ist ein Java-<br />
Framework, das bereits in Eclipse integriert ist und im Kontext der IDE genutzt werden<br />
kann (mindestens ab Eclipse 3.6).<br />
Betrachtet wird hier der Klassentest als spezielle Art eines Unit-Tests. Der Klassentest<br />
basiert auf dem Konzept des ”<br />
zustandsbasierten Testens”: Die Wirkung der Anwendung einer<br />
Methode auf ein Objekt (Instanz) hängt im allgemeinen sowohl von den Methodenparametern<br />
als auch vom aktuellen Zustand des Objekts ab.<br />
Jeder Testfall umfasst<br />
• die Erzeugung eines Objekts und das Versetzen des Objekts in einen bestimmten Ausgangszustand,<br />
anschließend<br />
• die Ausführung einer Operation (den Aufruf einer Methode, ”<br />
invocation”) und danach<br />
• die Validierung des durch die Operation herbeigeführten Endzustands.<br />
Ein Objekt wird als sequentieller Zustandsautomat (FSA) betrachtet. Es wird davon ausgegangen,<br />
dass die Menge der Zustände des AUtomaten endlich ist.<br />
Beispiel. Als Beispiel betrachten wir eine Klasse Count zur Realisierung eines Zählers, der<br />
bei 1 beginnend fortlaufend modulo 4 zählt. Abb. 7.6.1 zeigt den Quelltext.<br />
Abbildung 7.6.1: Zu testende Klasse Count<br />
Der aktuelle Zustand eines Objekts wird durch die Kombination der Attributwerte aller<br />
Attribute zu einem bestimmten Zeitpunkt gebildet. Um die potentiell große Zahl von<br />
Zuständen zu reduzieren, werden ”<br />
verallgemeinerte Zustände” (Substates) gebildet.
7.6. ZUSTANDSBASIERTES TESTEN MIT JUNIT UND JUNITDOCLET 118<br />
Der Zustand eines Count-Objekts soll vor und nach Ausführung einer Operation überprüft<br />
werden. Es wird eine Klasse CountUT erzeugt, die von Count abgeleitet wird und dadurch<br />
Zugriff auf die Attribute erlaubt.<br />
Es werden zwei Ziele verfolgt:<br />
1. Es soll ein Substate geprüft werden können. test-for-substate1 liefert true, wenn<br />
der als Parameter übergebene Zustand tatsächlich vorliegt, und sonst false.<br />
2. Es soll geprüft werden können, ob sich ein Attributwert geändert hat. iStoreMirror<br />
ist die Spiegelung eines Attributs. Das Mirror-Attribut wird initialisiert durch Aufruf<br />
der Operation test-for-substate-change1 mit true.<br />
Der Test:<br />
Abbildung 7.6.2: Klasse CountUT<br />
fritzsch$ java -classpath /usr/share/junit:/usr/share/junit/junit.jar:.
7.6. ZUSTANDSBASIERTES TESTEN MIT JUNIT UND JUNITDOCLET 119<br />
junit.textui.TestRunner JavaTests.CountUTTest<br />
.<br />
Test: testNext()<br />
Test: fertig<br />
Time: 0,002<br />
OK (1 test)<br />
fritzsch$
7.6. ZUSTANDSBASIERTES TESTEN MIT JUNIT UND JUNITDOCLET 120<br />
Abbildung 7.6.3: Ableitung der Testklasse CountUTTest vom JUnit-Framework
7.6. ZUSTANDSBASIERTES TESTEN MIT JUNIT UND JUNITDOCLET 121<br />
Abbildung 7.6.4: CountUTTest
7.7. GENERIEREN VON DOKUMENTATIONEN MIT JAVADOC 122<br />
7.7 Generieren von Dokumentationen mit Javadoc<br />
Mit dem Werkzeug Javadoc können zu Java-Quelltexten automatisch HTML-Dateien erzeugt<br />
werden, die die Struktur und den Quellcode dokumentieren (API). Das Programm javadoc<br />
ist Bestandteil des JDK.<br />
Javadoc parst Java-Quelltexte und erkennt (in der Regel Methoden vorangestellte) Javadoc-<br />
Kommentare in der Form:<br />
Javadoc-Kommentare können Javadoc-Tags enthalten (vgl. Abb. 7.7.1). Ein Doclet erzeugt<br />
die Ausgabe, es existieren Doclets für die Ausgabe in HTML, RTF, XML, PDF und<br />
anderen Formaten.<br />
Abbildung 7.7.1: Javadoc-Tags<br />
Syntax des Aufrufs von der Kommandozeile:<br />
javadoc [ options ] { package | sourcefile }<br />
Alternativ zum Aufruf mit Quelldatei-Namen kann javadoc auch mit Paketnamen als
7.7. GENERIEREN VON DOKUMENTATIONEN MIT JAVADOC 123<br />
Argument aufgerufen werden. Wenn nicht mit der Option -d etwas anderes verlangt wird,<br />
erzeugt javadoc die Dokumentationsdateien im aktuellen Verzeichnis.<br />
Abbildung 7.7.2 zeigt ein Beispiel zur Generierung mit Javadoc. Nach der Option -d wird<br />
das Zielverzeichnis für die Generierung angegeben.<br />
Abbildung 7.7.2: Beispiel eines Javadoc-Aufrufs<br />
Javadoc kann auch in Eclipse verwendet werden. Die Generierung wird über das Projekt-<br />
Menü erreicht (Abb. 7.7.3 und Abb. 7.7.4).<br />
Abbildung 7.7.3: Javadoc in Eclipse<br />
Die Visualisierung erfolgt nach der Generierung, indem die index.html mit einem Webbrowser<br />
geöffnet wird.<br />
Auch vom Quelltext aus können kontextabhängig mittels F3 die Javadoc Erklärungen in<br />
einer separaten View angezeigt werden.
7.7. GENERIEREN VON DOKUMENTATIONEN MIT JAVADOC 124<br />
Abbildung 7.7.4: Javadoc-Wizard
7.8. SIGNIEREN VON JAVA-ARCHIVEN 125<br />
7.8 Signieren von Java-Archiven<br />
Mit dem (kommandozeilenorientierten) keytool können<br />
• eine keystore-Datei erzeugt werden,<br />
• Schlüssel-Einträge und Zertifikat-Einträge in eine keystore-Datei vorgenommen werden.<br />
Das keytool ist Bestandteil des JDK.
Kapitel 8<br />
Wiederverwendung und Evolution<br />
8.1 Individuelle Komponenten: Module und Klassen<br />
Klassen verkörpern abstrakte Datentypen (ADT). Sie stellen Operationen zum Erzeugen<br />
von Instanzeobjekten bereit und bilden damit eine Art von ”<br />
Objektfabriken”. Neue Klassen<br />
können von vorhandenen Klassen abgeleitet werden. Das kann in separaten Übersetzungseinheiten<br />
erfolgen, bestehende Quelltexte müssen dazu nicht verändert werden.<br />
Die Wiederverwendung individueller Komponenten ist nicht kostenlos. Es entsteht Aufwand<br />
für das Ausfindigmachen anwendbarer Komponenten bzw. Klassen, für das Verstehen<br />
von Schnittstellen und für die Wartung der Bibliotheken. Objektbasierte Sprachen allein<br />
garantieren noch nicht die Wiederverwendung!<br />
Beispiel. Die im JDK (Java Development Kit) bereitgestellte Standardbibliothek umfasst<br />
folgende Pakete:<br />
java.applet<br />
java.awt<br />
java.lang<br />
java.io<br />
java.net<br />
java.util<br />
Die Funktionalität der Standardbibliothek ist u.a. in [MSS96]beschrieben.<br />
Bibliotheken mit Top-down-Routinen bzw. Klassen stellen keine Anwendungsstrukturen<br />
bereit, d.h. die Systemarchitektur muss selbst definiert werden (vgl. Abb. 8.1.1). Das Verhalten<br />
einzelner Routinen wird durch Parameter flexibel gestaltet.<br />
Bei Bibliotheken mit Routinen im Callback-Verfahren liegt umgekehrt die gesamte Anwendungsarchitektur<br />
in den Bibliotheksroutinen (vgl. Abb. 8.1.2). Die Routinen der Anwendung<br />
werden meist zyklisch aktiviert.<br />
126
8.2. KOMPONENTENBASIERTE SOFTWAREENTWICKLUNG - FRAMEWORKS 127<br />
Abbildung 8.1.1: Bibliotheken mit Top-down-Routinen bzw. Klassen<br />
Abbildung 8.1.2: Bibliotheken mit Routinen im Callback-Verfahren<br />
8.2 Komponentenbasierte <strong>Software</strong>entwicklung - Frameworks<br />
Die komponentenbasierte <strong>Software</strong>entwicklung mit Frameworks wird u.a. von W. Pree in<br />
[Pre97] ausführlich behandelt. Die nachfolgenden Beispiele entstammen dieser Quelle.<br />
Ein Framework ist eine Sammlung individueller Komponenten mit definiertem Koperationsverhalten<br />
zur Lösung einer bestimmten Aufgabe. Frameworks geben im Unterschied zu<br />
gewöhnlichen Klassenbibliotheken die Architektur eines Systems vor, beschreiben also auch<br />
die Interaktionen zwischen Komponenten (Verbindungslinien in den Abbildungen 8.2.2 und<br />
8.2.3).<br />
Gewisse Komponenten eines Frameworks sollen austauschbar sein. Solche ”<br />
variablen”<br />
Stellen eines Frameworks heißen Hot-Spots.<br />
Whitebox-Frameworks bestehen aus einer Anzahl unvollständig spezifizierter Klassen, d.h.<br />
Klassen mit abstrakten Methoden, sog. Einschubmethoden.<br />
Um das Verhalten von White-Box-Frameworks zu verändern, wird Vererbung eingesetzt.<br />
Einschubmethoden werden in den Unterklassen der Frameworkklassen überschrieben. Die<br />
Architektur der Anwendung verbleibt dabei im Framework. Beispiel (Abbildung 8.2.1) : Die<br />
grau markierte Methode in Klasse A wird in einer Unterklasse A1 überschrieben.<br />
Um Methoden sinnvoll überschreiben zu können, muss ein Programmierer das Framework<br />
bis zu einem gewissen Detaillierungsgrad gedanklich erfasst und verstanden haben.
8.2. KOMPONENTENBASIERTE SOFTWAREENTWICKLUNG - FRAMEWORKS 128<br />
. . .<br />
. . .<br />
Frameworkklassen<br />
. . .<br />
A<br />
B<br />
A1<br />
Anpassung<br />
des<br />
Frameworks<br />
Abbildung 8.2.1: Beispiel für eine Hierarchie von Frameworkklassen<br />
Blackbox-Frameworks bestehen aus einer Anzahl fertiger Komponenten. Anpassungen des<br />
Frameworks werden durch Kompositionen der Komponenten erreicht, nicht durch Vervollständigung<br />
von Klassen, also nicht durch Programmierung.<br />
Beispiel. An den Stellen A und B in Abb. 8.2.2 werden Instanzen benötigt, die zu den<br />
Klassen A und B kompatibel sind. Flexibilität wird dadurch erreicht, dass an diesen Stellen<br />
Instanzen der Klassen A1 und B2 gebildet und entsprechend eingesetzt werden, wie in Abb.<br />
8.2.3 gezeigt . Bei B liegt eine gebrauchsfertige Unterklasse vor. Bei A existiert eine abstrakte<br />
Methode (grau), die die Unterklasse A1 erst noch spezifizieren muss.<br />
Aus einem Whitebox-Framework entwickelt sich mit zunehmendem Reifegrad ein Blackbox-<br />
Framework. Gängige Frameworks sind weder reine Whitebox- noch reine Blackbox-Frameworks.<br />
Nachfolgend soll nun an einem Beispiel dargestellt werden, wie ein Framework entwickelt<br />
und für die Entwicklung einer Anwendung nachgenutzt und angepasst wird.<br />
Beispiel. Entwicklung eines Frameworks zur Simulation diskreter Ereignisse ( ”<br />
Diskrete Simulation”).<br />
Das Framework soll anschließend zur Simulation einer Kasse im Supermarkt und<br />
zur Simulation eines Parkhauses verwendet werden.<br />
Zentrale Elemente einer Simulation sind Ereignisse mit zugeordneter Zeit (Zeitpunkt,<br />
zu dem ein Ereignis eintritt. Zum Beispiel der Zeitpunkt, zu dem ein Kunde an der Kasse<br />
eines Supermarktes erscheint). Ereignisse werden durch einen Zufallsgenerator erzeugt. Die<br />
Simulation entwickelt sich in Richtung der Zeitachse und sammelt statistische Informationen.
8.2. KOMPONENTENBASIERTE SOFTWAREENTWICKLUNG - FRAMEWORKS 129<br />
A<br />
B<br />
Abbildung 8.2.2: Framework vor der Spezialisierung<br />
A1<br />
B2<br />
Abbildung 8.2.3: Framework nach der Spezialisierung<br />
Ein Actor (oder Akteur) ist ein Paar: Ereignis - Aktion (vgl. Abb. 8.2.4). Akteure wissen,<br />
wie sie auf erhaltene Nachrichten zu reagieren haben. In der Instanzvariablen time ist der<br />
Zeitpunkt des Eintritts des Ereignisses gespeichert. Die Methode commit() kapselt die Funktionalität<br />
des Actors. Das Simulationsframework ruft commit() auf, wenn die Eintrittszeit<br />
des Ereignisses erreicht ist.<br />
Die Klassen Simulation und Actor repräsentieren das Simulationsframework (vgl. Abb.<br />
8.2.5). Eine Instanz von Simulation verwaltet Akteure in einer SortedQueue.<br />
Eine Methode schedule der Klasse Simulation fügt einen Actor entsprechend dem<br />
Eintrittszeitpunkt seines Ereignisses in die SortedQueue ein. Die Zeit des Actors wird auf<br />
die aktuelle Simulationszeit eingestellt.<br />
Die Methode simulate iteriert über den Actor-Objekten in der SortedQueue (vgl. Abb.<br />
8.2.6). Dabei wird die aktuelle Zeit jeweils auf die Zeit des Actors eingestellt. Danach wird
8.2. KOMPONENTENBASIERTE SOFTWAREENTWICKLUNG - FRAMEWORKS 130<br />
Abbildung 8.2.4: Klasse Actor<br />
Abbildung 8.2.5: Simulations-Framework<br />
eine commit()-Nachricht an den Actor gesendet. Die Simulation endet, wenn die zu Beginn<br />
der Simulation eingestellte Simulationszeit verbraucht ist (Parameter duration). Sind keine<br />
Actor-Objekte mehr in der Warteschlange, wird das Ende der Simulation erzwungen.<br />
Auf die jeweils aktuelle Simulationszeit kann bei der Erzeugung neuer Actor-Objekte<br />
während des Ablaufens der Simulation Bezug genommen werden. In der Instanzvariablen<br />
time wird die Eintrittszeit des Ereignisses gespeichert.<br />
Mittels der Methode reset kann die Simulation ”<br />
auf Anfang” gestellt werden: Die Warteschlange<br />
in SortedQueue wird gelöscht und die abgelaufene Simulationszeit wird zurückgesetzt<br />
(time=0).<br />
Die Klasse Simulation realisiert eine (abstrakte) Simulation auf der Basis des Protokolls<br />
der abstrakten Klasse Actor. Jede Instanz von Simulation kann mit jeder Instanz von Actor<br />
oder jeder Subklasse von Actor interagieren, und das ohne Änderungen und Neuübersetzung.<br />
Das Verhalten von Actor (abstrakt!) kann später überschrieben werden.<br />
Das Framework setzt die Existenz von Klassen zur Realisierung von Warteschlangen<br />
voraus. Die in Abb. 8.2.7 gezeigten Klassen werden im Package Qs des Simulationsframeworks<br />
bereitgestellt. Die Einträge in einer Queue sind vom Typ QueueItem. Diese Klasse ist<br />
ebenfalls im Package Qs enthalten. Die Einträge in einer SortedQueue werden gemäß Ereigniszeitpunkt<br />
geordnet. Aufgrund der Polymorphie können Queue-Objekte auch Instanzen<br />
von Unterklassen von Actor speichern.<br />
Durch die Klassen Simulation und Actor wird ein White-Box-Framework gebildet. Die-
8.2. KOMPONENTENBASIERTE SOFTWAREENTWICKLUNG - FRAMEWORKS 131<br />
public class Simulation{<br />
public long time;<br />
protected SortedQueue actors;<br />
...<br />
public void simulate(long duration){<br />
Actor actor;<br />
long endOfSimulation = time + duration;<br />
do{<br />
if (!actors.isEmpty()){<br />
actor = actors.dequeue();<br />
time = actor.time;<br />
actor.commit();<br />
} else // no more actors enqueued<br />
time = endOfSimulation + 1;<br />
} while (time
8.2. KOMPONENTENBASIERTE SOFTWAREENTWICKLUNG - FRAMEWORKS 132<br />
Abbildung 8.2.7: Warteschlangen<br />
in die SortedQueue mit einem zeitlichen Abstand ein, der seiner Bediendauer entspricht.<br />
Kommt ein neuer Kunde an, während noch ein anderer Kunde bedient wird, wird der neue<br />
Kunde in die Kundenwarteschlange waitingLine eingereiht (Aufruf der Methode requestService).
8.2. KOMPONENTENBASIERTE SOFTWAREENTWICKLUNG - FRAMEWORKS 133<br />
public class SimpleServiceStation{<br />
protected Simulation sim;<br />
protected FIFOQueue waitingLine = new FIFOQueue();<br />
...<br />
public void requestService(Customer c){<br />
}<br />
if (path.n == 0)<br />
// Warteschlange leer<br />
// und kein Kunde wird gerade bedient ?<br />
c.activate();<br />
else<br />
waitingLine.enqueue(c);<br />
// Kunden in die Kundenwarteschlange<br />
// aufnehmen<br />
...<br />
// Erstellen von Statistiken<br />
}<br />
public void free(){<br />
Actor actor;<br />
}<br />
...<br />
// Erstellen von Statistiken<br />
...<br />
if (path.n > 0){<br />
// Warteschlange enthaelt Kunden ?<br />
actor = waitingLine.dequeue();<br />
if (actor instanceof Customer)<br />
((Customer)actor).activate();<br />
}<br />
Abbildung 8.2.8: Klasse SimpleServiceStation realisiert eine Kasse
8.2. KOMPONENTENBASIERTE SOFTWAREENTWICKLUNG - FRAMEWORKS 134<br />
public class Customer extends Actor{<br />
SimpleServiceStation station;<br />
Simulation simulation;<br />
long arrivalTime;<br />
public Customer(long t, SimpleServiceStation s, Simulation sim){<br />
super(t);<br />
station = s;<br />
simulation = sim;<br />
arrivalTime = simulation.time;<br />
}<br />
public void commit(){<br />
station.free();<br />
...<br />
}<br />
public void activate(){<br />
int myServiceDuration;<br />
simulation.schedule(this,simulation.time +<br />
(myServiceDuration =<br />
(int)(RandomNumbers.negExp(<br />
station.avrgServiceDuration))));<br />
...<br />
}<br />
}<br />
Abbildung 8.2.9: Klasse Customer
8.2. KOMPONENTENBASIERTE SOFTWAREENTWICKLUNG - FRAMEWORKS 135<br />
public class CustomerGenerator extends Actor{<br />
SimpleServiceStation station;<br />
Simulation simulation;<br />
public CustomerGenerator(long t, SimpleServiceStation s, Simulation sim){<br />
super(t);<br />
station = s;<br />
simulation = sim;<br />
}<br />
public void commit(){<br />
Customer c = new Customer(0,station,simulation);<br />
...<br />
station.requestService(c);<br />
simulation.schedule(<br />
new CustomerGenerator(0,station,simulation),<br />
simulation.time + (long)(RandomNumbers.negExp(station.arrivalRate)));<br />
}<br />
...<br />
}<br />
Abbildung 8.2.10: Klasse CustomerGenerator<br />
Abbildung 8.2.11: Szenario zur Bedienung eines Kunden
8.2. KOMPONENTENBASIERTE SOFTWAREENTWICKLUNG - FRAMEWORKS 136<br />
Abbildung 8.2.12: Nutzung des Frameworks ”<br />
Diskrete Simulation”
8.2. KOMPONENTENBASIERTE SOFTWAREENTWICKLUNG - FRAMEWORKS 137<br />
Entwurfsüberlegungen Eine Schwachstelle in der Realisierung der Anwendung ist die enge<br />
Kopplung zwischen den Klassen CustomerGenerator, Customer und SimpleServiceStation:<br />
Die Methode commit von CustomerGenerator erzeugt direkt Customer-Objekte. Die Klasse<br />
SimpleServiceStation arbeitet direkt mit einem Customer-Objekt. Problematisch wird es<br />
damit bei späteren Veränderungen. Zum Beispiel könnte eine Service-Station mit zwei Bedienern<br />
gewünscht sein oder der Ausfall eines Bedieners soll realisiert werden können. Es<br />
werden nachfolgend Mittel gezeigt, wie Frameworks flexibler gestaltet werden können.<br />
Platzierung von Einschubmethoden Schablonenmethoden rufen Einschubmethoden (engl.<br />
hook) auf. Das Überschreiben an vorgegebenen Einschubstellen (in abgeleiteten Klassen) verändert<br />
das Verhalten, ohne dass der Quellcode der Klasse geändert werden muss.<br />
So könnte es im Beispiel Diskrete Simulation interessant sein, die Anzahl benachrichtigter<br />
Actor-Objekte zu erfassen. Abb. 8.2.13 zeigt, wie das Verhalten der Methode simulate<br />
(Schablonenmethode) teilweise (Ausketten und Benachrichtigen eines Actors) in eine Einschubmethode<br />
notificationHook ausgelagert wird. Die Hook-Methode besitzt standardmäßig<br />
eine leere Implementierung. Eine von der Klasse Simulation abgeleitete Klasse MySimulation<br />
kann dann die Einschubmethode notificationHook mit der gewünschten Funktionalität definieren<br />
(vgl. Abb. 8.2.14).<br />
Abbildung 8.2.13: Schablonenmethode simulate und Einschubmethode notificationHook<br />
Entkopplung der Klassen CustomerGenerator und Customer Die Klasse CustomerGenerator<br />
unterstützt explizit die Erzeugung von Customer-Objekten (vgl. Abb. 8.2.10). Das Verhalten<br />
kann auch hier durch die Definition von Unterklassen veränderbar gestaltet werden.<br />
Die Klasse CustomerGenerator wird jetzt durch eine Klasse ActorGenerator ersetzt<br />
(vgl. Abb. 8.2.15).<br />
Wenn Schablonen- und Einschubmethoden sich in einer Klasse befinden, kann Verhalten<br />
nur durch die Definition weiterer Unterklassen verändert werden (Quellcode!). Damit sind<br />
Anpassungen zur Laufzeit nicht möglich.
8.2. KOMPONENTENBASIERTE SOFTWAREENTWICKLUNG - FRAMEWORKS 138<br />
Abbildung 8.2.14: Definition der Hook-Methode in der abgeleiteten Klasse MySimulation<br />
public class ActorGenerator extends Actor {<br />
...<br />
public void commit(){<br />
Actor a=makeActor();<br />
station.requestService(a);<br />
}<br />
public void makeActor(){<br />
return new Customer( ... );<br />
}<br />
...<br />
}<br />
Abbildung 8.2.15: Klasse ActorGenerator<br />
Flexibilität zur Laufzeit durch Komposition Einschubmethoden erlauben flexible Anpassungen<br />
zur Laufzeit, wenn ihre Klasse mit der Klasse abstrakt gekoppelt ist, die die zur<br />
Einschubmethode gehörende Schablonenmethode enthält.<br />
Beispiel Diskrete Simulation: In der bisherigen Lösung wird die abstrakte Methode<br />
enqueue() der Klasse Queue in Unterklassen überschrieben.
8.3. ENTWURFSMUSTER 139<br />
8.3 Entwurfsmuster<br />
Wesentliche Beiträge zur Thematik der Entwurfsmuster wurden von E. Gamma et al. geleistet<br />
[GHJV09]. Die Autoren stellten einen Katalog von 23 Entwurfsmustern vor.<br />
Ein Muster beschreibt allgemein ein in unserer Umwelt beständig wiederkehrendes Problem<br />
und erläutert den Kern der Lösung für dieses Problem. So kann die Lösung beliebig oft<br />
angewendet werden, ohne sie jemals ein zweites Mal gleich auszuführen.<br />
Die hier beschriebenen Entwurfsmuster beschreiben zusammenarbeitende Objekte und<br />
Klassen, die so zugeschnitten sind, dass sie ein allgemeines Entwurfsproblem in einem bestimmten<br />
Kontext lösen. Entwurfsmuster sind typischerweise in Anwendungen vorkommende<br />
Kombinationen von Klassen zu größeren Einheiten, unabhängig von der Programmiersprache.<br />
Muster sind durch vier grundlegende Elemente beschrieben:<br />
1. Mustername<br />
2. Problemabschnitt<br />
3. Lösungsabschnitt<br />
4. Konzequenzenabschnitt<br />
Der von Gamma et al. angegebene Katalog ordnen die Muster drei Klassen zu. Danach gibt<br />
es Erzeugungsmuster, Strukturmuster und Verhaltensmuster.<br />
Das Fabrikmethode-Muster<br />
Das Fabrikmethode-Muster ist ein Klassen-basiertes Erzeugungsmuster. Die Verwendung<br />
erfolgt in einem Framework, bestehend aus den abstrakten Klassen Product und Creator.<br />
Die Klasse Creator bietet eine Schnittstelle zum Erzeugen von Instanzen an. Das Muster<br />
wird genutzt, wenn eine Klasse (hier: Creator) die von ihr zu erzeugenden Objekte nicht im<br />
Voraus kennen kann (andernfalls könnte der Konstruktor direkt aufgerufen werden.<br />
Die abstrakte Methode createProduct() heißt Fabrikmethode.<br />
Das Beobachter-Muster<br />
Das Beobachtermuster ist ein Objekt-basiertes Verhaltensmuster (auch als Publisher - Subscriber<br />
- Muster bezeichnet).<br />
Definiere eine 1-zu-n-Abhängigkeit zwischen Objekten, so dass die Änderung des Zustandes<br />
eines Objektes dazu führt, dass alle abhängigen Objekte benachrichtigt und automatisch<br />
aktualisiert werden.<br />
Beispiel. Zeitgeber ist eine konkrete Subjektklasse zum Verwalten der Tageszeit. Der zeitgeber<br />
benachrichtigt den Beobachter jede Sekunde.<br />
Eine Digitaluhr ist ein Beobachter.
8.3. ENTWURFSMUSTER 140<br />
Abbildung 8.3.1: Fabrikmethode-Muster<br />
Abbildung 8.3.2: Beobachter-Muster<br />
Das Kompositum-Muster<br />
Füge Objekte zu Baumstrukturen zusammen, um Teil-Ganzes-Hierarchien zu repräsentieren.<br />
Das Kompositionsmuster ermöglicht es Klienten, einzelne Objekte sowie Kompositionen von<br />
Objekten einheitlich zu behandeln.<br />
Das Strategie-Muster<br />
Das Muster ist verhaltensorientiert.<br />
Definition. Definiere eine Familie von Algorithmen, kapsele jeden einzeln und mache sie<br />
austauschbar. Das Strategiemuster ermöglicht es, den Algorithmus unabhängig von ihn nutzenden<br />
Klienten zu variieren.
8.3. ENTWURFSMUSTER 141<br />
public class Zeitgeber extends Subject{<br />
...<br />
int gibStunde(){ }<br />
int gibMinute(){ }<br />
int gibSekunde(){ }<br />
void tick(){<br />
// aktualisiert internen Zustand des Zeitgebers<br />
benachrichtige();<br />
}<br />
...<br />
}<br />
Abbildung 8.3.3: Klasse Zeitgeber<br />
Abbildung 8.3.4: Strategiemuster<br />
Proxy-Muster (Proxy)<br />
Definition. Kontrolliere den Zugriff auf ein Objekt mit Hilfe eines vorgelagerten Stellvertreterobjekts.<br />
Der Proxy leitet Befehle an das echte Objekt weiter.<br />
Singleton-Muster (Singleton)<br />
Das Singleton-Muster ist ein Erzeugungsmuster.<br />
Definition. Sichere ab, dass eine Klasse genau eine Instanz besitzt (nur eine) und stelle<br />
einen globalen Zugriffspunkt darauf bereit.<br />
Dies wird üblicherweise durch private-Konstruktoren in Kombination mit static-Klassenfeldern<br />
realisiert.
8.3. ENTWURFSMUSTER 142<br />
Abbildung 8.3.5: Proxy-Muster<br />
Abbildung 8.3.6: Singleton-Muster<br />
public final class Singleton {<br />
private static Singleton uniqueInstance = null;<br />
private Singleton(){ }<br />
public static Singleton instance(){<br />
if (uniqueInstance == null) {<br />
uniqueInstance = new Singleton();<br />
}<br />
return uniqueInstance; }<br />
}<br />
In den meisten Fällen scheinen Singletons unnötig zu sein. Nach Keriewsky ist ein Singleton<br />
unnötig, wenn es einfacher ist, eine Objektressource als Verweis auf die Objekte, die<br />
sie benötigen, zu übergeben, als Objekte global auf die Ressource zugreifen zu lassen. Ein<br />
Singleton ist unnötig, wenn es dazu verwendet wird, unbedeutende Speicher- oder Leistungsverbesserungen<br />
zu erzielen[. . . ].<br />
Fassade-Muster (Facade)<br />
Definition. Biete eine einheitliche, einfache Schnittstelle zu einer Menge von Schnittstellen<br />
bzw. Klassen (Paket). Die Fassadenklasse definiert eine abstrakte Schnittstelle, um die
8.3. ENTWURFSMUSTER 143<br />
Benutzung des Pakets zu vereinfachen.<br />
Die Facade weiß, welche Klassen des Pakets für die Bearbeitung einer Botschaft zuständig<br />
sind und delegiert Botschaften an die zuständige Klasse. Die Fassade definiert keine neue<br />
Funktionalität!<br />
Adapter-Muster (Adapter, Wrapper)<br />
Definition. Passe die Schnittstelle einer Klasse an eine andere, von Kunden erwartete<br />
Schnittstelle an. Zur Anpassung eines Interfaces an ein anderes wird ein Adapterobjekt verwendet.<br />
Abbildung 8.3.7: Adapter-Muster<br />
Schablonenmethode-Muster (Template)<br />
Das Muster ist verhaltensorientiert und objektbasiert.<br />
Definition. Definiere das Skelett eines Algorithmus in einer Operation und delegiere einzelne<br />
Schritte an Unterklassen. Die Verwendung einer Schablonenmethode ermöglicht es Unterklassen,<br />
bestimmte Schritte eines Algorithmus zu überschreiben, ohne seine Struktur zu<br />
verändern.<br />
Schablonenmethoden bilden die grundlegende Technik zur Wiederverwendung von Code.<br />
Im Beispiel (vgl. Abb. 8.3.8) werden in der Schablonenmethode templateMethod() die<br />
Einschubmethoden hookOperation1 und hookOperation2 aufgerufen.<br />
Das Schablonenmethode-Muster haben wir in der Anwendung ”<br />
Diskrete Simulation” verwendet<br />
(vgl. Abb. 8.2.13).
8.3. ENTWURFSMUSTER 144<br />
Abbildung 8.3.8: Schablonenmethode
Kapitel 9<br />
Musterarchitekturen<br />
In diesem Kapitel geht es um die Nachnutzung konkreter <strong>Software</strong>bausteine und Techniken<br />
zu deren Nutzung. Dabei steht die Nützlichkeit für die Entwicklung von typischen Applikationen<br />
im Vordergrund. Solche Applikationen haben eine Client-Server-Architektur, greifen<br />
serverseitig auf Datenbanken zu und benötigen clientseitig grafische Benutzeroberflächen.<br />
9.1 Die MVC-Architektur<br />
Das Model-View-Controller-Prinzip, das bereits in der Programmiersprache Smalltalk für<br />
die Konstruktion von Benutzerschnittstellen Anwendung fand, bietet einen Ansatz für die<br />
Architektur von Anwendungen, die aus einer grafischen Benutzeroberfläche, einem funktionalen<br />
Kern und einer Datenverwaltung bestehen sollen. Ein Model–Objekt stellt das Anwendungsobjekt<br />
dar, das View–Objekt seine Bildschirmrepräsentation und das Controller–<br />
Objekt bestimmt die Möglichkeiten, mit denen auf Benutzereingaben reagiert werden kann.<br />
Grundgedanke ist, dass View und Model durch den Aufbau eines Protokolls zur gegenseitigen<br />
Benachrichtigung entkoppelt werden. Das Model-Objekt repräsentiert das Anwendungsobjekt.<br />
Ein oder mehrere View-Objekte verkörpern Bildschirmrepräsentationen. Das Controller-Objekt<br />
bestimmt die Möglichkeiten, mit denen auf Benutzereingaben reagiert werden<br />
kann.<br />
View und Model werden durch den Aufbau eines Protokolls zur Benachrichtigung voneinander<br />
entkoppelt.<br />
145
9.2. GRAFISCHE BENUTZEROBERFLÄCHEN 146<br />
Der Antwortmechanismus der Oberfläche wird in einen Controller gekapselt.<br />
Die MVC-Architektur verwendet die Entwurfsmuster Beobachter und Strategie.<br />
Ein interessanter Gesichtspunkt ist die Realisierung von der Komponenten in eigenständigen<br />
Threads.<br />
9.2 Grafische Benutzeroberflächen<br />
Die Entwicklung grafischer Benutzeroberflächen erfordert Aktivitäten auf zwei spezielleren<br />
Gebieten:<br />
- Oberflächenprogrammierung<br />
- Grafikprogrammierung<br />
Grafische Benutzeroberflächen werden aus vordefinierten Komponenten komponiert. Nachfolgend<br />
werden die für Java-Anwendungen verfügbaren Bibliotheken<br />
• AWT (schwergewichtig, plattformunabhängig),<br />
• Swing (leichtgewichtig, plattformunabhängig) und<br />
• SWT (plattformabhängig, schwergewichtig)<br />
behandelt.<br />
Der Aufbau einer GUI erfordert 2 Schritte:<br />
1. das Erstellen einer Struktur aus Grafik-Komponenten<br />
2. das Implementieren von Aktionen, die über GUI-Objekte ausgelöst werden.<br />
9.2.1 Java AWT<br />
Das ”<br />
Abstract Window Toolkit” (AWT) stellt eine plattformunabhängige Standard-API für<br />
die Programmierung grafischer Oberflächen dar und ist Bestandteil der Java Foundation<br />
Classes (JFC). Das Toolkit umfasst vordefinierte Klassen, die je nach Betriebssystem die<br />
geeigneten Bibliotheken benutzen (für UNIX z.B. Motif).<br />
Portabilität: Peers Die AWT–Objekte setzen nicht dierekt auf der Programmierschnittstelle<br />
(API) einer bestimmten Betriebssystemplattform auf. Die Unabhängigkeit der GUI-<br />
Klassen von der Betriebssystem-API wird durch Peers sichergestellt. Peers sind in Java<br />
geschriebene Interfaces, die im Paket java.awt.peer enthalten sind. Für unterschiedliche<br />
Plattformen werden Peer-Schnittstellen unterschiedlich implementiert (vgl. Abb. 9.2.1).<br />
Der Aufbau der AWT-Klassenbibliothek ist ein Beispiel für die Anwendung des Entwurfsmusters<br />
Kompositum” (vgl. Abschnitt 8.3).<br />
”<br />
Alle Komponenten sind von der abstrakten Klasse Component abgeleitet. Komponenten<br />
können
9.2. GRAFISCHE BENUTZEROBERFLÄCHEN 147<br />
Abbildung 9.2.1: Peers sichern die Plattformunabhängigkeit (Abb. aus [MSS96]).<br />
• mittels setVisible sichtbar oder unsichtbar<br />
• mittels setEnabled selektierbar/nicht selektierbar (enable/disable)<br />
gemacht werden. Komponenten haben weitere Merkmale, z.B. x-y-Koordinaten. Komponenten<br />
können mit dem Benutzer interagieren.<br />
Eine gewisse Inkonsequenz besteht darin, dass nicht alle Methoden bei allen von Component<br />
abgeleiteten Klassen sinnvoll sind.<br />
Container sind Komponenten, die andere Komponenten enthalten können (ermöglicht<br />
die Hierarchiebildung!). Dazu enthalten Container einen Vektor von Komponenten. Container<br />
werden nicht direkt instantiiert.<br />
Windows (Klasse Window) und Panels (Klasse Panel) sind spezielle Container. Objekte<br />
der Klasse Window besitzen keinen Rahmen und keine Menüleiste. Panel-Objekte können als<br />
Zeichenelement genutzt werden (z.B. Platzierung eines Buttons auf einem Hintergrundbild).<br />
Die Klasse Frame repräsentiert einen Container, der ein Fenster mit Rahmen bildet.<br />
Elementare AWT-Komponenten (wie Button, Checkbox, TextArea usw. ) werden hier<br />
nicht besprochen. Häufig benötigte ”<br />
Radiobuttons” werden - etwas umständlich - mit Hilfe<br />
der Klassen CheckboxGroup realisiert.<br />
9.2.2 Die Layout-Manager<br />
Jeder Container verwaltet einen (Voreinstellung beachten!) Layout-Manager. Layout-Manager<br />
nehmen dem Anwender die Arbeit der absoluten Positionierung von Komponenten in Containern<br />
ab. Sie definieren Regeln, nach denen Komponenten in einem Container platziert<br />
werden. Komponenten können mit Containern mitwachsen.<br />
FlowLayout Die Anordnung der Elemente erfolgt nebeneinander in logischer Reihenfolge.<br />
Die Komponenten werden in mehreren Reihen dargestellt, falls der Container zu schmal ist,
9.2. GRAFISCHE BENUTZEROBERFLÄCHEN 148<br />
um die Komponenten in einer Zeile darzustellen.<br />
FlowLayout ist der voreingestellte Layout-Manager von Panel.<br />
BorderLayout Es gibt maximal 5 Komponenten: oben, links, mitte, rechts, unten. Beim<br />
Hinzufügen einer Komponente zum Container muss zusätzlich eine der Zeichenketten North,<br />
East, South, West oder Center als Parameter angegeben werden. Ein im Zentrum hinzugefügtes<br />
Panel nimmt den gesamten Raum des Containers ein, wenn keine weiteren Plätze<br />
belegt werden.<br />
CardLayout Anordnung der Komponenten in einem Stapel (ähnlich Spielkarten), nur die<br />
oberste Komponente ist sichtbar. Es gibt vier Methoden zum Blättern.<br />
GridLayout Anordnung der Komponenten in einem Gitter aus gleichgroßen Gitterzellen.<br />
Jede Komponente nimmt eine Gitterzelle ein. Die Breite bzw. Höhe der Gitterzellen richtet<br />
sich nach der breitesten bzw. höchsten Komponente. Gitterzellen werden von links oben nach<br />
rechts unten mit Komponenten aufgefüllt.<br />
GridBagLayout Ähnlich dem GridLayout, aber flexibler. Eine Komponente kann sich<br />
über einen Anzeigebereich von mehreren Gitterzellen erstrecken. Spalten/Zeilen können unterschiedlich<br />
breit/hoch sein. Die Einstellung der Eigenschaften des GridBagLayout erfolgt<br />
über eine Instanz der Klasse GridBagConstraints. Diese Klasse enthält keine Methoden,<br />
ihre Instanzvariablen müssen in der gewünschten Weise belegt werden.<br />
Die Methode setConstraints der Klasse GridBagLayout erzeugt eine Verknüpfung<br />
zwischen der hinzuzufügenden Komponente und der GridBagConstraints-<br />
Instanz:<br />
setConstraints( Component comp, GridBagConstraints const)<br />
9.2.3 Swing 1.1<br />
Swing ist eine API und eine Grafikbibliothek zum Programmieren von grafischen Benutzeroberflächen.<br />
Dokumentationen zu Swing sind unter folgenden Adressen zu finden:<br />
1. The Swing Connection<br />
http://java.sun.com/products/jfc/swingdoc-current/<br />
2. The Component Gallery<br />
http://java.sun.com/products/jfc/swingdoc-current/comp\ gal.html<br />
3. Using the JFC/Swing Components<br />
http://java.sun.com/docs/books/tutorial/ui/swing/index.html<br />
Swing-Komponenten sind von java.awt.container abgeleitet (vgl. Abb. 9.2.3).
9.2. GRAFISCHE BENUTZEROBERFLÄCHEN 149<br />
9.2.4 Entwurf eines Swing–GUI<br />
Jedes Programm, das ein Swing-GUI repräsentiert, enthält wenigstens einen Toplevel-Swing-<br />
Container, d.i. eine Instanz von<br />
• JFrame oder<br />
• JDialog oder<br />
• JApplet.<br />
MVC-Architektur für GUI-Komponenten<br />
Neu in Swing sind die Klassen ChangeEvent und ChangeListener zur Behandlung von<br />
Zustandsänderungen an Modellen. View und Controller sind in einem Objekt vereinigt, das<br />
Delegate heißt. Delegates<br />
• rendert das Modell (View-Aspekt) und<br />
• übertragen Nutzereingaben zum Modell (Controller-Aspekt).<br />
Eine JComponent hat<br />
• ein simple model und<br />
• ein simple delegate.<br />
Mögliche Models für eine spezielle JComponent sind Klassen, die ein für die Komponente spezifisches<br />
Model Interface implementieren. Mögliche Delegates für eine spezielle JComponent<br />
sind Klassen, die ein für die Komponente spezifisches Delegate Interface implementieren. Alle<br />
Delegates sind von ComponentUI abgeleitet.<br />
MVC-Architektur und GUI-Event Handling<br />
Die Verwendung der MVC-Architektur wird nachfolgend an einem Beispiel (Klasse SwingMVC)<br />
demonstriert. Das GUI der betrachteten Anwendung (vgl. Abb. 9.2.4) besitzt ein TextField<br />
zur (wiederholten) Eingabe einzelner Ganzzahl-Werte (links), die zu einer Liste hinzugefügt<br />
werden sollen. Ein TextArea dient zur Visualisierung aller eingegebenen Werte (rechts<br />
oben). Aus allen in der Liste eingetragenen Zahlen wird ein Mittelwert berechnet und in<br />
einem weiteren Textfeld (TextField) angezeigt (rechts unten in Abb. 9.2.4).<br />
Eine ”<br />
Liste” fungiert als Modell für zwei Views. Ein Objekt der Klasse IntVectorModel<br />
verwaltet dazu als Modell einen Vektor von Integer-Zahlen. Das TextArea ListView zur<br />
Visualisierung der Werte hat den Status ”<br />
nicht editierbar”. Zur Benachrichtigung wird das<br />
Event-Handling benutzt. Die Views (avgField und textList) fungieren als ChangeListener<br />
zum Modell (implementieren ein ChangeListener-Interface). Sie benutzen eine Methode stateChanged.<br />
Das zur Eingabe vorgesehene Textfeld fungiert als Controller, der Nutzereingaben in die Liste<br />
überträgt. Dies erfolgt mittels eines Action-Listeners.
9.2. GRAFISCHE BENUTZEROBERFLÄCHEN 150<br />
Motivation: Der Mittelwert kann nicht von der View-Liste berechnet werden. Problem:<br />
Falls die View-Liste abgeschaltet wird, kann kein Mittelwert mehr berechnet werden.<br />
Bevor mit der Implementierung begonnen wird, wird noch eine weitere Entwurfsüberlegung<br />
umgesetzt: Mit Hilfe von Adaptern werden Controller, Modell und Views noch voneinander<br />
entkoppelt. Im Konstruktor SwingMVC(...) der Anwendung werden drei Adapter-<br />
Objekte erzeugt, die die Funktion der Listener übernehmen (Abb. 9.2.5).<br />
Der Controller kennt nur den Adapter cm, das Modell kann gegebenenfalls geändert werden,<br />
ohne den Controller zu verändern. Das Modell kennt ebenfalls nur die Adapter mv1 und<br />
mv2, die Views können geändert werden, ohne das Modell anpassen zu müssen.<br />
Der Adapter mv1 wirkt als ChangeListener beim Modell, er kennt Modell und View.<br />
Wird sein Handler aktiviert, wirkt das als Benachrichtigung, dass sich das Modell geändert<br />
hat. Er holt mit der Methode getData() die relevanten Daten aus dem Modell ab und reicht<br />
sie an die View weiter.<br />
9.2.5 JTree - Anwendung<br />
Die Klasse JTree im Package java.swing.tree ist die Basisklasse für die Präsentation<br />
hierarchischer Strukturen. Die Klasse ist von JComponent abgeleitet und implementiert die<br />
Interfaces Scrollable und Accessible. Ein JTree-Objekt enthält selbst keine Daten. Daten<br />
werden über das Datenmodell der Komponente beschafft.<br />
Jede durch ein JTree-Objekt angezeigte Zeile präsentiert ein Datenelement, d.h. einen<br />
Knoten des Baumes. Es werden Blattknoten (die keine Nachfolger haben können) und innere<br />
Knoten (die Nachfolger haben können) unterschieden.<br />
Innere Knoten können ”<br />
expanded” oder ”<br />
collapsed” sein. Knoten vom Typ ”<br />
hidden” sind<br />
solche, die als Vorfahren einen ”<br />
collapsed” Knoten haben.<br />
Verwendete Interfaces:<br />
• TreeModel beschreibt das Datenmodell<br />
• TreeSelectionModel<br />
• TreeCellRenderer spezifiziert die Visualisierung von Knoten im Baum<br />
• TreeNode und MutableTreeNode beschreiben, was an jedem Knoten des Baumes dargestellt<br />
wird<br />
Beispiel. Eine Anwendung von JTree findet man in dem im Rahmen der Lehrveranstaltung<br />
<strong>Software</strong>-Qualitätssicherung” entwickelten Testwerkzeug Jamus:<br />
”<br />
i:\Prakt\fritzsch\QSM\Projekt<br />
Beispiel. Die mit Swing gelieferte Anwendung SampleTree demonstriert zusätzlich die dynamische<br />
Erweiterung hierarchischer Strukturen.
9.2. GRAFISCHE BENUTZEROBERFLÄCHEN 151<br />
9.2.6 SWT: Das Standard Widget Toolkit<br />
Das SWT gehört zu den Komponenten (Plug-ins) der Eclipse-Workbench und steht gleichzeitig<br />
für <strong>Software</strong>-Entwickler als API zur Verfügung. Die Widgets des SWT sind in org.eclipse.swt.widgets<br />
enthalten.<br />
Zum Kennenlernen des SWT werden innerhalb der Eclipse-Dokumentation ”<br />
Examples”<br />
beschrieben, die in der Eclipse-Workbench als lauffähige Anwendungen bereit gestellt werden<br />
können.<br />
Die Eclipse-Dokumentation (Helios) insgesamt ist unter http://help.eclipse.org/helios/index.jsp<br />
erreichbar.<br />
Die Beispiele für die Nutzung des SWT sind darin erreichbar über<br />
→ Platform Plug-in Developer Guide<br />
→ Examples Guide<br />
→ Standard Widget Toolkit<br />
→Using the SWT example launcher<br />
Zum Erproben der Examples gibt es einen SWT Example Launcher. Die SWT-Examples<br />
können ”<br />
Workbench Views”(in der aktuellen Eclipse-Perspektive geöffnet) sein oder ”<br />
standalone”-<br />
Applikationen (launched in separatem Fenster).<br />
Nach seiner Installation in Eclipse kann der Launcher gestartet werden:<br />
Window → Show View → Other. Weiter Im Dialog : → SWT-Examples → SWT<br />
Example Launcher. Bei Auswahl wird der Launcher als Eclipse-View geöffnet. Über den<br />
gezeigten Explorer können Beispiele ausgewählt und mittels des Buttons ”<br />
run” gestartet<br />
werden. Die Ausführung des ”<br />
standalone”-Examples ”<br />
Hello World (5)” zum Beispiel öffnet<br />
ein Frame (vgl. Abb. 9.2.6).<br />
Das SWT ist (im Unterschied zu JFace) ein ”<br />
low level”-Toolkit. Den Kern von SWT<br />
bilden Widgets, Layouts und Events.<br />
Eine typische ”<br />
stand alone”-Applikation mit SWT umfasst:<br />
• Erzeugen eines ”<br />
Display”-Objekts zur Repräsentation einer Session,<br />
• Erzeugen einer oder mehrerer ”<br />
Shells”, die das oder die Hauptfenster der Applikation<br />
repräsentieren,<br />
• Erzeugen weiterer ”<br />
Widgets”, die innerhalb der Shell verwendet werden,<br />
• Initialisieren der Zustände der Widgets (z.B. der Größe) und Registrieren der benötigten<br />
Event-Listener,<br />
• Öffnen der Shell,<br />
• Behandlung von Events bis zum Auftreten einer erfüllten Abbruchbedingung und<br />
Schließen des Shell-Fensters,<br />
• Beenden der ”<br />
Display”-Session.
9.2. GRAFISCHE BENUTZEROBERFLÄCHEN 152<br />
Display Die Klasse Display (org.eclipse.swt.widgets.Display) bildet die Verbindung zwischen<br />
SWT und dem GUI-System der Plattform. Displays dienen dem Management der<br />
Event-Loop”und steuern die Kommunikation zwischen dem UI-Thread und anderen Threads.<br />
”<br />
Ein Display ist vor einem Window (Shell) zu erzeugen und am Ende nach Entfernen der Shell<br />
zu schließen. Displays sind für Multi-Thread-Anwendungen wichtig.<br />
Shell Ein Shell-Objekt ist ein Fenster (top level shell oder dialog shell) einer Applikation<br />
und wird vom Window-Manager der Betriebssystem-Plattform organisiert. top level shells<br />
werden als Kind-Objekte von Display erzeugt. These windows are the windows that users<br />
move, resize, minimize, and maximize while using the application. Secondary shells are those<br />
that are created as a child of another shell. These windows are typically used as dialog<br />
windows or other transient windows that only exist in the context of another window.<br />
Beispiel. ”<br />
Hello, World.”-Programm mit SWT.<br />
Um das Beispiel in Eclipse ausführen zu können, gibt es zwei Möglichkeiten:<br />
1. Die Ausführung als Eclipse-Projekt, oder<br />
2. die Ausführung als Java-Applikation. In diesem Fall muss zuvor das Paket org.eclipse.swt.widgets<br />
verfügbar gemacht werden.<br />
Beispiel.
9.2. GRAFISCHE BENUTZEROBERFLÄCHEN 153<br />
Abbildung 9.2.2: Muster ”<br />
Kompositum” zur Beschreibung von Komponenten einer grafischen<br />
Benutzeroberfläche
9.2. GRAFISCHE BENUTZEROBERFLÄCHEN 154<br />
Abbildung 9.2.3: Abbildung aus Wikipedia<br />
Abbildung 9.2.4: Swing-Applikation mit MVC-Architektur
9.2. GRAFISCHE BENUTZEROBERFLÄCHEN 155<br />
Abbildung 9.2.5: Erzeugung und Registrierung der Adapter<br />
Abbildung 9.2.6: Beispiele im ”<br />
Example Launcher”<br />
Abbildung 9.2.7: ”<br />
Hello, World.” mit SWT
9.2. GRAFISCHE BENUTZEROBERFLÄCHEN 156<br />
Abbildung 9.2.8: Einstellung der Umgebungsvariablen ECLIPSE HOME
9.2. GRAFISCHE BENUTZEROBERFLÄCHEN 157<br />
9.2.7 Einbettung von Swing-Komponenten in SWT<br />
Die Eclipse-IDE basiert wie gesagt auf SWT. Soll nun eine bereits existierende Applikation (in<br />
der Regel ein Entwicklungswerkzeug, z.B. das Testwerkzeug Jamus) mit einer AWT/Swingbasierten<br />
GUI in der Eclipse-IDE zur Verfügung gestellt werden, ist dies auf zwei Wegen<br />
möglich:<br />
1. kann die Swing-basierte Applikation umgeschrieben werden oder<br />
2. kann die Swing-Applikation in SWT eingebettet werden.<br />
Beispiel: Integration von Jamus in die Eclipse-IDE<br />
Hier wird nachfolgend die Einbettung von Swing-Komponenten am Beispiel von Jamus betrachtet.<br />
Um etwas (Jamus) einbetten zu können, muss zunächst eine ”<br />
Hülle” geschaffen<br />
werden. Dazu wird ein neues Jamus-Projekt als PDE-Projekt (Plug-in Development Environment)<br />
geschaffen. Alle Pakete unter de.htw.dresden.jamus werden neu geschaffen.<br />
Für die Enbettung ist ein spezielles Package swingintegration.example 0.0.2.jar nutzbar,<br />
das über die Eclipse-Homepage erreichbar ist<br />
(http://www.eclipse.org/articles/Article-Swing-SWT-Integration/files/swingintegration.example 0.0.2.jar)<br />
Das Package ist zunächst in die eigene Anwendung - das PDE-Projekt - zu übernehmen.<br />
Dazu werden alle Klassen dieses Pakets in das Paket de.htw.dresden.jamus.embeddedSwing<br />
übertragen, anschließend müssen die Quelltexte individuell angepasst werden.<br />
Die Klasse EmbeddedSwingComposite ist die Basis für die Einbettung von AWT/Swing-<br />
Komponenten in die SWT-basierte Oberfläche (vgl. Abb. 9.2.9).<br />
Folgende Anpassungen sollen vorgenommen werden:<br />
1. Jamus-Perspektive schaffen<br />
2. Integration der Jamus-Menüs<br />
3. Integration von Views/Editoren in die Eclipse-IDE<br />
4. Frames bleiben Frames<br />
Zu 1.: Jamus-Perspektive schaffen<br />
Die Jamus-Perspektive soll eigene Views beinhalten. Die Perspektive wird der Workbench<br />
über den Extension Point org.eclipse.ui.perspectives bekannt gegeben. Abb. 9.2.10 zeigt die in<br />
der Datei plugin.xml verwaltete XML-Darstellung. Zur Erstellung der Datei in der Eclipse-<br />
IDE kann ein Wizard verwendet werden.<br />
Das Attribut id bestimmt einen eindeutigen Identifikator zur Referenzierung der Perspektive.<br />
Der Wert des Attributs name ist ein Text-Label für die Perspektive. Falls fixed="true"<br />
gesetzt ist, können zur Perspektive gehörige Views nicht geschlossen werden. Die im Attribut
9.2. GRAFISCHE BENUTZEROBERFLÄCHEN 158<br />
Abbildung 9.2.9: Paketstruktur des Jamus-Plug-in-Projekts<br />
class angegebene Klasse beschreibt das Layout der Perspektive. Die Klasse wird durch einen<br />
parameterlosen Konstruktoraufruf instantiiert.<br />
Die Klasse Perspective (vgl. Abb. 9.2.11) muss org.eclipse.ui.IPerspectiveFactory<br />
implementieren. Die Methode CreateInitialLayout ist die im Interface definierte und in<br />
Perspective überschriebene Methode, mit der der Perspektive verschiedene Ansichten (z.B.<br />
Projekt Explorer, Editor) hinzugefügt und das Layout für die Perspektive konfiguriert werden<br />
können. Per Default beinhaltet der Layout-Bereich Platz für Editoren, aber nicht für<br />
Views. Die Factory-Methode kann weitere Views hinzufügen, die relativ zum Editor oder zu<br />
anderen View platziert werden.<br />
Abbildung 9.2.12 zeigt die Eclipse-IDE bei geöffneter Jamus-Perspektive.<br />
Zu 2.: Integration der Jamus-Menüs
9.2. GRAFISCHE BENUTZEROBERFLÄCHEN 159<br />
Abbildung 9.2.10: Extension Jamus-Perspektive<br />
Abbildung 9.2.11: Klasse Perspective
9.2. GRAFISCHE BENUTZEROBERFLÄCHEN 160<br />
Abbildung 9.2.12: Eclipse-IDE in der Jamus-Perspektive<br />
Abbildung 9.2.13: Beispiel zur Einbettung
9.2. GRAFISCHE BENUTZEROBERFLÄCHEN 161<br />
9.2.8 JFace<br />
JFace ist ein ”<br />
higher level” UI-Toolkit. Darin werden aus den von SWT zur Verfügung gestellten<br />
Basiskomponenten komplexere Widgets zusammengesetzt. Es wird eine Abstraktionsschicht<br />
für den Zugriff auf die Komponenten bereitgestellt.<br />
JFace besitzt Abhängigkeiten zu einigen Eclipse-Bibliotheken (die ggf. als .jar-Dateien aus<br />
Eclipse verfügbar gemacht werden müssen). Eclipse ist eine Applikation, die JFace einsetzt.
9.3. CLIENT-SERVER-SYSTEME MITTELS HTTP UND SERVLETS 162<br />
9.3 Client-Server-Systeme mittels http und Servlets<br />
Servlets sind spezielle Java-Klassen, die Code enthalten, der in einer Web-Anwendung serverseitig<br />
unter Kontrolle eines Servers ausgeführt werden kann. Servlets können mit Web-<br />
Clients mittels eines Request/Response–Paradigmas interagieren, basierend auf dem HTTP-<br />
Protokoll. Java-Servlets sind für Server, was Java-Applets für Clients sind.<br />
Java-Servlets können zur dynamischen Erzeugung von Web-Inhalten (alternativ zu CGI-<br />
Skripten, PHP etc.) eingesetzt werden. Aktuell ist die Spezifikation Java Servlet 3.0.<br />
Der Servlet-Lebenszyklus<br />
Servlets werden durch eine spezielle Laufzeitumgebung - einen Servlet-Container bzw. Web-<br />
Container - als Instanz der Servlet-Klasse erzeugt und zur Abarbeitung gebracht. Ein verbreitet<br />
eingesetzter Web-Container ist Apache Tomcat. Die Ausführung unterliegt - ähnlich<br />
wie die Ausführung von Applets - einem speziellen Lebenszyklus. Der Web-Container ist für<br />
das Laden und Initialisieren zuständig. Servlets akzeptieren Requests von Clients und liefern<br />
Daten an sie zurück. Schließlich können Web-Container Servlets entfernen.<br />
Im einzelnen bedeutet das:<br />
1. Die Laufzeitumgebung aktiviert die Methode init des Servlets. Ein Neuladen ist erst<br />
nach Entfernen des Servlets möglich. Dazu muss der Server die Methode destroy<br />
ausführen.<br />
2. Nach dem Initialisieren ist ein Servlet in der Lage, von Clients gelieferte Daten zu verarbeiten<br />
(request) und Daten an sie zurückzuliefern (response). Die Behandlung von<br />
Client-Requests erfolgt mit der Methode service des Servlets. Jeder Client–Request<br />
bewirkt, dass die service-Methode in einem eigenen Thread läuft. Für die Organisation<br />
der gleichzeitigen Verarbeitung mehrerer service-Methoden ist der Nutzer zuständig.<br />
Programmierung eines Servlets<br />
Servlets implementieren das Interface javax.servlet.Servlet.<br />
Es gibt 2 Möglichkeiten:<br />
1. Entwickler können das Interface direkt implementieren.<br />
2. Servlets, die das http-Protokoll benutzen, um mit Clienten zu interagieren, spezialisieren<br />
zweckmäßigerweise die Klasse javax.servlet.http.HttpServlet und nutzen sie<br />
auf diese Weise nach. Die Klasse HttpServlet implementiert ihrerseits das Interface<br />
javax.servlet.Servlet.<br />
Die Methode service leitet Requests an eine in der Servlet-Klasse definierte Methode<br />
doXXX weiter, sie muss vom Anwender in der Klasse HttpServlet nicht überschrieben werden.<br />
Für das Interagieren mit Clients ist es notwendig, die erwünschten Methoden doXXX<br />
für die Behandlung von http-Interaktionen der Klasse HttpServlet zu überschreiben.
9.3. CLIENT-SERVER-SYSTEME MITTELS HTTP UND SERVLETS 163<br />
Kandidaten dafür sind:<br />
• doGet - Behandlung von GET-Requests, bedingten GET-Requests, HEAD-Requests<br />
• doPost - Behandlung von POST-Requests<br />
• doPut - Behandlung von PUT-Requests<br />
• doDelete - Behandlung von DELETE-Requests<br />
Alle Methoden, die überschrieben werden, erwarten zwei Parameter:<br />
1. Instanz von HttpServletRequest für Daten vom Client<br />
2. Instanz von HttpServletResponse für Antwort an den Client<br />
Eine HttpServletRequest-Instanz ermöglicht den Zugriff auf die Http-Header-Daten. Ein<br />
Http-Header enthält Informationen über den Körper, wie z.B. Kodierung oder Inhaltstyp,<br />
die Http-Methode, mit der der Request ausgeführt wurde sowie weitere Angaben (Browser,<br />
Sprache, Cookies). Für den Zugriff auf diese Daten (Requests) gibt es folgende Möglichkeiten:<br />
• für alle HTTP-Methoden:<br />
– getParameterValues liefert den Wert eines Parameters<br />
– getParameterNames liefert die Namen der Parameter<br />
• speziell für GET (HTTP):<br />
– getQueryString liefert einen String, der ggf. zu parsen ist.<br />
• für POST, PUT, DELETE:<br />
– getReader liefert einen BufferedReader, der zum Lesen von Text-Daten verwendet<br />
werden kann,<br />
– getInputStream liefert einen ServletInputStream, der zum Lesen von binär-<br />
Daten verwendet werdeen kann.<br />
Für die Rückgabe von Antworten bietet ein HttpServletResponse-Objekt folgende Möglichkeiten:<br />
• getWriter liefert einen Writer für die Ausgabe von Textdaten<br />
• getOutputStream liefert einen Output Stream für die Ausgabe von Binaries<br />
Vor der Ausgabe müssen die Header-Daten gesetzt werden. Dazu werden die Methoden<br />
contentType, contentLength und encoding benutzt.<br />
Nach der Übertragung signalisiert das Schließen des Writers oder des Output Streams<br />
dem dem Server, dass die Übertragung beendet ist.
9.3. CLIENT-SERVER-SYSTEME MITTELS HTTP UND SERVLETS 164<br />
Servlet-Metadaten<br />
Metainformationen über das Servlet werden in einer XML-Datei web.xml gespeichert (sog.<br />
Deployment Deskriptor). Die XML-Datei wird zusammen mit der compilierten Klasse gespeichert.<br />
Die Nutzung des Servlet-Runners aus dem Java Servlet Development Kit erfordert das<br />
Anlegen einer Property-Datei servlet.properties, sie enthält den Namen des Servlets und<br />
die Initialisierungsparameter.<br />
Kommunikation zwischen Client und Servlet<br />
Vom Client aus kann die Kommunikation mit einem Servlet auf folgenden Wegen erfolgen:<br />
• direkt über die URL des Browsers,<br />
• mittels eines HTML-Formulars,<br />
• mittels eines Applets, das in einem HTML-Dokument refereziert wird.<br />
Beispiel. Das in der Server2Go-Applikation verwendete Servlet MyServlet:<br />
...<br />
...<br />
...<br />
Die Methode doMysql wird ebenfalls in der Klasse MyServlet definiert und realisiert eine<br />
Datenbankabfrage. Der Methode wird der PrintWriter übergeben, über den in der Methode<br />
Ausgaben erfolgen.
9.3. CLIENT-SERVER-SYSTEME MITTELS HTTP UND SERVLETS 165<br />
Beispiel. Ein Applet Countis baut eine Verbindung zum Servlet auf, schreibt und liest. Es<br />
gibt verschiedene Konstruktoren für URL-Objekte. Der Host-Name kann durch getCodeBase().getHost()<br />
ermittelt werden.<br />
public class Countis extends Applet {<br />
public void init(){<br />
URL url = new URL(...);<br />
URLConnection connection = url.openConnection();<br />
OutputStream out = connection.getOutputStream;<br />
PrintStream pout = new Printstream(out);<br />
InputStream in = connection.getInputStream();<br />
}<br />
}
9.4. DATENBANKANBINDUNGEN AN JAVA-APPLIKATIONEN 166<br />
9.4 Datenbankanbindungen an Java-Applikationen<br />
Ein Client-Programm, das auf eine Datenbank zugreifen soll, enthält folgende Funktionen:<br />
1. Laden eines JDBC-Treibers<br />
2. Öffnen einer Datenbank (Verbindung aufbauen)<br />
3. Senden von SQL-Anweisungen an die Datenbank<br />
4. Auslesen und Bearbeiten der Ergebnisse<br />
Die Schritte 1. und 2. sind einmal in einer Anwendung auszuführen, die Schritte 3 und 4<br />
werden wiederholt ausgeführt.<br />
Beispiel:<br />
1. JDBC-Treiber laden<br />
Class.forName("com.mysql.jdbc.Driver");<br />
Ein JDBC-Treiber ist eine gewöhnliche Java-Klasse, die aber erst zur Laufzeit mit<br />
Class.forName(klassenname) bereitgestellt wird. Class.forName(klassenname) gibt zu<br />
einem ihr als Parameter übergebenen Klassennamen ein Class-Objekt zurück und lädt zuvor<br />
die Klasse in die JVM.<br />
com.mysql.jdbc.Driver implementiert com.sql.Driver. Um den Treiber benutzen<br />
zu können, muss das mit der Java-Distribution bereitgestellte Archiv<br />
mysql-connector-java-[version]-bin.jar verfügbar gemacht werden. Die vollständige<br />
Adresse muss in den CLASSPATH aufgenommen werden.<br />
2. Datenbankverbindung aufbauen.<br />
Der Treiber-Manager prüft mittels<br />
Connection con = DriverManager.getConnection(...)<br />
welcher der geladenen Treiber den als Parameter angegebenen JDBC-URL öffnen kann. Die<br />
Datenbank wird mit der Methode getConnection geöffnet. Die Methode erwartet ein, zwei<br />
oder drei Parameter. Drei Parameter haben folgende Bedeutung:<br />
(a) die Datenbank, angegeben als url-String der Form jdbc : subprotocol : subname.<br />
Beispiel: String url = "jdbc:mysql://localhost:7188/server2go";<br />
(b) den Benutzernamen, angegeben als String,<br />
Beispiel: "root"<br />
(c) das zugehörige Passwort, angegeben als String.<br />
Beispiel: ""<br />
b) und c) zusammen erlauben den DB-Zugriff.<br />
Die Methode getconnection(...) gibt ein Connection-Objekt zurück.
9.4. DATENBANKANBINDUNGEN AN JAVA-APPLIKATIONEN 167<br />
3. SQL-Anweisung an die DB senden.<br />
Über die zuvor geöffnete DB-Verbindung sendet der Client SQL-Anweisungen an das<br />
DBMS:<br />
(a) Erzeugen eines Statement-Objektes, um SQL-Statements an die Datenbank zu<br />
senden:<br />
Statement stmt = con.createStatement();<br />
(b) Übermitteln des SFW-Blockes in der Methode executeQuery:<br />
String query =<br />
"SELECT *" +<br />
"FROM items";<br />
ResultSet rs = stmt.executeQuery(query);<br />
(c) Automatische Übermittlung des Statements an das DBMS mittels JDBC-Treiber.<br />
4. Auslesen und Bearbeiten der Ergebnisse<br />
Die SFW-Anweisung liefert eine Tabelle als Instanz der Klasse ResultSet. Die Methoden<br />
von ResultSet dienen dem Durchlaufen der Tabelle und dem Zugriff auf Ergebnisse:<br />
Zeilenweise von links nach rechts (bzw. von vorn nach hinten) .<br />
Die Tabelle wird zweckmäßigerweise als HTML-Datei ausgegeben. In dem Fall kümmert<br />
sich der Web-Browser um die Formatierung.<br />
Der Zugriff auf Metadaten ermöglicht z.B. die Ausgabe von Attribut- bzw. Spaltenzahl<br />
und -bezeichnungen:<br />
ResultSetMetaData rsmd = rs.getMetaData();<br />
int colums = rsmd.getColumnCount();<br />
System.out.println(""<br />
for(int i=1;i
9.5. SERVER2GO 168<br />
9.5 Server2Go<br />
Server2Go (Homepage http://www.server2go-web.de) ist ein Web-Server, der ohne Installation<br />
arbeitsfähig ist (Autor: Timo Haberkern). Auf Server2Go basierende Web-<br />
Applikationen sind direkt vom Datenträger nutzbar, ohne Apache, PHP oder MySQL konfigurieren<br />
zu müssen. Ein Tomcat bearbeitet Anfragen an Servlets oder JSP. Die Verarbeitung<br />
von Java-Servlets ist im Funktionsumfang von Server2Go nicht enthalten, dazu muss der Server2Go<br />
speziell eingerichtet werden.<br />
Beispiel. Für die Lehrveranstaltung wurde eine Beispielkonfiguration zusammengestellt<br />
(Dank an J. Roeper!). Eine entsprechend bereitgestellte zip-Datei ist zunächst an einen<br />
gewünschten Ort zu entpacken. Es entsteht ein Verzeichnis server2go. Bei Ausführung der<br />
darin enthaltenen Server2Go.exe wird die Anwendung gestartet. Es öffnet sich der MS<br />
Internet Explorer mit der Adresse http://127.0.0.1:4001/. Bei Schließen des Internet<br />
Explorers wird der Server2Go wieder heruntergefahren.<br />
Wir nutzen nach dem Starten eine vorgefertigte Servlet-Einbindung: Klick auf ”<br />
Servlet<br />
mit Datenbankabfrage” unter den rechts platzierten Angeboten. Es öffnet sich im Explorer<br />
http://127.0.0.1:4001/mywebapp/MyServlet und zeigt einen einfachen HTML-Text:<br />
Auf Seiten des Clients steht ein Link im HTML-Text:
9.5. SERVER2GO 169<br />
Server mit DB-Abfrage<br />
Das Servlet MyServlet.class (ebenso MyServlet.java) befindet sich im Unterverzeichnis<br />
htdocs/webapps/mywebapp/Web-INF/classes.<br />
Beispiel. Die im Beispiel benötigte Datenbank items kann zuvor mittels phpmyadmin erstellt<br />
werden.<br />
Abbildung 9.5.1: Bearbeiten der verwendeten Datenbank mittels phpMyAdmin
9.5. SERVER2GO 170<br />
Abbildung 9.5.2: Darstellung der Tabelle items in der server2go-Datenbank
9.5. SERVER2GO 171<br />
Beispiel. Sächsische Historische Biographie (BioLex)<br />
Es wird ein Client-Server-Applikation entwickelt, die sich Client-seitig auf ein Applet<br />
stützt.
9.5. SERVER2GO 172
Literaturverzeichnis<br />
[Bal05]<br />
[BES08]<br />
Balzert, Heide: Lehrbuch der Objektmodellierung - Analyse und Entwurf mit<br />
der UML 2. 2. Auflage. Heidelberg : Spektrum Akademischer Verlag, 2005<br />
Balzert, H. ; Ebert, Ch. ; Spindler, G.: Lehrbuch der <strong>Software</strong>technik: <strong>Software</strong>management.<br />
2. Auflage. Spektrum Akademischer Verlag, 2008<br />
[Dum03] Dumke, Reiner: <strong>Software</strong> <strong>Engineering</strong>. 4. Auflage. Vieweg Verlag, 2003<br />
[Ecl]<br />
www.eclipse.org/juno<br />
[GHJV09] Gamma, E. ; Helm, R. ; Johnson, R. ; Vlissides, J.: Entwurfsmuster - Elemente<br />
wiederverwendbarer objektorientierter <strong>Software</strong>. Addison-Wesley, 2009<br />
[HGE]<br />
[Lar05]<br />
[Mey90]<br />
[MSS96]<br />
[Obj]<br />
[O’S]<br />
[Pop08]<br />
MercurialEclipse. http:javaforge.com/project/HGE<br />
Larman, Craig: UML 2 und Patterns angewendet - Objektorientierte <strong>Software</strong>entwicklung.<br />
1. Auflage. Bonn : mitp-Verlag, 2005<br />
Meyer, B.: Objektorientierte <strong>Software</strong>entwicklung. New York etc. : Coedition<br />
Carl Hanser Verlag u. Prentice-Hall, 1990<br />
Mittendorf, S. ; Singer, R. ; Strobel, R.: Java Programmierhandbuch und<br />
Referenz. Dpunkt.verlag, 1996<br />
http://www.microtool.de/objectif/de/index.asp?gclid=<br />
CN6ynL6r0rkCFQdb3godwUMAUA<br />
O’Sullivan, B.: Mercurial: The Definite Guide. http://hgbook.red-bean.com.<br />
– Homepage des gleichnamigen Buches<br />
Popp, Gunther: Konfigurationsmanagement - mit Subversion, Ant und Maven:<br />
Grundlagen für <strong>Software</strong>architekten und Entwickler. 2., aktualisierte Auflage.<br />
dpunkt.verlag, 2008<br />
[Pre97] Pree, W.: Komponentenbasierte <strong>Software</strong>entwicklung mit Frameworks.<br />
dpunkt.verlag, 1997<br />
[Rac] DrRacket. http://racket-lang.org, Abruf: 2013-09-17<br />
173
LITERATURVERZEICHNIS 174<br />
[Som07] Sommerville, I.: <strong>Software</strong> <strong>Engineering</strong>. 8.,aktualisierte Auflage. Addison-<br />
Wesley, 2007<br />
[SWI] SWI-Prolog. http://www.swi-prolog.org, Abruf: 2013-12-06<br />
[Tor] TortoiseSVN. http://tortoisesvn.tigris.org/, Abruf: 2013-12-06
Index<br />
A<br />
Abstraktion, 29<br />
Actor, 129<br />
Adapter-Muster, 143<br />
Aggregation, 35<br />
Aggregationsbaum, 36<br />
Aktion, 46<br />
Ant, 17, 105<br />
Ant-Skript, 106<br />
Apache Tomcat, 162<br />
Assembler, 7<br />
Assoziation, 32<br />
Attribut, 29<br />
Ausführungsumgebung, 93<br />
AWT, 146<br />
B<br />
Beobachter-Muster, 139<br />
Blackbox-Framework, 128<br />
Build-Management, 17<br />
Build-Prozess, 7<br />
Build-Ziele, 106<br />
C<br />
Case-Tools, 8<br />
Changeset, 99<br />
Check-in, 97<br />
Classifier, 80<br />
Compiler, 7<br />
Concurrent Versions System, 97<br />
CVS, 97<br />
D<br />
Debugger, 7<br />
Delegate, 149<br />
Doclet, 122<br />
doGet, 163<br />
doMysql, 164<br />
doPost, 163<br />
E<br />
Eclipse, 8<br />
Einschubmethode, 127<br />
Einzelwerkzeug, 7<br />
EmbeddedSwingComposite, 157<br />
Entwurfsmuster, 139<br />
Ereignis, 46<br />
Erweiterbarkeit, 21<br />
evolutionäre Entwicklung, 19<br />
F<br />
Fabrikmethode-Muster, 139<br />
Fassade-Muster, 142<br />
Feature, 9<br />
Filesets, 109<br />
Framework, 127<br />
G<br />
Gerät, 93<br />
H<br />
Handler, 69<br />
Head, 99<br />
Hot-Spot, 127<br />
Http-Header, 163<br />
HttpServlet, 162<br />
I<br />
ID, 99<br />
Initialzustand, 46<br />
Inklusionsrelation, 36<br />
Instanziierung, 29<br />
175
INDEX 176<br />
Instanzobjekt, 30<br />
Interface, 63<br />
J<br />
Jamus, 157<br />
Jar-Archiv, 111<br />
Jar-Funktion, 110<br />
Javac-Funktion, 110<br />
Javadoc, 122<br />
Javadoc-Kommentar, 122<br />
Javadoc-Tag, 122<br />
JDBC-Treiber, 166<br />
JFace, 8, 151, 161<br />
JFC, 146<br />
JRE, 9<br />
JTree, 150<br />
K<br />
Klassendiagramm, 30<br />
Klassenheterarchie, 37<br />
Klassenhierarchie, 37<br />
Klassenname, 30<br />
Klonen, 99<br />
KM-Prozess, 96<br />
Knoten, 93<br />
Kommunikationspfade, 94<br />
Komposition, 36<br />
Konfigurationselemente, 96<br />
Korrektheit, 21<br />
L<br />
Layout-Manager, 147<br />
Listener, 69<br />
M<br />
make, 105<br />
Maven, 17, 105<br />
MERCURIAL, 16<br />
Mercurial, 99<br />
Merge, 100<br />
Methode, 28, 29<br />
Motif, 146<br />
Multiplizität, 34<br />
MVC-Architektur, 145<br />
O<br />
ObjectIF, 8<br />
Objektdiagramm, 30<br />
Objekttechnologie, 5<br />
P<br />
package, 80<br />
Paket, 80<br />
Paketdiagramm, 80<br />
PDE, 9<br />
Peer, 146<br />
Perspective, 158<br />
Perspektiven, 8<br />
Plug-ins, 8<br />
private, 31<br />
Produkt, 110<br />
Programmierumgebung, 7<br />
Programmverbinder, 7<br />
protected, 31<br />
Proxy-Muster, 141<br />
Prozessmodell, 19<br />
public, 31<br />
Pull, 100<br />
Q<br />
QSM, 21<br />
R<br />
Relation, 36<br />
Repository, 97<br />
Revision, 97<br />
Robustheit, 21<br />
Rolle, 34<br />
S<br />
SADT, 6<br />
Schablonenmethode-Muster, 143<br />
Sequenzendiagramm, 44<br />
Server2Go, 168<br />
Servlet, 162<br />
Servlet-Lebenszyklus, 162<br />
Servlet-Runner, 164
INDEX 177<br />
SFW-Anweisung, 167<br />
Sichtbarkeit, 31<br />
Sichtbarkeitsbereich, 44<br />
Simulationsframework, 129<br />
Singleton-Muster, 141<br />
Smalltalk, 145<br />
<strong>Software</strong>entwicklungsprozess, 18<br />
<strong>Software</strong>entwicklungsumgebung, 7<br />
Strategie-Muster, 140<br />
Structured Analysis, 6<br />
subclipse, 16, 98<br />
Subklasse, 37<br />
Subversion, 97<br />
Superklasse, 37<br />
svn, 16<br />
Swing, 148<br />
SWT, 8, 151, 157<br />
Whitebox-Framework, 127<br />
Wiederverwendbarkeit, 21<br />
X<br />
XMI, 16<br />
Z<br />
Zustand, 29, 46<br />
T<br />
Target, 106<br />
Task, 110<br />
Testdatengeneratoren, 7<br />
Thread, 77, 146<br />
Tip, 99<br />
tools, 4<br />
Topcased, 8<br />
Transition, 46<br />
Typkompatibilität, 44<br />
U<br />
Übersetzungseinheit, 56<br />
UML, 6, 30<br />
Unified Modeling Language, 5<br />
URL-Objekt, 165<br />
V<br />
Verifikation, 22<br />
Verknüpfung, 32<br />
Version, 97<br />
View, 8<br />
Vorgehensmodell, 19<br />
W<br />
Wasserfallmodell, 19