30.01.2014 Aufrufe

Software Engineering I/II - Fakultät Informatik/Mathematik

Software Engineering I/II - Fakultät Informatik/Mathematik

Software Engineering I/II - Fakultät Informatik/Mathematik

MEHR ANZEIGEN
WENIGER ANZEIGEN

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

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!