Example 1 User Requirements GERICHTETER GEWICHTETER GRAPH – DESIGNDOKUMENT Softwareentwicklung Praktikum, Übungsbeispiel 1 Gruppe 18 Andreas Hechenblaickner [0430217] Daniela Kejzar [0310129] Andreas Maller [0431289] Gruppe 18 Seite 1 User Requirements 1 User Requirements 1.1 Aufgabenstellung Example 1 Es soll ein gerichteter, gewichteter Graph erstellt werden, welcher aus Knoten (nodes) und Kanten (edges) besteht. Hierbei sollten Knoten mit anderen Knoten durch Kanten verbunden werden. Die Datenstruktur sollte verschiedene Funktionalitäten, wie zB das Einfügen und Löschen von Knoten und Kanten, aufweisen. 1.2 Anforderungen Es ist eine geeignete Datenstruktur zu entwickeln, die es ermöglicht die gewünschten Funktionalitäten zu erfüllen. Das Testprogramm sollte diese Funktionalitäten zeigen können. Ein Input-Handler soll die eingegebenen Kommandos verarbeiten und die dazu entsprechenden Operationen am Graphen durchführen. Das Programm muss sinnvoll objektorientiert aufgebaut sein. Seite 2 Gruppe 18 Example 1 Architectural Design 2 Architectural Design 2.1 Module Das Programm wird in folgende Module aufgeteilt: Name Beschreibung Seite Modul Graph Beschreibt die interne Speicherung eines gerichteten Graphen. Stellt Klassen mit Methoden zur Manipulation der im Graphen gespeicherten Knoten und Kanten zur Verfügung. 4 Modul Input-Handler Verarbeitet eingegebene Kommandos und führt die entsprechenden Befehle aus. 7 Modul Testprogramm Leitet eingegeben Kommandos an den Input-Handler weiter und gibt Fehlermeldungen entsprechend an den Benutzer weiter. 9 Gruppe 18 Seite 3 Modul Graph Example 1 3 Modul Graph 3.1 Beschreibung Ein gerichteter gewichteter Graph besteht aus beliebig vielen Knoten die untereinander durch beliebig viele Kanten verbunden sein können. Jede Kante hat einen Start- und einen Zielknoten. Start- und Zielknoten können auch gleich sein – in diesem Fall zeigt ein Knoten auf sich selbst. Ein Graph, jeder Knoten und jede Kante haben einen Namen. Es dürfen nicht zwei Knoten mit demselben Namen existieren. Es dürfen weder zwei Kanten mit selbem Namen, zwischen zwei Knoten existieren, noch dürfen zwei Kanten mit demselben Namen denselben Knoten als Start- oder Zielknoten haben. Es jedoch erlaubt, dass mehrere Kanten denselben Namen besitzen, vorausgesetz sie teilen sich weder Start- noch Zielknonten. Jede Kante speichert zusätzlich noch eine positive Ganzzahl als Gewichtung. Ein neuer Graph enthält anfangs weder Knoten noch Kanten. 1 bd : ce: 1 ad: 1 Abbildung 1: Beispiel eines Graphen 3.2 Bereitgestellte Klassen Graph, Node und Edge Die Klasse Graph speichert einen gerichteten, gewichteten Graphen. Alle möglichen Operationen wie zB das Einfügen von Knoten und Kanten werden über public-Methoden bereitgestellt. Die interne Speicherung der Zuordnung von Kanten und Knoten wird im nächsten Abschnitt Verwendete Datenstruktur beschrieben. Seite 4 Gruppe 18 Example 1 Modul Graph Displayable Alle anzeigbaren Objekte haben die gemeinsame abstrakte Superklasse Displayable, die eine Implementierung der virtuellen Methode show erfordert. In dieser Methode wird einfach die gewünschte Repräsentation der Klasse als String auf den Standard-Output geschrieben. Abbildung 2: Klassendiagramm des Moduls Graph 3.3 Verwendete Datenstruktur Die Knoten werden intern in einem assoziativen Speicher (Template Map aus der STL) mit dem Name des Knoten und den Knoten selbst als SchlüsselWerte-Paar gespeichert. Diese Datenstruktur wird verwendet da auf diese Weise einfach verhindert werden kann, das mehrere Knoten mit demselben Namen im Graphen existieren, und die häufige Operation „Knoten mit einem bestimmten Namen ermitteln“ kann ohne zeitaufwändiges durchlaufen aller Knoten durchgeführt werden. Die Kanten werden intern ebenfalls in Maps gespeichert. Es gibt jedoch im Graphen keine gesamte Liste, sondern jeder Knoten hält jeweils zwei Maps von Kanten. Die erste Map speichert alle von diesem Knoten ausgehenden Kanten, die zweite alle Kanten die diesen Knoten als Ziel haben. Jeder Knoten wird somit in genau zwei Listen gleichzeitig gehalten. Zusätzlich weiß jede Kante welche ihre Start- und Zielknoten sind. Durch diese Speicherung wird auch hier einerseits das schnelle Ansprechen einer bestimmten Kante über ihren Namen ermöglicht sowie das Einfügen von zwei gleichnamigen Kanten innerhalb eines Knotens verhindert. Für die korrekte An- und Abmeldung der Kanten bei den entsprechenden Knoten ist die Kante selbst verantwortlich. Wird ein Knoten gelöscht der von einer Kante als Start- oder Zielknoten verwendet wird, so muss auch diese Kante gelöscht werden. Wird eine Kante gelöscht, ist es notwendig die Verweise aus den Listen des Start- und des Zielknotens zu entfernen. Gruppe 18 Seite 5 Modul Graph Der Example 1 zuvor dargestellte gerichtete Graph wird daher folgendermaßen gespeichert: Graph Key Value "A" *Node A "B" *Node B "C" *Node C "D" *Node D "E" *Node E Node A Key Value "ab" *Edge ab "ad" *Edge ad Edge ab Source Edges Source *Node A Destination *Node B Edge ad Destination Edges Nodes Node B Key Value Key Value "bd" *Edge bd "ab" *Edge ab "bc" *Edge bc "cb" *Edge cb Source Edges Destination Edges Source *Node A Destination *Node D Edge bd Source *Node B Destination *Node D Node C Edge cb Key Value Key Value "cb" *Edge cb "bc" *Edge bc "ce" *Edge ce Source Edges Source *Node C Destination *Node B Destination Edges Edge ... Node ... Abbildung 3: Interne Speicherung eines Graphen 3.4 Fehlerbehandlung Zur Fehlerbehandlung werden folgende Exceptions zur Verfügung gestellt: Exception GraphException Seite 6 Beschreibung / Fehlermeldung Abstrakte Basisklasse für alle Fehlerfälle die in einem Graph auftreten können. NodeAlreadyExistsException error: node already exists EdgeAlreadyExistsException error: edge already exists in this context NoSuchNodeException error: no such node in graph NoSuchEdgeException error: no such edge Gruppe 18 Example 1 Modul Input-Handler 4 Modul Input-Handler 4.1 Beschreibung Aufgaben des Input-Handlers ist es eingelesene Kommandos zu verarbeiten und die entsprechenden Befehle mit ihren Parametern auszuführen. Ungültige Kommandos und Fehler beim Ausführen der Befehle, zB durch unvollständige/falsche Parameter, werden an das aufrufende Modul gemeldet. 4.2 Bereitgestellte Klassen InputHandler Die Klasse InputHandler verarbeitet eingelesene Kommandozeilen. Diese werden von außen (im Übungsbeispiel vom Modul Testprogramm) mittels der Methode handleInput als String übergeben. Der Input-Handler speichert eine Liste aller möglichen Befehle und deren zugehörige Objekte einer Command-Klasse (Ableitung der Klasse BaseCommand), welche in der virtuellen Methode executeCommand die für diesen Befehlt auszuführenden Anweisungen enthalten. Die Klasse InputHandler ist eine abstrakte Basisklasse, die selbst noch keine Befehle kennt, erst ein Objekt der Klasse GraphInputHandler kennt alle für einen Graph relevanten Befehle. Abbildung 4: Klassendiagramm des Input-Handlers Gruppe 18 Seite 7 Modul Input-Handler Example 1 GraphInputHandler Der GraphInputHandler stellt Befehle zur Manipulation eines Graphen zur Verfügung. Zur Konstruktion muss ein Graph-Objekt übergeben werden. Der GraphInputHandler kann folgende Befehle verarbeiten und auf den zugehörigen Graphen anwenden: Kommando add node <name>... 4.3 Beschreibung Neue(n) Knoten einfügen. add edge <src> <dst> <name> <weigth>... Neue Kante(n) einfügen. delete node <name>... Bestimmte(n) Knoten löschen. delete edge <src <dst> <name>... Bestimmte Kante(n) löschen. show [<node>] Alle Kanten die einen bestimmten Knoten als Start- oder Zielknoten haben anzeigen. Wird kein Knoten angegeben, wird er gesamte Graph angezeigt. quit Veranlasst das aufrufende Modul das Programm zu beenden. Fehlerbehandlung Zur Fehlerbehandlung werden folgende Exceptions zur Verfügung gestellt: Exception InputHandlerException 4.4 Beschreibung / Fehlermeldung Abstrakte Basisklasse für alle Fehlerfälle die im Input-Handler auftreten können. InvalidArgumentException error: invalid argument(s) UnknownCommandException error: unknown command Weitere Designentscheidungen StringTokenizer Zur einfachen Verarbeitung der übergeben Befehlszeile wird diese zunächst von einer eigenen Klasse StringTokenizer in ihre einzelnen Wörter zerlegt. Ein Aufruf der Methode getNextToken liefert immer das jeweils nächste Wort, solange hasMoreToken wahr zurückliefert. Seite 8 Gruppe 18 Example 1 Modul Testprogramm 5 Modul Testprogramm 5.1 Beschreibung Das Testprogramm stellt Befehle zur Verfügung mit der ein Graph bearbeitet werden kann. Dazu wird zunächst ein leerer Testgraph erstellt. Es wird eine Eingabeauforderung angezeigt, in der der Benutzer eine Reihe von durch das Modul Input-Handler definierte Befehle zur Manipulation eines Graphen eingeben kann. Das Testprogramm ist dafür verantwortlich das alle vom Graphen und vom Input-Handler weitergereichte Exceptions durch ihre entsprechende Fehlermeldungen dargestellt werden. Das Testprogramm besteht lediglich aus der Klasse TestProgram und wird über die Methode run vom Hauptprogramm aus gestartet. 5.2 Testfälle Um das Programm automatisch auf Fehler zu überprüfen, wird eine Liste von Befehlen in einer Datei bereitgestellt. Diese können dann einfach mittels workflow < testinput.txt in das Programm eingelesen und verarbeitet werden. Danach lässt sich die Ausgabe mit einer zuvor händisch erstellten Referenz-Ausgabe vergleichen. Die erledigt zum Beispiel das Programm diff und kann somit bequem zB in das Makefile eingebaut werden. Verschieden Testfälle werden mit anderen Gruppen gemeinsam erarbeitet und ausgetauscht. Gruppe 18 Seite 9