Entwurf
Ziel dieser Phase ist es, die Architektur des Softwaresystems festzulegen.
Diese komplexe Aufgabe kann man in folgende Schritte aufteilen:
-
Zerlegung in Teilsysteme und Spezifikation der Wechselwirkungen zwischen
den Teilsystemen
-
weitere Zerlegung der Teilsysteme in Komponenten (Module), Spezifikation
der Anforderungen an diese Komponenten durch Festlegung der Modulschnittstellen.
-
Entwurf der Algorithmen, die die geforderte Funktionalität bereitstellen.
Entwurfstechniken
Wichtigstes Hilfsmittel zur Meisterung von Komplexität ist das Prinzip
der Abstraktion. Anhand der Reihenfolge, in der man zu Abstraktionen gelangt,
lassen sich die beiden folgenden Vorgehensweisen unterscheiden:
-
Topdown-Entwurf
-
Ausgangspunkt ist eine Analyse der Systemspezifikation. Realisierungsdetails
werden zunächst nicht betrachtet. Man beginnt mit abstrakt beschriebenen
Lösungsideeen, die nach und nach konkretisiert werden, aber nur soweit,
wie dies für das momentane Verständnis des Systems notwendig
ist. Die Grundidee dieser Vorgehensweise geht auf Dijkstra und Wirth zurück.
-
Bottomup-Entwurf
-
Hardware und darüberliegende Schichten (vom Betriebssystem bis zur
vollständigen Applikation) bilden abstrakte Maschinen. Man geht also
von den Gegebenheiten einer gegebenen, "konkreten Maschine" aus, die um
benötigte Eigenschaften nach und nach ergänzt wird, bis schließlich
die zur Bereitstellung der gewünschten Funktionalität notwendige
Maschine verwirklicht ist.
Nicht nur die Reihenfolge, in der man zu den unverzichtbaren Abstraktionen
gelangt, sondern auch die Kriterien, nach denen ein System in Teile zerlegt
wird, haben entscheidenden Einfluß auf das Entwurfsergebnis. Folgende
Entwurfsprinzipien lassen sich unterscheiden:
-
funktionsorientiert
-
Das System wird betrachtet als eine Black-Box, die in der Lage ist, eine
(oder mehrere) Funktionen zu erbringen. Diese Funktionen, beschrieben in
den funktionalen Anforderungen der Systemspezifikation, werden schrittweise
in Teilaufgaben zerlegt, bis ihre Komplexität so gering ist, dass
sie direkt realisiert werden können. Die für diese aufgabenorientierte
Zerlegung vorgeschlagenen Methoden und Prinzipien gehen auf Wirth (stepwise
refinement) und Yourdan/Constantine (structured design) zurück.
-
datenorientiert
-
die Struktur der zu verarbeitenden Daten bestimmt die Struktur des Softwaresystems.
Beispiele finden sich im Compilerbau und häufig in der kommerziellen
Datenverarbeitung. Die wichtigsten Methoden sind attributierte Grammatiken
und, als Spezialfall davon, die von Jackson vorgeschlagenen Methoden und
Prinzipien.
-
objektorientiert
-
Ein Softwaresystem wird angesehen als eine Sammlung von miteinander kommunizierenden
Objekten. Jedes Objekt besitzt
-
einen von außen nicht zugänglichen Zustand (meist realisiert
durch Datenstrukturen, die nur dem Objekt zugänglich sind) und
-
ausführbare Operationen, die allen "Kunden" des Objektes zur Verfügung
stehen und mit denen möglicherweise dieser Zustand geändert oder
beobachtet werden kann.
Die Objekte tauschen Nachrichten aus, die dem Empfängerobjekt mitteilen,
welche seiner Operationen ausgeführt werden soll. Die Zerlegung orientiert
sich dabei an der Zusammengehörigkeit von Daten und Funktionen. Entscheidend
für die Reduktion von Komplexität ist die strikte Einhaltung
des Geheimnisprinzips. Es besagt, dass einem "Kunden" eines Objektes
nur soviel Wissen über ein Objekt zur Verfügung steht, wie er
zur Verwendung der "öffentlichen" Operationen des Objektes benötigt.
Modularisierung
Die Zerlegung der Projektaufgabe in Teilaufgaben ist Grundvoraussetzung
für Arbeitsteilung und Teamwork. Die Absprache der Teilaufgaben macht
eine frühe Festlegung der Schnittstellen zwischen den Teilaufgaben
notwendig. Gelingt dies in ausreichender Präzision und Stabilität,
können die Teilaufgaben weitgehend unabhängig voneinander durch
verschiedene Bearbeiter(gruppen) gelöst werden, und zwar unter Einhaltung
der in der Schnittstellendefinition getroffenenen Vereinbarungen. Dies
garantiert, dass die Lösungen der Teilaufgaben zu einer Lösung
für das Gesamtsystem integriert werden können.
Richtlinien
Ein Modul
-
fasst Operationen und Daten zur Realisierung einer in sich geschlossenen
Aufgabe zusammen,
-
darf mit der Außenwelt nur über eine eindeutig definierte Schnittstelle
kommunizieren,
-
kann in ein Programmsystem integriert werden, ohne die innere Arbeitsweise
des Moduls zu kennen,
-
kann auf Korrektheit geprüft werden (z.B. in einem Modultest), ohne
die Einbettung des Moduls in ein Programmsystem zu kennen.
Die zentrale Frage der Modularisierung lautet: Welche Operationen werden
mit welchen Daten in einem Modul zusammengefaßt? Folgende Kriterien
kann man berücksichtigen, um zu einer guten Modularisierung des Systems
zu gelangen:
-
Geschlossenheit und Bindung (cohesion)
-
meint den inneren Zusammenhang, die Zusammengehörigkeit der Operationen
und Daten in Bezug auf die in sich abgeschlossene Aufgabe des Moduls. Ziel
ist eine hohe Bindung.
-
Kopplung (coupling)
-
eines Moduls drückt aus, wie stark es mit anderen Modulen verbunden
und damit von ihnen abhängig ist. Ziel ist eine geringe Kopplung.
-
Minimale Schnittstelle
-
erkennt man z.B. an wenigen (am besten gar keinen) globalen Variablen,
wenigen exportierten Prozeduren mit kurzen Parameterlisten und einer kurzen,
leicht verständlichen Schnittstellenbeschreibung. Sie verringert die
Gefahr einer hohen Modulkopplung.
-
Größe
-
eines Moduls ist leicht meßbar, sagt aber meist recht wenig über
seine Komplexität. Eine sinnvolle Regel für eine anzustrebende
Größe gibt es nicht.
-
Testbarkeit
-
meint den Test des Moduls als eigenständige Einheit (in der Phase
Modultest). Dies wird entscheidend erleichtert durch geringe Kopplung und
eine knappe Schnittstelle.
-
Interferenzfreiheit
-
drückt aus, daß ein Modul keine unerwünschte Nebenwirkung
auf ein anderes hat. Nur wenn dies gewährleistet ist, kann man ein
Modul durch ein anderes mit gleicher Schnittstellendefinition ersetzen
und damit Änderbarkeit und Wartbarkeit eines Programmsystems steigern.
-
Importzahl
-
gibt an, wieviele weitere Module für die Implementierung des Moduls
benutzt werden. Eine hohe Importzahl ist ein Indiz für eine hohe Kopplung.
-
Verwendungszahl
-
gibt an, wieviele andere Module dieses Modul verwenden. Eine hohe Verwendungszahl
ist Indiz für Allgemeinheit und hohe Wiederverwendbarkeit.
-
Modulhierarchie
-
großer Programmsysteme enthält typischerweise vier übereinanderliegende
Ebenen, wobei auf einer Ebene die Dienste von Modulen tieferer Ebenen verwendet
werden:
-
das Steuermodul an der Spitze der Modulhierarchie ist für Koordinierung,
Initialisierungs- und Abschlußarbeiten zuständig. Es ist meist
recht kurz.
-
problemorientierte Module stellen die eigentlichen, anwendungsspezifischen
Algorithmen zur Problemlösung bereit.
-
Hilfsmodule stellen häufig benutzte Operationen allgemeiner Natur
bereit. Beispiele sind Verwaltung von Tabellen, Listen, Bäumen, ...
-
Module zur Kommunikation mit Hardware und mit Betriebssystem sind meist
nicht problemspezifisch und stehen fast immer in Bibliotheken zur Verfügung.
Abstrakte Datenstrukturen
und abstrakte Datentypen
Mit Modulen lassen sich Datenkapseln bilden. Sie verhindern unbeschränkte
Zugriffe auf globale Daten, eine Fehlerquelle ersten Ranges. Statt jedermann
auf komplexe Datenbestände zugreifen zu lassen, werden abstrakte
Datenstrukturen (ADS) angelegt, die Zugriff auf gemeinsame Daten nur
noch über einige, ausgezeichnete Prozeduren erlauben. Das Wissen über
die komplexen Datenbestände besitzen nur noch diese Zugriffsroutinen,
die dann auch verantwortlich für deren Konsistenz sind. Beim Entwurf
einer ADS werden zunächst die benötigten Zugriffsroutinen identifiziert,
um dann mit den Daten, die gekapselt werden sollen, in ein Modul zusammengefasst
zu werden. Danach ist Zugriff auf die Datenstruktur nur noch über
die Schnittstelle des Moduls, d.h. über die exportierten Zugriffsroutinen
möglich.
Zur Erzeugung mehrerer, gleichartiger abstrakter Datenstrukturen dienen
abstrakte Datentypen. Dort werden die Daten nicht mehr in ein Modul gekapselt,
sondern den Zugriffsroutinen als Parameter übergeben, wie im folgenden
Beispiel für einen abstrakten Datentyp Text
in Modula-2 Notation:
DEFINITION MODULE TextMod;
TYPE Text;
PROCEDURE NewText (VAR t:Text);
PROCEDURE DisposeText(VAR t:Text);
PROCEDURE AppendChar (VAR t:Text; ch:CHAR);
PROCEDURE GetChar (VAR t:Text; index:CARDINAL; VAR ch:CHAR);
PROCEDURE TextLength (VAR t:Text):CARDINAL;
PROCEDURE InsertChar (VAR t:Text; before:CARDINAL; ch:CHAR);
PROCEDURE DeleteChar (VAR t:Text; index:CARDINAL);
END TextMod;
Als Besonderheit von Modula-2 sind die opaque types ("undurchsichtige
Datentypen") ein entscheidendes Hilfsmittel, das Geheimnisprinzip für
abstrakte Datentypen auch durchzusetzen: Im obigen Beispiel ist dem Verwender
des Moduls nur der Name Text des abstrakten
Datentyps bekannt, nicht aber die Datenstruktur von Text,
die erst im IMPLEMENTATION MODULE
festgelegt wird, und damit einem Verwender des Moduls prinzipiell unbekannt
und, wichtiger, nicht zugänglich ist.
Entwurf von Benutzerschnittstellen
Diese Entwurfsaufgabe wird größtenteils schon während der
Systemspezifikation angegegangen, weil Art und Weise der Programmbedienung
schon im Pflichtenheft festgelegt werden muss, häufig durch Festschreibung
eines Prototyps.
Folgende grundlegende Anforderungen an den Zielrechner werden im folgenden
vorausgesetzt: grafischer Bildschirm, Maus, Fenstertechnik, Menüs.
Zustände. Entscheidenden Einfluss
hat der Leitsatz "Avoid Modes", wobei "Mode" eine Situation meint, in der
der Benutzer nur eine beschränkte Anzahl von Operationen ausführen
kann. Zustände (in diesem Sinne) engen den Benutzer ein und zwingen
ihn, von seiner natürlichen Arbeitsweise abzuweichen. Als Konsequenz
wird man also statt eines Dialoges, der vom Benutzer nach und nach bestimmte
Informationen in einer starren, vom System vorgegebenen Reihenfolge abfragt,
ein Formular anbieten, in das der Benutzer die vom Programm benötigte
Information in frei gewählter Reihenfolge eintragen kann. Sind verschiedene
Zustände (Modes) nicht vermeidbar, empfiehlt es sich, ein eigenes
Fenster dafür einzuführen.
Zustände lassen sich auch dadurch vermeiden, dass man komplexe
Benutzeraktionen in kleine, einfache, unteilbare Elementaroperationen zerlegt.
Z.B. kann man den Befehl move 1-3,10 in vier
Elementaroperationen zerlegen:
-
Auswahl der ersten drei Zeilen (Mausbefehl) ,
-
Menükommando "Cut",
-
Versetzen der Einfügemarke vor Zeile 10 (Mausbefehl),
-
Menükommando "Paste".
Menüs. Sie bedeuten für ungeübte
und gelegentliche Benutzer eine erhebliche Arbeitserleichterung. Pulldown
-Menüs sind am häufigsten anzutreffen, auch deshalb, weil sie
eine große Menge von Befehlen übersichtlich strukturieren können
und dem Benutzer die zur Verfügung stehenden Operationen jederzeit
übersichtlich präsentieren. Menübefehle können nach
ihrer Funktion in drei Klassen eingeteilt werden:
-
sofort ausführbar (z.B. "Cut" , "Paste")
-
ausführbar erst, nachdem der Benutzer weitere Parameter angegeben
hat (z.B. "Find...")
-
zur Zustandsänderung (z.B. "Show Controls")
Menübefehle können für geübte Benutzer lästig
werden, weil sie durch den Wechsel zwischen Maus und Tastatur den Arbeitsfluss
unterbrechen. Daher werden meist Tastaturkürzel angeboten, mit denen
die wichtigsten Menübefehle aktiviert werden können.
Oft sind Menübefehle nur in bestimmten Zuständen ausführbar,
z.B. ist "Copy" erst sinnvoll, wenn in einem aktiven Fenster etwas selektiert
ist. In einem solchen Fall kann man den Menübefehl ohne Wirkung lassen,
oder, besser, eine Fehlermeldung nach Aktivierung dieses Befehls ausgeben,
oder, am besten, die Auswahl des nicht ausführbaren Befehls von vornherein
verhindern. Dies sollte man aber nicht erreichen, indem man den Befehl
aus dem Menü entfernt, sondern ihn geeignet als inaktiv markiert.
Masken, Dialogboxen. Sie wurden früher
zur zustandsfreien Eingabe von Textfeldern in Bildschirmformularen genutzt.
Heute sind weitere Gestaltungsmittel hinzugekommen: Statischer Text, Editierbarer
Text, Befehlsknopf, Ein-/Ausschalter, Umschalter, Popup-Menüs, Schieberegler,
Auswahlleisten, Piktogramme, Trennlinien, Rahmen, ... Alle gängigen
grafischen Benutzeroberflächen bieten Bibliotheken an, mit denen diese
Gestaltungsmittel einfach verwendet werden können.
Farbe. Farbbildschirme stehen fast überall
zur Verfügung. Der Einsatz von Farbe sollte allerdings folgende Punkte
beachten:
-
Etwa 5% der Menschheit ist farbenblind. Farbe sollte daher nie als alleiniger
Informationsträger verwendet werden.
-
Langzeitstudien haben gezeigt, daß beim täglichen Arbeiten schwarze
Schrift auf weißem Grund am besten lesbar ist. Bei schwarzem Hintergrund
hat sich Bernstein bewährt. Am schlechtesten lesbar ist Schrift in
den Farben rot oder blau. Die extremen Wellenlängen dieser Farben
an den entgegegesetzten Enden des sichtbaren Spektrums zwingen das Auge
zu Anpassungsbewegungen, die es frühzeitig ermüden.
-
Helles Blau auf weißem Grund ist schwer erkennbar, besonders bei
dünnen Linien oder kleinen Objekten. Man kann diesen Effekt verwenden,
um unwichtige Dinge (etwa Rasterlinien) in den Hintergrund treten zu lassen.
Ton. Die Zeiten, in denen Computer nur einfache
Piepser von sich geben konnten, sind vorbei. Der sinnvolle Einsatz von
Tönen ist heute fast überall möglich. Bei der Gestaltung
des Einsatzes sollte man folgende Punkte beachten:
-
Geräusche sind ein gutes Mittel, um Aufmerksamkeit zu erregen. Es
stumpft aber schnell ab, wenn es übermäßig verwendet wird.
Sinnvolle Anwendungen sind Fehlersituationen (z.B. kein Papier für
einen Druckvorgang), Eintreten unerwarteter Ereignisse oder zum Ende eines
lang andauernden Vorgangs (z.B. Ende einer Datenübertragung, Wecker).
-
Wie die Farben, sollten Töne eine Situation nur verdeutlichen, nie
aber alleiniger Träger der Information sein. Der Ton wird möglicherweise
nicht gehört, weil der Benutzer schwerhörig ist, er den Raum
verlassen hat, in einer lauten Umgebung arbeitet, oder ...
-
Es ist sinnvoll, verschiedene Ereignisss durch deutlich verschieden Töne
zu signalisieren.
-
Laute und grelle Töne sind zu vermeiden, ebenso Melodien mit mehreren
Tönen. Man denke an ein Großraumbüro, in dem alle paar
Minuten ein Kuckucksruf erklingt. Der anfängliche Heiterkeitserfolg
wird schnell als störend empfunden.
-
Auch in Notfällen sollte kein Dauerton verwendet werden. Dies kann
zu Panik oder anderem Fehlverhalten der Benutzer führen. Besser ist
ein kurzer Ton, der regelmäßig, etwa in einminütigem Abstand,
wiederholt wird, bis er vom Benutzer abgestellt wird.
Konsistenz. Einheitlichkeit der Benutzerschnittstelle
innerhalb eines Programms muß eine Selbstverständlichkeit sein.
Dies wirkt sich u.a. aus auf Benenung und Gruppierung der Menübefehle,
Tastaturabkürzungen, Beschriftung und Positionierung der Knöpfe.
Ähnlichkeiten und Übereinstimmungen mit den Benutzerschnittstellen
anderer Programme, die auf demselben Computer verwendet werden, sollte
man anstreben, allein um den Lernaufand für den neuen Benutzer gering
zu halten. Ein neues Programm wird eher akzeptiert, wenn es schnell erlernbar
ist und keine Überraschungen bereithält. Was würden Sie
von einem Auto halten, bei dem die Position von Gas- und Bremspedal vertauscht
sind?
Objektorientierter Entwurf
Ein Objekt ist ein Ding mit eigener Individualität und Identität.
Es besitzt Attribute, zeigt ein Verhalten, gehört zu einer (Objekt-)Klasse
und kann Beziehungen zu anderen Objekten besitzen. Objekte können
neu entstehen, sich verändern und auch wieder verschwinden. Mengen
gleichartiger Objekte werden zu Klassen zusammengefasst. Eine Klasse läßt
sich als Herstellungsvorschrift für ein Objekt deuten, ein Objekt
ist Instanz einer Klasse.
Die zentrale Frage des objekt-orientierten Entwurfs lautet: Wie findet
man die zur Aufgabenlösung geigneten Objekte und die zu ihnen gehörenden
Aktionen? In der Literatur findet man zahlreiche, unterschiedliche
und meist recht detailliert ausgearbeitete Ansätze für objekt-orientierte
Analyse und objekt-orientierten Entwurf. Häufig liegt das Hauptgewicht
auf einer speziellen, meist grafischen Notation. Viele gehen vom Entity-Relationship
Modell zur Modellierung der Beziehungen zwischen Objekten aus, andere
verwenden semantische Netze. Bisher hat sich keines der empfohlenen
Verfahren auf breiter Front durchgesetzt.
Im folgenden wird daher ein sehr elementares Verfahren zum Auffinden
der Objekte beschrieben, ohne auf die oben erwähnten, detaillierteren
Ansätze näher einzugehen.
Methode von Abbot
Ausgangspunkt und Grundlage ist ein in Umgangssprache formulierter Text,
der die Aufgabenstellung verbal beschreibt. Er kann z.B. der Systemspezifikation
oder auch anderen Dokumenten (z.B. Formulare, Daten, Gerätebeschreibungen,
Archive) entnommen sein. Entscheidend ist, dass sie für den Problembereich
relevante Beschreibungen liefern. Abbot empfiehlt, bestimmte Wortarten
in der Spezifikation zu suchen und aus ihnen den Entwurf abzuleiten. Ursprünglich
zielte die Methode darauf, abstrakte Datenstrukturen zu bilden und die
mit ihnen erforderlichen Operationen zu bestimmen. Sie kann aber auch sinnvoll
als Grundlage für einen objekt-orientierten Entwurf verwendet werden.
Man folgt folgenden Schema:
-
Hauptwörter herausfiltern
-
mit dem Ziel, Objekte und Klassen zu identifizieren und zu benennen. Gattungsnamen
wie z.B. "Mensch", "Auto", "Ampel", "Vorlesung" sind Kandidaten für
Klassen, Eigennamen wie z.B. "Albert Einstein", "Paris", "FH Gießen-Friedberg"
sind Kandidaten für Objekte. Häufig führt ein Eigenname
auch zu einem entsprechenden Gattungsnamen, z.B. "Physiker", "Stadt", "Hochschule".
Unergiebig in diesem Zusammenhang sind Mengen- und Größenangaben
("Liter"), Materialbezeichnungen ("Holz"), Sammelnamen ("Regierung") und
abstrakte Begriffe ("Schönheit", "Arbeit"). Sie führen meist
nicht zur Identifizierung von Klassen. Im Deutschen werden häufig
Zeitwörter als Hauptwörter verwendet, z.B. "das Abspielen von
Musik". Sie deuten auf Aktionen hin und werden behandelt wie die zugehörigen
Zeitwörter (s.u.).
-
Gemeinsamkeiten suchen.
-
In der Regel treten in der Spezifikation viele verwandte Hauptwörter
auf, wie z.B. "Abteilungsleiter" und "Vorgesetzter", "Autor" und "Verfasser",
"Auto" und "Kraftfahrzeug". Dann ist zu klären, ob es sich wirklich
um verschiedene Begriffe handelt. Dies ist der Fall, wenn die Objekte dieser
Klassen verschiedene Eigenschaften besitzen, die auch für die Anwendung
relevant sind. Steht fest, dass zwei verwandte Begriffe verschieden sind,
sollte man klären, in welcher Beziehung sie zueinander stehen. Häufig
entdeckt man eine Teilmengenbeziehung (Ist-ein-Relation), die man an Sätzen
wie "Jedes Auto ist ein Kraftfahrzeug" erkennt. Die Teilmengenbeziehung
ist die Grundlage, auf der Klassenhierarchien (s.u.)
gebildet werden.
-
Relevante Zeitwörter auffinden
-
Sie bezeichnen Aktionen mit Objekten. Im Gegensatz zum funktionsorientierten
Ansatz wird eine Aktion aber nicht isoliert betrachtet, sondern immer im
Zusammenhang mit dem Objekt, zu dem sie gehört. Sie wird zu einer
Methode dieses Objektes. Zum Beispiel gehört das Zeitwort "umschalten"
zu einer Verkehrsampel, das Zeitwort "beschleunigen" hängt mit einem
Kraftfahrzeug zusammen. Die Zuordnung von Aktion zu Objekt kann manchmal
schwierig sein, wenn z.B. ein Satz wie der folgende vorliegt: "Das Auto
soll auf dem Bildschirm angezeigt werden." Wird die Aktion "anzeigen" dem
Auto oder dem Bildschirm zugeordnet? Dies wird häufig so gelöst,
dass die Aktion beiden Klassen zugeordnet wird, bei der Implementierung
eine der beiden Operationen auf die andere zurückgeführt wird.
Die oben genannten Regeln sind kein allgemein gültiges Rezept, das
einen guten Entwurf garantiert. Entscheidenden Einfluss hat auch Umfang
und Qualität der Spezifikation. Sie sollte möglichst in der Sprache
des Problembereichs verfasst sein. Dies führt zu Klassen, deren Bedeutung
auch der Auftragggeber verstehen kann.
Eine natürliche, objekt-orientierte Zerlegung des Gesamtsystems
führt zu einer Sammlung von Klassen mit klaren Schnittstellen, die
weitgehend unabhängig voneinander behandelt werden können. Man
geht meist schrittweise vor: Am Anfang des Entwurfprozesses stehen Objekte
aus dem Problembereich, die zunächst nur als Black Boxes betrachtet
werden, die aber immer konkretere Gestalt annehmen. Beim algorithmischen
Entwurf der Aktionen wird man in der Regel auf weitere Klassen stossen,
die mit der ursprünglichen Aufgabenstellung nur noch am Rande zu tun
haben. Beim Entwurf eines Simulationsprogrammes für eine Ampelsteuerung
könnten z.B. der Reihe nach folgende Klassen entstehen:
-
direkt aus der Spezifikation: Verkehrsampel, Auto, Straße, Kreuzung,
Zeit, Statistik.
-
beim Entwurf der Klassen aus 1: Fahrspur, Richtung, Farbe, Histogramm.
-
beim algorithmischen Entwurf der Operationen: Ereignis, Generator, Warteschlange,
Liste, Menge, Matrix.
Klassenhierarchien
Wesentliches Kennzeichen des objekt-orientierten Ansatzes ist die Möglichkeit,
Hierrarchien zwischen Klassen aufzubauen und auszunutzen. Hierbei werden
ähnliche Dinge erkannt und unter gemeinsame Oberbegriffe zusammengefasst.
Dadurch entsteht eine Baumstruktur, bei der allgemeine, abstrakte Begriffe
nahe der Wurzel stehen, und die Blätter die spezialisierten, konkreten
Begriffe darstellen.
| KFZ |
Auto |
LKW |
|
|
|
PKW |
Limousine |
|
|
|
Cabriolet |
|
|
|
Kombi |
|
Zweirad |
Moped |
Mofa |
|
|
Motorrad |
|
Klassenhierarchie der Kraftfahrzeuge
Die Klasse KFZ bestimmt die allgemeinen Eigenschaften der von ihr abgeleiteten
Klassen. Unabhängig von der Art des KFZ hat jedes KFZ Marke, Typ,
Seriennummer, Motorleistung, .. Jedes Auto hat ebenfalls diese Eigenschaften,
weil es (auch) ein KFZ ist. Weiterhin besitzt ein Auto weitere, spezifische
Merkmale z.B. Anzahl der Türen. Bei jedem Spezialisierungsschritt
kommen weitere Eigenschaften hinzu.
Mit zunehmender Spezialisierung nimmt auch die Funktionalität zu.
Mit einem Cabriolet kann z.B. alles machen, was auch mit einem PKW möglich
ist, darüber hinaus kann man aber auch sein Dach öffnen.
Für die Implementierungssprache hat die Unterstützung von
Klassenhierarchien zwei wichtige Konsequenzen:
-
Vererbung
-
Eigenschaften und Funktionen, die für eine allgemeine Klasse definiert
sind, gelten automatisch auch für alle von ihr abgeleiteten, spezielleren
Klassen. Gibt es z.B. für ein Auto eine Funktion, die aus Luftwiderstandsbeiwert
und Motordaten seine Höchstgeschwindigkeit berechnen, so kann diese
Funktion auch auf alle Objekt abgeleiteteter Klassen angewendet werden.
-
Polymorphismus
-
Ein Programmstück, das für Objekte einer Klasse K funktioniert,
kann auch mit Objekten einer von K abgeleiteten Klasse arbeiten,
obwohl solche Objekte anderen Aufbau und anderes Verhalten als K-Objekte
besitzen können. Dies ist möglich, weil in abgeleiteten Klassen
nur neue Eigenschaften hinzukommen, aber niemals welche entfernt werden.
Eine weitere Voraussetzung für Polymorphismus ist die strikte Einhaltung
des Geheimnisprinzips: das Programmstück funktioniert auch für
Objekte mit verschiedenen Implementierungen, weil das Programmstück
diese Kenntnis nicht besitzt und auch nicht verwenden kann, sondern lediglich
die Information in der Schnittstellenbeschreibung der Klasse K ausnutzen
kann.
Abstrakte Klassen. Oft kommen in einer Spezifikation Begriffe vor,
die sich in vielerlei Hinsicht ähneln, aber in einigen Details deutlich
unterscheiden. Als Beispiel diene ein Programm, das Kreise und Rechtecke
anzeigen soll. Beide weisen Eigenschaften auf, die gemeinsam behandelt
werden können und sollen (Strichstärke, Farbe, Füllmuster,
bewegen auf dem Bildschirm). Sie können aber nicht sinnvoll mit einer
Ist-ein-Beziehung verknüpft werden. In solchen Fällen sollte
man eine gemeinsame Oberklasse schaffen, der die beiden Klassen untergeordnet
werden können, z.B. "Grafik". Die "Rechteck" und "Kreis" gemeinsamen
Eigenschaften werden in die gemeinsame Klasse verlegt, nur spezielle Eigenschaften
und Operationen (Mittelpunkt und Radius, Breite und Höhe, Zeichenoperationen)
verbleiben in den abgeleiteten Klassen. Die gemeinsame Oberklasse ist eine
Abstraktion der speziellen Klassen und besitzt keine eigenen Objekte. Man
spricht daher von einer abstrakten Klasse.
Das Verfahren von Abbot ist ein analytisches Topdown-Verfahren:
Es geht von der Aufgabenstellung aus, beginnt mit dem Entwurf anwendungsspezifischer
Klassen und schreitet fort zum Entwurf implementierungsspezifischer Klassen.
Dabei wird auf eventuell schon bestehende Klassen keine Rücksicht
genommen. Diese werden aber für viele Anwendungsbereiche in Klassenbibliotheken
bereitgestellt. Außerdem ist es erklärtes Ziel des objekt-orientierten
Ansatzes, bestehende Klassen wiederzuverwenden. Dies wird erreicht durch
den alternativen Ansatz des konstruktiven Bottomup-Vorgehens. Hier
wird versucht, aus den zur Verfügung stehenden Klassen eine Problemlösung
zu konstruieren. Dies wird gelingt jedoch selten vollständig, da problemnahe
Klassen in Bibliotheken meist nicht bereit stehen. So wird man beide Ansätze
verfolgen müssen und das Produkt von zwei Seiten wachsen lassen:auf
der einen Seite durch problemnahe, mit der Abbot-Methode entworfenen Klassen,
auf der anderen Seite durch Konstruktion und Kombination bestehender Klassen
mit Lösungen für Standardprobleme.
Überlegungen zum Klassenentwurf
-
Was sind die physischen und logischen Objekte des realen Systems? Die Antwort
führt zu den Klassen .
-
Welche Operationen kann man mit diesen Objekten ausführen? Die Antwort
führt zu den Methoden
-
Welche Daten müssen in einem Objekt gespeichert werden, damit die
Operationen ihre Aufgabe erfüllen können? Die Antwort führt
zu den Attributen des Objekts, sie bilden den Objektzustand.
Generalisierung
Wiederverwendbarkeit spielt eine zentrale Rolle im objet-orientierten Entwurf.
Innerhalb eines Progammes wird Wiederverwendung durch Vererbung unterstützt,
bei programmübergreifender Wiederverwendung gehören Klassenbibliotheken
zu den wichtigsten Hilfsmitteln. Die Klassen in solchen Bibkliotheken sollten
eine möglichst hohe Allgemeinheit anstreben, um ihre Verwendbarkeit
in vielen Projekten, auch unter nicht vorhergesehenen Umständen sicherzustellen.
Folgende Maßnahmen sind dabei zweckmäßig:
-
Vorbeugende Erweiterung: nicht nur die zur Lösung der aktuellen
Aufgabe benötigten Eigenschaften werden implementiert, sondern auch
solche, deren Notwendigkeit man für zukünftige Anwendungen vorhersehen
kann.
-
Faktorisierung: Ist absehbar, daß es für eine Klasse
später einmal Alternativen mit ähnlichem Verhalten geben wird,
sollte man eine abstrakte Klasse einführen, die das gemeinsame Verhalten
dieser ähnlichen Alternativen beschreibt. Die zukünftigen Klassen
werden dann von dieser abstrakten Klasse abgeleitet.
-
Einfache Funktionen: Da bei der Ableitung neuer Klassen bestehende (geerbte)
Funktionen, die nicht genau passen, neu implementiert ("überschrieben")
werden müssen, sollte man komplexe Funktionen vermeiden und diese
stattdessen in mehrere, kleinere zerlegen, die dann getrennt überschrieben
werden können.
Ein weiteres, wichtiges Hilfsmittel ist der Einsatz von erprobten Entwurfsmustern.
Statische Beziehungen zwischen
Klasse
Zum Entwurf gehört auch die Analyse der Beziehungen zwischen Klassen.
Man kann folgende drei Arten von Beziehungen unterscheiden:
-
Ist-Ein-Beziehung
-
Sie gilt zwischen allgemeiner Klasse A und spezieller Klasse S,
wenn
jedes S-Objekt auch ein A-Objekt ist. Häufig verwendete Sprechweisen:
A ist Oberklasse und S ist Unterklasse, A
ist die
Basisklasse ´und S eine (von A) abgeleitete Klasse.
-
Aggregationsbeziehung (Hat-Beziehung)
-
Sie gilt zwischen Objekten G (das Ganze) und Ti (der
Teil), wenn Ti ein Teil von G ist. Anders ausgedrückt,
wenn G aus ein oder mehreren T1, T2,
..Tn besteht. Z.B besteht ein Auto aus einem Motor, einer
Karosserie und vier Rädern.
-
sonstige Assoziationen
-
Damit sind alle anderen Beziehung zwischen Objekten gemeint. Z.B. die Beziehung
"bucht" zwischen "Passagier" und "Flug"
Die Beziehungen zwischen Klassen kan man in Entity-Relationship Diagrammen
grafisch veranschaulichen. Es gibt verschiedene Notationen. In der Chen-Notation
werden Entity-Typen (= Klassen) als beschriftete Rechteck dargestellt,
die Beziehungstypen als beschriftete Rauten, die mit den beteiligten Entity-Typen
durch Linien verbunden sind. An die Linien wird der Grad der Beziehung
geschrieben.Auch dafür gibt es (zu)viele Notationen. Bewährt
hat sich folgende Intervallnotation. Das Bild
A ---(minA,maxA)---
<R>---(minB,maxB)---B
besagt, dass jedes A-Objekt am R-Beziehungstyp mindestens
minA-mal
und höchstens maxA-mal beteiligt ist. Analoges gilt
für B-Objekte.
Weitere Entwurfshinweise
In einer hybriden Programmiersprache wie z.B. C++, in der man objekt-orientierte
Konzepte verwenden kann, aber auch andere Ansätze realisieren kann,
stellt sich die Frage: Wann ist es sinnvoll, Klassen einzusetzen? Die folgenden
Fragen führen zu einer Antwort:
-
Bringt Datenabstraktion eine Vereinfachung? Sind die Daten komplex genug?
Falls nicht, reicht es, einen konkreten Datentyp der Programmiersprache
zu verwenden.
-
Falls ja, gibt es mehrer Exemplare der Daten? Falls nicht, sollte man sie
als abstrakte Datenstruktur anlegen.
-
Falls ja, existieren Daten in Varianten, die gleich behandelt werden sollen?
Falls nicht, reicht es, einen abstrakten Datentyp anzulegen. Andernfalls
sollte man eine Klasse vorsehen.
Die folgenden Fehler sind häufig beim objekt-orientierten Entwurf
zu beobachten:
-
Zu viele triviale Klassen
-
Verwechslung der Ist-Ein-Beziehung mit der Hat-Beziehung
-
Verwechslung von Ober- und Unterklasse
-
Varianten trotz gleicher Struktur und gleichem Verhalten
-
Falsches Empfängerobjekt
-
Zu tiefe oder zu flache Klassenhierarchie