203 18 16MB
German Pages 244 [248] Year 1992
Für
Heike, Sarah, Dominic und Philipp
Einführung in die objektorientierte Programmierung von Prof. Dr. Kurt-Ulrich Witt Fachhochschule Rheinland-Pfalz
R. Oldenbourg Verlag München Wien 1992
Prof. Dr. Kurt-Ulrich Witt Anschrift: Fachhochschule Rheinland-Pfalz, Abt. Trier Angewandte Informatik Schneidershof 5500 Trier
Die Deutsche Bibliothek - CIP-Einheitsaufnahme Witt, Kurt-Ulrich: Einführung in die objektorientierte Programmierung / von Kurt-Ulrich Witt. - München ;Wien : Oldenbourg, 1992 ISBN 3-486-21615-5
© 1992 R. Oldenbourg Verlag GmbH, München Das Werk außerhalb lässig und filmungen
einschließlich aller Abbildungen ist urheberrechtlich geschützt. Jede Verwertung der Grenzen des Urheberrechtsgesetzes ist ohne Zustimmung des Verlages unzustrafbar. Das gilt insbesondere für Vervielfältigungen, Übersetzungen, Mikroverund die Einspeicherung und Bearbeitung in elektronischen Systemen.
Gesamtherstellung: R. Oldenbourg Graphische Betriebe GmbH, München ISBN 3 - 4 8 6 - 2 1 6 1 5 - 5
Inhaltsverzeichnis Vorwort 1
2
XI
Einführung
1
1.1 1.2
E n t s t e h u n g von Simula Anforderungen an Software-Systeme 1.2.1 Benutzerschnittstellen und Arbeitsplätze 1.2.2 Multimediasysteme 1.2.3 Datenbanksysteme 1.2.4 Betriebssysteme und verteilte Systeme 1.3 Ein einführendes Beispiel Literaturhinweise
1 6 7 8 9 10 11 24
Software-Engineering
25
2.1 2.2
Software-Qualität 25 Vorgehensmodelle für die Software-Entwicklung 31 2.2.1 Software-Lebenszyklus 31 2.2.2 Prototyping 36 2.2.3 Evolutionäre Software-Entwicklung 36 2.3 Modularisierung und Datenkapselung 38 2.3.1 Modularisierung 40 2.3.2 A b s t r a k t e Spezifikationen und Implementierung von Modulen 43 Literaturhinweise 52
3
Grundkonzepte objektorientierter Programmierung
53
3.1
54 57 58 63 74
Objekte 3.1.1 Zustand von O b j e k t e n 3.1.2 Werte und O b j e k t e 3.1.3 Objekt-Identität und -Gleichheit 3.1.4 Verhalten von O b j e k t e n
VI
Inhalt
3.2
Nachrichten, Überladen und spätes Binden 3.2.1 Nachrichten 3.2.2 Überladen 3.2.3 Spätes Binden 3.3 Klassen und Exemplare 3.3.1 Klassendefinition 3.3.2 Objekt-Erzeugung 3.3.3 Löschen von Objekten 3.3.4 Klassenextensionen 3.4 Vererbung 3.4.1 Subtypbeziehung und Vererbungskonzept 3.4.2 Klassenvererbung Literaturhinweise
75 75 77 81 83 83 91 94 95 97 101 106 116
4
Optionale Eigenschaften 4.1 Multiple Vererbung 4.2 Prototyping und Delegation 4.3 Parallele objektorientierte Systeme 4.3.1 Parallele Prozesse in Smalltalk 4.3.2 Aktorsysteme Literaturhinweise
117 117 127 129 129 131 135
5
Objektorientierte Programmiersprachen 5.1 Simula 5.1.1 O b j e k t e und Klassen 5.1.2 Erzeugung von Objekten 5.1.3 Referenztypen und -variable 5.1.4 Referenzzuweisungen und -ausdrücke 5.1.5 Zugriff auf Attribute 5.1.6 Das Sieb des Eratosthenes in Simula 5.1.7 Schlußbemerkungen 5.2 Smalltalk 5.2.1 Grundelemente 5.2.2 Nachrichten 5.2.3 Methoden 5.2.4 Klassen und Vererbung 5.2.5 Das Sieb des Eratosthenes in Smalltalk 5.2.6 Schlußbemerkungen 5.3 C++ 5.3.1 Klassen und Objekte 5.3.2 Vererbung 5.3.3 Überladen von Operatoren
137 138 140 143 144 144 147 148 150 150 151 156 163 163 165 167 168 168 175 182
Inhalt
6
VII
5.3.4 Das Sieb des Erstosthenes in C + + 5.3.5 Schlußbemerkungen 5.4 Turbo Pascal 5.4.1 Klassen und Instanzen 5.4.2 Vererbung 5.4.3 Uberladen von Methoden 5.4.4 Das Sieb des Eratosthenes in Turbo Pascal 5.4.5 Schlußbemerkungen 5.5 Klassifizierung objektorientierter Programmiersprachen Literaturhinweise
185 187 188 188 192 193 194 199 200 201
Objektorientierter Entwurf 6.1 Ausgangspunkte für den objektorientierten Entwurf 6.2 Analyse, Entwurf und Implementierung 6.3 Einige objektorientierte Entwurfsmethodologien Literaturhinweise
205 206 207 209 218
Literaturverzeichnis
219
N a m e n - und Sachregister
227
Abbildungsverzeichnis 1.1 1.2 1.3 1.3 1.3 1.4
Prozedurale Struktur des Sieb des Eratosthenes Programmablauf von E r a t o s t h e n e s Objekte, ihre Zustände und Nachrichten während der Primzahlerzeugung mit E r a t o s t h e n e s Fortsetzung Fortsetzung Objektorientierte Struktur des Sieb des Eratosthenes
17 18 19 23
2.1 2.2 2.3 2.4
Vorgehensmodell: Linearer Software-Lebenszyklus ("Wasserfallmodell") Vorgehensmodell: Prototyping Vorgehensmodell: Evolutionäre Software-Entwicklung Modulaufbau
34 37 39 42
Grundstruktur objektorientierter Systeme Objekt-Aufbau Objektgraphen zu den Objekten o 2 , o6, o i s und o n Komplexes Objekt Ou Komplexes Objekt on mit strukturierten Werten als Komponentenwerte Objekt-Identität und -Gleichheit Flache und tiefe Kopie von oj Verschmelzen der Objekte 06 und 07 Aufbau von Nachrichten Grundansätze der objektorientierten sowie der prozeduralen Programmierung 3.11 Klassenbeschreibung 3.12 Klassenhierarchie aus der Fahrzeugwelt 3.13 Klassenhierarchien implementieren Subtyphierarchien
55 56 62 64 65 69 72 73 76 82 85 100 104
4.1 4.2 4.3
Einfache und multiple Vererbung Linearisierung der Motorf ahrzeuge-Klassen Verhalten eines Aktors
119 123 133
5.1 5.2
Entwicklungslinien objektorientierter Programmiersprachen Klassenhierarchie des Smalltalk-Systems (Auszug)
139 164
3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 3.10
13 16
X
Abbildungen
5.3
Eine Klassenhierarchie und die entsprechende Metaklassenhierarchie . . 166
6.1 6.2
Objektorientierte und funktionale Entwürfe und ihre Implementierung Spezifizierung und Generalisierung von Klassen
208 215
Vorwort Nachdem die Verbreitung und Anwendung von Methoden der strukturierten Programmierung sowie die Entwicklung und Anwendung von auf ihnen basierenden Methoden der Software-Entwicklung nicht zu einer wesentlichen Verbesserung der Situation geführt haben, die allgemein mit dem Begriff Software-Krise bezeichnet wird, ist seit Beginn der achtziger J a h r e viel Hoffnung auf das neue Paradigma objektorientierte Programmierung gesetzt worden. Es werden für alle Anwendungsbereiche Software-Systeme mit der Eigenschaft, objektorientiert zu sein, angeboten, und b e k a n n t e Programmiersprachen und Anwendungssysteme werden um Konzepte erweitert, die diese zu objektorientierte Sprachen bzw. Systeme machen sollen. Dieses Buch gibt eine Einführung in die Konzepte der objektorientierten Programmierung. Es ist an diejenigen Leserinnen gerichtet, die Grundkenntnisse in der elektronischen Datenverarbeitung und in mindestens einer höheren Programmiersprache besitzen. Es soll insbesondere die Leserinnen mit den Grundkonzepten objektorientierter Programmierung und objektorientierter Programmiersprachen v e r t r a u t machen, die bisher Erfahrung in der prozeduralen P r o g r a m m i e r u n g gesammelt h a b e n . Die Konzepte objektorientierter Programmierung werden in diesem Buch weitgehend Programmiersprachen- und systemunabhängig dargestellt. Die Leserinnen werden dadurch in die Lage versetzt, "real existierende" Sprachen u n d Systeme zu beurteilen und einzuordnen sowie Methoden des objektorientierten SoftwareEntwurfs und der objektorientierten Programmierung unabhängig von einem speziellen Ziel-System anzuwenden. D a s Buch gliedert sich in sechs Kapitel, ein Literaturverzeichnis sowie einen Index. Im ersten Kapitel wird ein kurzer historischer Rückblick auf die Entwicklung höherer Programmiersprachen gegeben, dabei wird insbesondere die Entstehungsgeschichte von Simula, der " U r a h n i n " objektorientierter Programmiersprachen, geschildert. Desweiteren werden Anforderungen aus einigen verschiedenen Anwendungsbereichen diskutiert, denen mit Hilfe objektorientierter Konzepte zufriedenstellend begegnet werden kann. Am Ende des ersten Kapitels wird mit Hilfe eines Beispiels eine informelle Einführung in die objektorientierte Programmierung gegeben. D a s zweite Kapitel behandelt G r u n d a s p e k t e des Software-Engineering wie Soft-
XII
Vorwort
ware-Qualitätsmaße u n d Vorgehensmodelle f ü r die Herstellung und die Nutzung von Software sowie das Modulkonzept und Möglichkeiten zur Spezifikation und Implementierung von Modulen. Diese Aspekte sind die Grundlage f ü r die Definition von Zielen, welche mit Hilfe des Einsatzes objektorientierter SoftwareEntwicklungsmethoden erreicht werden sollen. Im dritten Kapitel werden die wesentlichen G r u n d k o n z e p t e objektorientierter P r o g r a m m i e r m e t h o d e n und -sprachen, Objekte, Nachrichten u n d Uberladen sowie Klassen und Vererbung, dargestellt. Das vierte Kapitel behandelt optionale Konzepte objektorientierter Programmiermethoden u n d -sprachen, wie z. B. multiple Vererbung, Aktor-Systeme und Parallelität. Das f ü n f t e Kapitel stellt einige der kommerziell verfügbaren, mit dem Attribut "objektorientiert" versehene Programmiersprachen vor u n d ordnet sie hinsichtlich der in den Kapiteln 3 u n d 4 gegebenen Merkmale ein. Kapitel 6 diskutiert Ansätze für den Programmiersprachen- und systemunabhängigen objektorientierten Software-Entwurf. Am Ende jedes Kapitels werden Hinweise auf Literaturstellen gegeben, welche den im Kapitel behandelten Stoff ergänzen. Die Literaturstellen selbst sind im Literaturverzeichnis aufgelistet. Ziel des Buches ist es, die Leserinnen in den S t a n d zu versetzen, objektorientierte Software-Entwicklungsmethoden einzusetzen. Das Buch soll dazu beitragen, das Werkzeug-Repertoire der Leserinnen für die Software-Entwicklung zu erweitern — wohlwissend, d a ß neben den Werkzeugen auch a n d e r e Aspekte, wie ProjektOrganisation und - M a n a g e m e n t , die in diesem Buch nicht behandelt werden, eine wichtige Rolle bei der Entwicklung "guter" Software spielen. In den Text sind Ergebnisse von Arbeiten, die der Autor während seiner Tätigkeit am Zentralinstitut für Angewandte M a t h e m a t i k des Forschungszentrums Jülich durchgeführt h a t , sowie Erfahrungen a u s Vorlesungen und Seminaren, die der Autor a m Institut für Informatik der Universität Hildesheim und am Instit u t für Angewandte Informatik und Formale Beschreibungsverfahren der Universität Karlsruhe abgehalten hat, eingegangen. Allen Kolleginnen, Mitarbeiterinnen und S t u d e n t i n n e n an diesen Orten, die durch ihre kritischen Anmerkungen und ihre Diskussionsbereitschaft oder durch ihre technische und organisatorische Unterstützung an der E n t s t e h u n g dieses Textes mitgeholfen haben, sei an dieser Stelle gedankt. Der besondere Dank des Autors gilt Herrn Professor Dr. G. Vossen, dessen konstruktiv-kritische Durchsicht eines ersten Manuskriptes zu einer sowohl inhaltlichen als auch äußerlichen Verbesserung des Textes geführt h a t . Herrn Dr. G. Demarest und Frau S. Spitzer vom Oldenbourg Verlag sei gedankt für die gewährte Unterstützung und Geduld während der gesamten Dauer des Buch-Projektes. Den Graphikerinnen des Verlages sei gedankt für das Anfertigen der Abbildungen. Schließlich d a n k t der Autor herzlich Heike, Sarah, Dominic u n d Philipp für ihre
Vorwort
XIII
Bereitschaft, den für die Erstellung des Buches notwendigen Zeitaufwand zu tolerieren, und er bittet sie um Entschuldigung dafür, unzählige Male ihren Wunsch nach gemeinsamen Unternehmungen abgelehnt zu haben.
Bedburg, im Januar 1992
Kurt-Ulrich Witt
Kapitel 1 Einführung In diesem einführenden Kapitel wird zunächst ein kurzer historischer Rückblick auf die Entwicklung höherer Programmiersprachen gegeben, dabei wird insbesondere auf die Situation und die Anforderungen eingegangen, die zur Entwicklung der Programmiersprache Simula geführt haben, die als " U r a h n i n " der objektorientierten Programmiersprachen gilt. Dabei werden Gründe dafür angegeben, warum eine mit so "modernen" Konzepten versehene Programmiersprache zur Zeit ihrer E n t s t e h u n g keine große Verbreitung gefunden hat, sondern sich ihrer erst in den letzten J a h r e n im Z u s a m m e n h a n g mit den Diskussionen über Objektorientierung wieder erinnert worden ist. Anschließend werden aus der Sicht von Anforderungen an Programmiermethoden u n d Programmiersprachen, die sich aus verschiedenen Bedürfnissen und Anwendungsbereichen ergeben, objektorientierte Konzepte als eine mögliche Antwort darauf vorgestellt, was d a n n im Laufe des Textes begründet werden soll. Im letzten Teil des Kapitels werden mit Hilfe eines kleinen Beispiels einige grundlegende Konzepte der objektorientierten Programmierung informell erläutert sowie einige Unterschiede zwischen objektorientierter und prozeduraler Prog r a m m i e r u n g diskutiert. Dies soll dazu dienen, über die in den späteren Kapiteln formaler behandelten Begriffe der objektorientierten Programmierung bereits eine gewisse Vorstellung zu entwickeln, die beim weiteren Verständnis der Konzepte hilfreich ist.
1.1
E n t s t e h u n g v o n Simula
Die elektronische Datenverarbeitung wurde zunächst für die Implementierung mathematischer (numerischer) Verfahren im wissenschaftlich-technischen Bereich 1 ein1 D a ß elektronische D a t e n v e r a r b e i t u n g s s y s t e m e für Einsätze im militärischen Bereich entwickelt bzw. in diesem Bereich eingesetzt w u r d e n (und werden), soll an dieser Stelle nicht verschwiegen werden. Da diese T a t s a c h e wohl grundsätzlich für einen großen Anteil (jeweils neuer) wissenschaftlich-technischer Entwicklungen gilt, wird im folgenden an e n t s p r e c h e n d e n Stellen nicht wieder jedes Mal explizit d a r a u f h i n g e w i e s e n , sondern Entwicklung für den und Einsatz im
2
1 Einführung
gesetzt. Ein Ziel für die Entwicklung elektronischer Rechnersysteme war es, Werkzeuge zu entwickeln, welche Wissenschaftlerinnen und Ingenieurinnen bei der Durchf ü h r u n g aufwendiger, im Prinzip maschinell ausführbarer Berechnungen entlasten. 2 Die höhere Geschwindigkeit u n d die höhere Genauigkeit, die bei der Ausführung numerischer Algorithmen durch elektronische Rechnersysteme erreicht werden können, und die mit menschlichen Rechnerinnen nicht oder nur mit nicht v e r t r e t b a r e m Aufwand erreicht werden könnten, f ü h r t e n zudem dazu, daß neue Aufgabenstellungen in Angriff genommen werden konnten. Ein Merkmal numerischer Berechnungen ist, daß in der Regel auf einfachen, homogenen D a t e n s t r u k t u r e n (ganze u n d reelle Zahlen, Vektoren, Matrizen) komplex s t r u k t u r i e r t e Algorithmen operieren. Dieser T a t b e s t a n d spiegelt sich in den K o n s t r u k t e n der Programmiersprache Fortran wider, die als erste höhere Programmiersprache gerade f ü r die Implementierung numerischer Verfahren entwickelt wurde. Die ersten Versionen von Fortran u n t e r s t ü t z t e n ausschließlich die oben genannten D a t e n s t r u k t u r e n und ließen — wegen der uneingeschränkten Verwendungsmöglichkeit der Sprunganweisung — die Programmierung von P r o g r a m m e n mit beliebigem Kontrollfluß zu ("Spaghetti-Programme"). Mittlerweile ist Fortran unter a n d e r e m dahingehend weiterentwickelt worden, daß strukturiertes Programmieren u n t e r s t ü t z t wird und die uneingeschränkte Anwendung der Sprunganweisung nicht mehr möglich ist. Zukünftige Fortran-Versionen werden zudem Elemente enthalten, welche die Programmierung paralleler Algorithmen unterstützen bzw. die Ausnutzung der Möglichkeiten paralleler Rechnerarchitekturen gestatten. Tatsache ist, daß h e u t z u t a g e der weitaus größte Anteil mathematischer Software-Systeme in Fortran programmiert ist, und daß damit ein Großteil der Aufwendungen in diesem Bereich durch die W a r t u n g und Weiterentwicklung dieser Systeme entstehen. Die Frage, inwieweit hierbei Konzepte und Methoden der objektorientierten P r o g r a m m i e r u n g sinnvoll verwendet werden können, wird später behandelt. Die Nutzungsmöglichkeiten der elektronischen Datenverarbeitung im kommerziell-administrativen Bereich wurden ebenfalls alsbald erkannt. Anwendungen in diesem Bereich, wie z. B. Personalverwaltung, Gehaltsabrechnung, Bestellwesen oder B u c h h a l t u n g , zeichnen sich in der Regel dadurch aus, daß große Datenmengen verwaltet und verarbeitet werden, deren Elemente inhomogen strukturiert sind, d. h. die Elemente (Sätze, Records) bestehen aus K o m p o n e n t e n unterschiedlichen Typs u n d können selbst wieder strukturiert sein. Die Algorithmen, welche auf diesen D a t e n s t r u k t u r e n operieren, besitzen in der Regel keine komplexe Ablaufwissenschaftlich-technischen Bereich schließt Entwicklung für den u n d Einsatz im militärischen Bereich mit ein. 2 E i n e u r s p r ü n g l i c h e B e d e u t u n g des englischen W o r t e s Computer ist Rechenknecht. R e c h e n k n e c h t e f ü h r e n mit Hilfe von Tabellenwerken u n d jeweils zur V e r f ü g u n g s t e h e h e n d e n mechanischen oder maschinellen Hilfsmitteln Berechnungen d u r c h .
1 . 1 Entstehung von Simula
3
s t r u k t u r . Dieser T a t b e s t a n d spiegelt sich in der speziell für die Programmierung im kommerziell-administrativen Bereich entwickelte Programmiersprache Cobol wider. Sie u n t e r s t ü t z t insbesondere die Deklaration von strukturierten Variablen, welche oben beschriebene strukturierte Daten aufnehmen können, sowie die Prog r a m m i e r u n g der Ein- und Ausgabe von Mengen solcher Daten. Analog zum wissenschaftlich-technischen Bereich und zu Fortran gilt, daß im administrativ-kommerziellen Bereich ein Großteil der sich dort im Einsatz befindlichen Software-Systeme in Cobol programmiert ist, obwohl hier außerdem Anwendungssysteme unterschiedlichster Art (z. B. Reportgeneratoren, Datenbankm a n a g e m e n t s y s t e m e , Anwendungsentwicklungssysteme) Verwendung gefunden haben u n d zunehmend verwendet werden. Ein weiterer Anwendungsbereich, der sich die Möglichkeiten der elektronischen D a t e n v e r a r b e i t u n g zu Nutze gemacht hat und zur Entwicklung der Urahnin der objektorientierten Programmiersprachen, Simula, geführt h a t , ist der Bereich der c o m p u t e r u n t e r s t ü t z t e n Simulation von (ereignisgesteuerten) soziologischen oder technischen Systemen. Ein Ausgangspunkt für die Entwicklung von Simula war, eine Programmiersprache für die Programmierung von Simulationsmodellen für die Anwendungsfälle zur Verfügung zu haben, für die keine geeigneten mathematischen Modelle (z. B. Differential-, Integralgleichungssysteme) zur Verfügung stehen. A n s t a t t durch ein "übliches" mathematisches Modell werden die zu simulierenden P h ä n o m e n e , d. h. die O b j e k t e und Prozesse der Anwendungswelt und ihr Zusammenwirken, unmittelbar im Simulationsprogramm repräsentiert. 3 Bei diesem Ansatz wird ein zu modellierendes System als eine Menge kooperierender O b j e k t e (zunächst wurde hier der Begriff Prozesse verwendet) aufgefaßt. O b j e k t e sind die atomaren Bestandteile eines solchen Systems. Ein O b j e k t besteht aus lokalen Daten, welche jeweils seinen aktuellen Zustand repräsentieren, und aus lokalen Prozeduren, welche seine Verhaltensmöglichkeiten definieren und welche allein die lokalen Daten ändern können. Ein Objekt ist also eine in sich geschlossene Einheit, deren Zustand von außen, d. h. von anderen O b j e k t e n , nicht unmittelbar verändert werden kann. Ein Objekt kann allerdings von einem anderen O b j e k t dazu aufgefordert werden, durch die Aktivierung eigener Prozeduren eine Z u s t a n d s ä n d e r u n g zu bewirken. Eine b e s t i m m t e externe Aufforderung kann also dazu f ü h r e n , daß ein O b j e k t mit einem bestimmten Verhalten reagiert, welches in einer Ä n d e r u n g seines eigenen Zustandes und außerdem in einer Reaktion nach außen, d. h. in einer Aufforderung an ein anderes Objekt oder in einer Antwort an den Absender der ursprünglichen Aufforderung, resultieren. Dieses Grundprinzip objektorientierter Konzepte basiert auf der Beobachtung, daß z. B. G r u p p e n menschlicher Individuen oder Gesellschaften, welche aus unter3 I ) e r k o n k r e t e Anwendungsfall, der der A u s g a n g s p u n k t für die Entwicklung von Simula war, waren S i m u l a t i o n e n , die E n d e der vierziger J a h r e in Norwegen d u r c h g e f ü h r t w u r d e n , u m den D u r c h m e s s e r f ü r die U r a n s t ä b e des ersten norwegischen K e r n r e a k t o r s zu b e s t i m m e n
4
1 Einführung
schiedlichen Gruppen bestehen, oder technische Systeme, welche aus verschiedenen Komponenten bestehen, in folgender Art und Weise miteinander interagieren: Die atomaren Einheiten (Individuen, Gruppen, Komponenten) befinden sich jeweils in einem bestimmten (physischen, psychischen, sozialen, wirtschaftlichen oder technischen) Zustand, besitzen bestimmte Verhaltensweisen und können mit anderen Einheiten kommunizieren. Eine Kommunikation mit einer Einheit kann bei dieser zu einer Reaktion führen, welche ihren Zustand ändert und ihrerseits zu einer Kommunikation mit anderen Einheiten führt. Mit Hilfe von sogenannten Klassendefinitionen kann in Simula festgelegt werden, welche Zustände und welches Verhalten die Objekte besitzen können, die inhaltlich zusammengehören. Konkrete Objekte einer Klasse können durch entsprechende Simula-Sprachkonstrukte dynamisch erzeugt und gelöscht werden. Simula selbst ist eine Erweiterung von Algol, eine Programmiersprache, die in ihrem ersten Entwurf zu Beginn der sechziger Jahre vorgestellt wurde und die bereits viele Konzepte beinhaltete, die erst später im Zusammenhang mit der strukturierten Programmierung diskutiert worden sind. Algol ist aufwärtskompatibel zu Simula, d. h. Algol-Programme können von einem Simula-Compiler übersetzt werden. Simula konnte und kann auf vielen unterschiedlichen Rechnersystemen kompatibel installiert werden. Ermöglicht wird dies durch die Common Base Language, mit welcher Simula auf den Systemen implementiert wird. Simula selbst bleibt dadurch unberührt und steht auf allen Systemen in gleicher Art und Weise zur Verfügung. Trotz ihrer "modernen", objektorientierten Konzepte hat Simula keine große Verbreitung gefunden. Dafür mögen unter anderem die folgenden Gründe ausschlaggebend gewesen sein: • Methoden für den Software-Entwurf und für die Programmierung waren in den sechziger und siebziger Jahren weitgehend geprägt durch prozedurales, Programmiersprachen- und prozessorabhängiges Denken. Der Einsatz einer objektorientierten Entwurfsmethode bzw. die Verwendung einer objektorientierten Programmiersprache zur Realisierung von Anwendungssystemen verlangt jedoch ein anwendungsorientiertes und nicht-prozedurales Vorgehen. • Mit der Entwicklung "neuerer" Programmiermethoden und Programmiersprachen beschäftigten sich nur wenige Wissenschaftlerinnen, hauptsächlich im universitären Bereich. Eine Verbreitung neuer Methoden und Werkzeuge außerhalb dieses Bereichs war aufgrund des gesamten Entwicklungsstandes der Informatik und der elektronischen Datenverarbeitung zu diesem Zeitpunkt nicht möglich. Die Verantwortlichen waren hinreichend mit der Bewältigung der Probleme der sich in den Kinderschuhen befindlichen und schnell wachsenden elektronischen Datenverarbeitung beschäftigt. Zudem verhinderte der für diese Zeit relativ hohe Preis die Anschaffung von Simula als "Spielsystem", welches möglicherweise den Wunsch des einen oder anderen Anwenders zu einem professionellen Einsatz von Simula geweckt h ä t t e .
5
1 . 1 Entstehung von Simula
• F ü r die Programmierung von Simulationen (technischer, soziologischer wie ökonomischer Systeme) wurden, nicht zuletzt wegen der schon genannten Gründe, Programmiersprachen wie Fortran oder Assembler-Sprachen eingesetzt oder Simulationssprachen verwendet, die speziell — teilweise auf der Basis von Fortran oder Assembler-Sprachen — für die Programmierung von Simulationsprogrammen entwickelt worden waren. Erst in den letzten Jahren ist im Zusammenhang mit den Diskussionen über Objektorientierung das Interesse an Simula geweckt worden, und zwar nicht nur im Bereich von Forschung und Entwicklung, sondern auch in Anwendungsbereichen. Es ist aber sicher nicht zu erwarten, daß Simula nun eine weite Verbreitung finden wird, denn mittlerweile stehen andere objektorientierte Programmiersprachen und Anwendungssysteme zur Verfügung, welche über Simula hinausgehende objektorientierte Konzepte umfassen und zudem kompatibel zu existierenden, sich in der Anwendung befindlichen Software-Systemen sind. Auf die der objektorientierten Programmierung zugrunde liegenden Konzepte sowie auf Simula und weitere objektorientierte Programmiersprachen wird in späteren Kapiteln noch detaillierter eingegangen. Zu einer ersten Orientierung wird i m folgenden ein kleines Simula-Beispiel angegeben: Die Klassendeklaration c l a s s Kreis(X, Y, Radius); real X, Y, Radius; begin real P i ; r e a l p r o c e d u r e Fläche; begin Fläche := Radius * Radius * Pi e n d Fläche; procedure Skaliere(Faktor); real Faktor; begin Radius := Radius * Faktor end Skaliere; procedure Verschiebe(DifX, DifY); real DifX, DifY; begin X := X + DifX; Y := Y + DifY e n d Verschiebe; P i := 3 . 1 4 end Kreis; definiert die Klasse Kreis.
Jedes Objekt dieser Klasse besitzt die Zustände X
1 Einführung
6
u n d Y sowie Radius, das sind die kartesischen K o o r d i n a t e n seines M i t t e l p u n k t e s bzw. sein R a d i u s . Jedes O b j e k t kann auf diesen Z u s t ä n d e n die O p e r a t i o n Fläche, die seinen F l ä c h e n i n h a l t liefert, die O p e r a t i o n Skaliere, die seinen R a d i u s u m einen F a k t o r skaliert, und die O p e r a t i o n Verschiebe, die seinen M i t t e l p u n k t u m den B e t r a g DifX in x-Richtung u n d u m den B e t r a g DifY in y - R i c h t u n g verschiebt, a u s f ü h r e n . N u r diese O p e r a t i o n e n können auf die Z u s t ä n d e zugreifen bzw. k ö n n e n diese ä n d e r n . Ein u n m i t t e l b a r e r Zugriff auf diese Z u s t ä n d e von außen ist nicht möglich. J e d e s Kreis-Objekt besitzt außerdem d e n (lokalen) Z u s t a n d Pi. Bei Generier u n g eines Kreis-Objektes wird diesem Z u s t a n d der Wert 3 . 1 4 zugewiesen. U m ein konkretes K r e i s - O b j e k t zu erzeugen, müssen z u n ä c h s t s o g e n a n n t e Referenzvariable, welche Zeigervariable auf die O b j e k t e darstellen, deklariert w e r d e n . Die D e k l a r a t i o n ref (Kreis) K; definiert die Referenzvariable K vom T y p Kreis. Durch K :- n e w K r e i s ( l , 2, 3) ; wird ein Kreis-Objekt m i t d e m M i t t e l p u n k t ( 1 , 2 ) u n d d e m R a d i u s 3 erzeugt u n d der Variablen K zugewiesen. Bei der G e n e r i e r u n g wird z u d e m Pi der W e r t 3 . 1 4 zugewiesen. Der A u s d r u c k K.Fläche wird z u m aktuellen F l ä c h e n i n h a l t von K, also 3 * 3 * 3 . 1 4 , a u s g e w e r t e t . Aufruf
Der
K. S k a l i e r e t ) v e r d o p p e l t den R a d i u s von K, u n d der Aufruf K.Verschiebe(4, 5) verschiebt d e n M i t t e l p u n k t von K von ( 1 , 2) nach ( 5 , 7 ) . K . F l ä c h e w ü r d e n u n zu 6 * 6 * 3 . 1 4 ausgewertet.
1.2
A n f o r d e r u n g e n an S o f t w a r e - S y s t e m e
E r s t die Entwicklung der und die Veröffentlichungen zu d e n S m a l l t a l k - S y s t e m e n E n d e der siebziger, A n f a n g d e r achtziger J a h r e h a t o b j e k t o r i e n t i e r t e K o n z e p t e
1.2 Anforderungen an Software-Systeme
7
in den M i t t e l p u n k t von intensiven, vielfältigen und weit verbreiteten Forschungsund Entwicklungsarbeiten gerückt. Seit dieser Zeit sind viele neue objektorientierte Programmiersprachen und Anwendungssysteme entwickelt oder bereits existierende Programmiersprachen und Anwendungssysteme als objektorientiert deklariert oder mit zusätzlichen objektorientierten Eigenschaften versehen worden. Objektorientierte Konzepte sind in viele andere Bereiche der Informatik eingeflossen, wie z. B. in die Gestaltung von Benutzerschnittstellen und Arbeitsplatzsystemen, in die Entwicklung von Datenbanksystemen f ü r sogenannte Nicht-Standard-Anwendungen oder in die Entwicklung von Formalismen zur Wissensrepräsentation im R a h m e n der "Künstlichen Intelligenz". Dabei ist festzustellen, daß die Begriffe weder innerhalb der verschiedenen Bereiche noch über die Bereiche hinweg in einheitlicher Weise verwendet werden. Erst in jüngster Zeit werden innerhalb der einzelnen Disziplinen Versuche und Vorschläge in Richtung einer Vereinheitlichung unternommen. Im folgenden soll auf einige der erwähnten Bereiche näher eingegangen und begründet werden, warum den in diesen Bereichen entstehenden Anforderungen mit Hilfe von objektorientierten Konzepten begegnet werden kann.
1.2.1
B e n u t z e r s c h n i t t s t e l l e n und A r b e i t s p l ä t z e
Die Entwicklung eines leistungsfähigen, benutzerfreundlichen und individuell anpaß baren Arbeitsplatzsystems war das Ziel der Aktivitäten, die zum Smalltalk80-System g e f ü h r t haben. Der Endbenutzer soll die Möglichkeit haben, zur Unt e r s t ü t z u n g seiner täglichen Arbeit seine A r b e i t s u m g e b u n g leicht und a d ä q u a t auf das System abbilden und neuen Anforderungen anpassen zu können, d. h. so etwas wie einen "persönlichen Assistenten" zur Verfügung zu haben. Erreicht werden sollte diese Flexibilität durch Verwendung objektorientierter Konzepte, wie sie bereits in Simula vorhanden waren — die gerade erwähnte Nachbildung des Arbeitsplatzes kann auch als Simulation des Arbeitsplatzes aufgefaßt werden, sowie durch weitere im Zuge der Entwicklung von Smalltalk entstandene Konzepte, die h e u t e ebenfalls als objektorientiert bezeichnet werden. Im SmaIltalk-80-System ist Objektorientierung als einheitliches Systemkonzept und in seiner wohl reinsten Form verwirklicht. Nicht nur die Programmiersprache Smalltalk, die das System zur Verfügung stellt, ist objektorientiert, sondern das g e s a m t e System, d. h. Komponenten wie das Betriebssystem, die Benutzeroberfläche (Fenster, Editoren, Text, Graphik), Interpreter, Debugger, Dateiverwaltung, Basisobjekte sind objektorientiert konzipiert und realisiert. O b j e k t e von benutzerdefinierten Klassen und O b j e k t e von Klassen, die in der reichhaltigen Klassenbibliothek zur Verfügung stehen und die die Benutzerinnen nach Bedarf generieren können, können in vielfältiger Weise interagieren, so daß die Entwicklung flexibler Systeme möglich ist. Trotz dieser Möglichkeiten sind die ursprünglichen, oben erwähnten Ziele mit dem Smalltalk-80-System sicherlich nicht erreicht worden. Dazu muß der End-
8
1 Einführung
benutzer noch zu viele Systemkenntnisse haben, um effizient arbeiten zu können. Das Smalltalk-80-System hat aber wesentlich dazu beigetragen, daß mittlerweile vielfältige Anstrengungen unternommen werden, um die gesteckten Ziele zu erreichen. Programmiersprachen der vierten Generation, komfortable Tabellenkalkulationssysteme, Werkzeuge für die Entwicklung von Prototypen, Window-Systeme oder Hypercard-Systeme sind — teilweise beeinflußt von der Smalltalk-Entwicklung — weitere Ansätze auf dem Weg zu Endbenutzerinnen-gerechten Systemen. Software-Entwerferinnen und Programmiererinnen sind schon seit langer Zeit nicht in der Lage, und sie werden es auch in Zukunft nicht sein, die ständig an Quantität wie an Qualität gewachsenen und weiter wachsenden Anforderungen und Wünsche nach Endbenutzerschnittstellen auch nur annähernd zu befriedigen. 4 Diese Situation kann nur dadurch verbessert werden, daß den Endbenutzerinnen Werkzeuge an die Hand gegeben werden, mit denen sie sich Schnittstellen der oben beschriebenen Art für ihren Aufgabenbereich zurechtschneidern können. Dies sollte weitestgehend ohne Kenntnisse der Werkzeug- und System-Interna möglich sein. Sie müssen die Komplexität ihrer Anwendungen bewältigen können, ohne noch zusätzlich die Komplexität von System-Software und System-Prozessen beherrschen zu müssen. Objektorientiert konzipierte und realisierte Werkzeuge stellen eine Möglichkeit dar, diese Ziele zu erreichen. Vordefinierte Klassen und umfangreiche flexibel und dynamisch änderbare Klassenbibliotheken, möglicherweise — zumindest für bestimmte Anwendungsfälle — standardisiert, ermöglichen den Endbenutzerinnen, ihren Bedürfnissen entsprechende Arbeitsplatzsysteme zu realisieren, welche die interne Systemkomplexität verbergen, aber die Modellierung der Anwednungskomplexität in einfacher und flexibler Weise unterstützen.
1.2.2
Multimediasysteme
Die Komplexität von Anwendungen wächst nicht nur in dem Sinne, daß die Benutzerinnen immer komplexere und aufwendigere Funktionen benötigen, sondern auch in dem Sinne, daß die zu speichernden und zu verwaltenden Daten komplexer werden. Insbesondere sollen die aus der Anwendersicht komplex strukturierten Daten je nach Bedarf als atomare Einheiten definiert, gespeichert und verwaltet werden können. Zugriffe auf diese "Objekte" brauchen also nicht, wie es in prozeduralen Programmen üblich und notwendig ist, im Anwendungsprogramm durch Zugriffe auf ihre atomaren Bestandteile und ihre anschließende Rekonstruktion aus diesen Bestandteilen programmiert zu werden. Die interne Komplexität der Objekte bleibt der Anwendersicht also auch hier verborgen. Die Datenstrukturen sind dabei nicht nur "klassischen" Typs, d. h. aus einfachen Standardtypen, wie z. B. integer, real, char, boolean, mit Hilfe von Konstruktoren, wie z. B. array, record, set, aufgebaut, sondern komplex strukturierte ''Diese Situation wird auch als " A n w e n d u n g s s t a u " bezeichnet.
1.2 Anforderungen an Software-Systeme
9
D a t e n , wie z. B . T e x t e , Graphiken, Bilder, T ö n e oder Bewegtbilder. D a t e n , die heutzutage noch mit unterschiedlichen Techniken und auf unterschiedlichen Systemen implementiert sind, werden in zunehmendem Maße auf einheitlichen Systemen integriert implementiert werden können. Die Entwicklung von solchen Anwendungssystemen wird einerseits hohe Anforderungen an die Hardware-Ausstattung stellen (Bildschirmauflösung, Mustererkennung, Bildverarbeitung, Audio- und Videotechnik), andererseits den Endbenutzerinnen leicht erlernbare und leicht nutzbare Schnittstellen zur Verfügung stellen müssen, welche eine einheitliche, integrierte Verwendung und Bearbeitung von M u l t i m e d i a - O b j e k t e n unterstützen. Auch für den Entwurf und die Realisierung dieser S y s t e m e kann die Anwendung o b j e k t o r i e n t i e r t e r K o n z e p t e hilfreich sein. So erlaubt z. B . die Eigenschaft o b j e k t o r i e n t i e r t e r S y s t e m e , die Uberladen genannt wird und später noch eingehender erläutert wird, daß unterschiedliche O b j e k t e auf ein und dieselbe Nachricht unterschiedlich reagieren können. So mögen z. B . einige O b j e k t e a u f die Nachricht Ausgeben mit der Ausgabe einer Bewegtbildsequenz reagieren, andere O b j e k t e hingegen mit dem Abspielen eines Musikstückes.
1.2.3
Datenbanksysteme
Insbesondere in technisch-wissenschaftlichen Bereichen, aber auch z. B . in Büroumgebungen besteht der B e d a r f danach, komplex strukturierte O b j e k t e , wie z. B . Landkarten oder Konstruktionspläne, aber auch M u l t i m e d i a - O b j e k t e in Datenbanken verwalten zu können. Auch hier sollten die O b j e k t e der Anwendungswelten als a t o m a r e Einheiten verwaltet werden können. Es müssen also Datenbanksprachen zur Verfügung stehen, mit denen auf diese O b j e k t e direkt zugegriffen werden kann, d. h., es ist nicht notwendig, komplexe Anfragen formulieren oder gar Anwendungsprogramme schreiben zu müssen, um auf komplex strukturierte O b j e k t e zugreifen zu können. Ein objektorientiertes D a t e n b a n k m a n a g e m e n t s y s t e m stellt diese Dienstleistungen den Benutzerinnen zur Verfügung, wobei die System-Interna verborgen bleiben. Die abgespeicherten O b j e k t e bestehen dabei nicht nur aus komplex strukturierten D a t e n , sondern besitzen auch ein Verhalten. In einem Landinformationssystem könnten z. B . Polygone die grundlegenden zu verwaltenden O b j e k t e sein, wobei Polygone Landschaftsgebiete oder Stadtgebiete repräsentieren. Zum Verhalten dieser O b j e k t e könnte es z. B . gehören, ihre F l ä c h e zu berechnen oder sich (für die A u s g a b e ) zu färben. Dienstleistungen, die außerdem von einem objektorientierten Datenbankman a g e m e n t s y s t e m zu erbringen sind und die über die von Multimediasystemen erbrachten Dienstleistungen hinausgehen, sind z. B . • die Konsistenzüberwachung der O b j e k t e : Hierzu gehört die automatische Überwachung der von den Benutzerinnen für die zu verwaltenden O b j e k t e festgelegten Integritätsbedingungen. Die Überwachung kann mit Hilfe von
10
1 Einführung
" D ä m o n e n " oder "Triggern", deren Implementierung von objektorientieren Konzepten u n t e r s t ü t z t wird, realisiert werden. Generell muß die Konsistenz der Datenbank gewährleistet sein, d. h. den Benutzerinnen ist garantiert, daß sie immer auf einem konsistenten Zustand arbeiten. • die Gewährleistung eines effizienten, sicheren Mehrfachbenutzerlnnen-Betriebs: Dazu ist ein Transaktionskonzept erforderlich, welches (quasi-) parallele, effiziente Zugriffe auf die Objekte realisiert und dabei ihre Konsistenz bewahrt. • eine Haupt- u n d Sekundärspeicherorganisation, welche den effizienten Zugriff auf die abgespeicherten Objekte realisiert. In jüngerer Zeit werden unter dem Begriff intelligente Datenbanksysteme Konzepte f ü r die Integration von konventionellen Datenbanksystemen, Dokumentennachweissystemen (Information-Retrieval-Systeme), Multimediasystemen, deduktiven Datenbanksystemen, objektorientierten Datenbanksystemen sowie Expertensystemen entwickelt.
1.2.4
B e t r i e b s s y s t e m e und verteilte S y s t e m e
Die Kommunikationstechnologie ermöglicht es prinzipiell, weltweit verteilte Hardund Software-Systeme zu nutzen. Erschwert wird die Nutzung in vielen Fällen allerdings durch den großen Aufwand, der getrieben werden muß, um die zur Verfügung stehenden Ressourcen in Anspruch zu nehmen. Der Grund dafür liegt wesentlich darin, daß den Systemen, insbesondere den Betriebssystemen und den Systemprogrammen, monolithische und unflexible Software-Architekturen zugrunde liegen. Nur eine vertiefte Kenntnis dieser Architekturen ermöglicht ihre Nutzung. Diese Situation kann mit Hilfe objektorientierter Konzepte überwunden werden. In Ebenen geschichtete Systeme, von deren Komponenten nicht bekannt sein muß, wie sie intern realisiert sind, um ihre Dienstleistungen zu nutzen, sondern lediglich ihre (Export-) Schnittstellen bekannt sein müssen, verringern wesentlich den Aufwand für ihre Nutzung. Dabei bleibt für die Endbenutzerinnen vollkommen transparent, wie die Systeme und ihre Komponenten verteilt sind.
Schlußbemerkung Die Beobachtung der aktuellen Forschungs- und Entwicklungsaktivitäten, welche sich mit der theoretischen Fundierung objektorientierter Konzepte bzw. mit der Konzipierung und Realisierung objektorientierter Systeme für oben erwähnte und weitere Anwendungsbereiche befassen, läßt die Prognose zu, daß in den nächsten J a h r e n die im folgenden genannten Anwendungssysteme in der aufgeführten Reihenfolge jeweils im A b s t a n d von drei bis fünf J a h r e n als objektorientierte Systeme
1.3 Ein Beispiel
11
kommerziell zur Verfügung stehen und Eingang in viele Anwendungsbereiche finden werden: •
Programmiersprachen,
•
Entwicklungswerkzeuge,
•
Datenbanksysteme,
•
Betriebsysteme,
• umfangreiche Klassenbibliotheken für Endbenutzer bzw. für dedizierte Anwendungen.
1.3
E i n e i n f ü h r e n d e s Beispiel
Z u m Abschluß des einleitenden Kapitels soll die Aufgabenstellung "Primzahlberechnung nach der M e t h o d e Sieb des Eratosthenes" objektorientiert behandelt werden. Dies soll wie das kleine Beispiel a m Ende von Kapitel 1.1 dabei helfen, eine erste Vorstellung über objektorientierte Programmierung zu bekommen. Nach der M e t h o d e Sieb des Eratosthenes werden die Primzahlen bis zu einer vorgebenen Obergrenze n € N 5 in der folgenden Weise b e s t i m m t : (1) Schreibe alle Zahlen von 1 bis n auf und streiche die Zahl 1 durch. (2a) Sei i die kleinste noch nicht durchgestrichene und nicht eingerahmte Zahl. R a h m e i ein und streiche alle Vielfachen von i durch. (Die Vielfachen von i werden "ausgesiebt".) (2b) Wiederhole (2a) so lange, bis i * i > n ist. (3) Die eingerahmten und die nicht durchgestrichenen Zahlen sind die Primzahlen von 1 bis n. Diese Version des Sieb des Eratosthenes dermaßen implementiert:
wird prozedural in der Regel folgen-
(1) Die Komponenten eines n-elementigen Booleschen Arrays werden mit t r u e initialisiert, die erste K o m p o n e n t e wird mit f a l s e initialisiert. (2) In einer Schleife, in welcher ein Zähler i von 2 bis [^/n] ([z] bezeichnet den ganzzahligen Anteil von z) läuft, wird jeweils getestet, ob der Wert der Komponente i. noch t r u e ist. Falls das der Fall ist, werden die Werte aller Komponenten, deren Indizes Vielfache von i sind, auf f a l s e gesetzt. 5
N bezeichnet die M e n g e der natürlichen Zahlen, N = { 1 , 2 , 3 , . . . } .
12
1 Einführung
(3) Die Indizes der Komponenten, deren Werte true sind, werden ausgegeben u n d sind gerade die Primzahlen unter den Zahlen von 1 bis n. Diese Lösung hat einerseits den Nachteil, daß sie nur für ein festes n implementiert werden kann. Andererseits benötigt ihre Implementierung viel im Prinzip unnötigen Speicherplatz. Die Anzahl der Primzahlen ist sehr gering im Vergleich zur Anzahl der Nicht-Primzahlen zwischen 1 und n. In der obigen Lösung werden aber alle Zahlen explizit während der gesamten Prozedur im Speicher repräsentiert. Es könnten sehr viel mehr Primzahlen berechnet werden, wenn nur diese und die anderen Zahlen nicht gespeichert werden müßten. Obwohl in einer praktischen Implementierung obige Prozedur wohl nicht in weitere Teilprozeduren zerlegt würde, soll dies trotzdem gemacht werden, u m weiter unten nach der Vorstellung einer objektorientierten Lösung bereits an diesem kleinen Beispiel einige wesentliche Unterschiede zwischen prozeduraler und objektorientierter P r o g r a m m i e r u n g herauszustellen. Eine Teilprozedur würde die Initialisierung entsprechend dem Schritt (1) übernehmen, eine weitere das "Sieben", d. h. den Schritt (2), und eine dritte die Ausgabe, also den Schritt (3). Wichtig an dieser Stelle ist die Beobachtung, daß, wie gewöhnlich bei der prozeduralen Programmierung, die Prozeduren und die Daten vollkommen voneinander getrennt sind. In diesem Fall operieren die drei (Teil-) Prozeduren auf einer D a t e n s t r u k t u r (siehe Abbildung 1.1). Ein objektorientierter Ansatz geht davon aus, daß es keine Trennung von Daten und auf ihnen operierenden Prozeduren gibt, sondern nur O b j e k t e . O b j e k t e sind atomare, gekapselte Einheiten, die jeweils einen Zustand und ein Verhalten besitzen. Der Zustand wird durch Datenstrukturen, das Verhalten durch Prozeduren bestimmt. Wenn das Sieb des Eratosthenes aus dieser Sicht betrachtet wird, kann eine objektorientierte Lösung z. B. folgendermaßen aussehen: (1) Ein O b j e k t Eratosthenes erlaubt den Benutzerinnen den S t a r t des P r o g r a m m s sowie die Eingabe der Zahl, bis zu welcher die Primzahlen bestimmt werden sollen. (2) Es gibt ein Erzeugerobjekt, welches als Zustand Werte natürlicher Zahlen annehmen und das auf eine Aufforderung hin seinen aktuellen Zustand um 1 erhöhen und das diesen Wert dem ersten Siebobjekt mitteilen kann. (3) Jede Primzahl wird durch ein Siebobjekt repräsentiert, dessen Zustand gerade die Primzahl ist, die dieses Objekt repräsentiert, und dessen Verhalten im "Sieben" besteht, d. h. ein Siebobjekt dividiert das Argument einer ankommenden Nachricht, eine natürliche Zahl, durch seinen Wert. Falls der Rest gleich Null ist, kann die Zahl keine Primzahl sein, u n d das Siebobjekt beauft r a g t das Erzeugerobjekt, die nächste Zahl durch das Sieb zu schicken. Falls der Rest ungleich Null ist, schickt das Siebobjekt die Nachricht, also die weiter
13
1.3 Ein Beispiel
Abbildung 1.1: Prozedurale S t r u k t u r des Sieb des
Eratosthenes
14
1 Einführung
zu siebende Zahl, an das Objekt, welches die nächste Primzahl repräsentiert. Falls kein nächstes Siebobjekt existiert, ist die Zahl eine Primzahl, und ein nächstes Siebobjekt mit dem Wert der Zahl als Zustand wird generiert. (4) Jedesmal, wenn ein neues Siebobjekt generiert wird, wird sein Wert an ein Ausgabeobjekt gesendet, welches diesen Wert ausgibt und anschließend das Erzeugerobjekt mit dem Senden der nächsten Zahl an das Sieb beauftragt. Es gibt also vier Klassen von Objekten: Die Klasse Eratosthenes, welche das Benutzerinnen-Interface für das objektorientierte Programm Sieb des Eratosthenes darstellt, die Klasse Erzeuger, von der nur ein Objekt generiert wird, die Klasse Ausgabe, von der ebenfalls nur ein Objekt generiert wird, sowie die Klasse Sieb, von der so viele O b j e k t e generiert werden, wie Primzahlen bestimmt werden. Die Objekte, d. h. ihre Zustandsvariablen sowie ihr Verhalten, welches durch sogenannte Methoden festgelegt wird, werden in entsprechenden Klassendefinitionen festgelegt. Für die genannten Klassen des Beispiels könnten diese wie folgt aussehen: 6 klasse Eratosthenes; methoden Start(Zahl); I n i t ( E r z e u g e r , Zahl) end Start; end Eratosthenes. k l a s s e Erzeuger; NächsteZahl: integer; Obergrenze: integer; Siebanfang: Sieb; methoden Init(Grenze); new (Erzeuger); NächsteZahl := 1; Obergrenze := Grenze; Siebanfang := nil; Inkreraent(Erzeuger) end Init; Inkrement; NächsteZahl := NächsteZahl + 1; if NächsteZahl < Obergrenze t h e n if NächsteZahl = 2 then 6 Die verwendete Syntax ist nicht die Syntax einer existierenden Programmiersprache, sondern die einer gedachten objektorientierten Programmiersprache, in der weitgehend Elemente prozeduraler Programmiersprachen verwendet werden, u m den Leserinnen das Verständnis des P r o g r a m m e s zu erleichtern.
1 . 3 Ein Beispiel
15
Siebanfang := I n i t ( S i e b , NächsteZahl) eise Sieben(Siebanfang, NächsteZahl) eise Drucke (Ausgabe, ' . . . f e r t i g . . . ' ) e n d Inkrement; e n d Erzeuger. klasse Sieb; Primzahl: integer; Nachfolger: Sieb; methoden Init(Zahl); new(Sieb) ; Primzahl := Zahl; Nachfolger := nil; Drucke(Ausgabe, Primzahl) end I n i t ; Sieben(Probant); if Probant m o d Primzahl = 0 t h e n Inkrement(Erzeuger) elseif Nachfolger ^ nil t h e n Sieben(Nachfolger, Probant) eise Nachfolger := I n i t ( S i e b , Probant) e n d Sieben; end Sieb. klasse Ausgabe; methoden Drucke(Wert); writeln(Wert); Inkrement(Erzeuger) end Drucke; e n d Ausgabe. Im folgenden wird informell eine Art "operationale Semantik" für das objektorientierte Programm Sieb des Eratosthenes gegeben, in dem der Programmablauf für die Erzeugung der Primzahlen bis zur Zahl 7 einschließlich geschildert wird. Zur Verdeutlichung der Schilderung gibt Abbildung 1.2 eine graphische Darstellung des Programmablaufs, und Abbildung 1.3 zeigt die Objekte der drei Klassen Erzeuger, Sieb und Ausgabe, ihre Zustände und die versendeten Nachrichten während der Erzeugung der Primzahlen. Die Klasse Eratosthenes erlaubt den Benutzerinnen, das Primzahl-Programm zu starten. In einer objektorientierten Arbeitsumgebung könnte die Erzeugung eines Eratosthenes-Objektes etwa durch Anklicken eines entsprechenden Sym-
16
1 Einführung
Sieben oder Init
Abbildung 1.2: Programmablauf von E r a t o s t h e n e s
17
1.3 Ein Beispiel (a)
(d)
Benutzer
Inkrement
I
1
Eratosthenes
(b) Start (7)
I
Erzeuger Nächste Zahl = 1 Obergrenze = 7 Siebanfang = i
(c)
Inkrement
(e) Erzeuger
Inkrement
I
Nächste Zah = 2 Obergrenze = 7 Siebanfang -
r
Sieb
Primzahl = 2 Nachfolger = 1
Abbildung 1.3: Objekte, ihre Zustände Primzahlerzeugung mit E r a t o s t h e n e s
und
Nachrichten
während
der
18
1 Einführung
(f)
(g)
Inkrement
Inkrement
Erzeuger
Erzeuger
i
l
Nächste Zahl = 6 Obergrenze = 7 Siebanfang —
Nächste Zahl = 5 Obergrenze = 7 Siebanfang —
Sieb
Sieb
Primzahl = 2 Nachfolger -
Primzahl = 2 Nachfolger —
Sieb
Sieb Primzahl = 3 Nachfolger —
Sieb
Primzahl = 5 Nachfolger = 1
Primzahl = 3 Nachfolger —
Sieb
drucke (5)
Primzahl = 5 Nachfolger = 1
Abbildung 1.3: Fortsetzung
1.3 Ein Beispiel
19
(h)
(i)
Inkrement
Inkrement
Erzeuger
Nächste Zahl = 8 Obergrenze = 7 Siebanfang —
l Sieb Primzahl = 2 Nachfolger
i
3
1 Sieb Primzahl = 7 Nachfolger = 1
Abbildung 1.3: Fortsetzung
20
1 Einführung
bols m i t der M a u s in einem Fenster der Benutzeroberfläche geschehen (siehe Abb i l d u n g 1.3(a)). G e s t a r t e t w ü r d e die P r i m z a h l e r z e u g u n g d a n n m i t der E i n g a b e — möglicherweise auch u n t e r s t ü t z t durch Anklicken e n t s p r e c h e n d e r Symbole u n d T e x t e i n g a b e — von Start (Obergrenze), i m Beispiel Start (7). Dies bewirkt d a s S e n d e n der Nachricht Init mit d e m A r g u m e n t 7 a n die Klasse Erzeuger. In d e m E r a t o s t h e n e s - B e i s p i e l ist der e r s t e P a r a m e t e r einer Nachricht, d. h. eines O b j e k t - A u f r u f s , der A d r e s s a t , u n d der zweite P a r a m e t e r ist d a s A r g u m e n t f ü r die a u f g r u n d der Nachricht ausgelöste M e t h o d e , d a s Verhalten, des A d r e s s a t e n . In der e n t s p r e c h e n d e n M e t h o d e k o m m t als P a r a m e t e r n u r einer f ü r d a s A r g u m e n t vor. Die Klasse Erzeuger generiert ein O b j e k t mit demselben N a m e n . Die Variable NächsteZahl dieses O b j e k t e s wird mit 1, die Variable Obergrenze mit 7 u n d die Variable Siebanfang, die Adresse des " e r s t e n " S i e b o b j e k t e s , mit nil initialisiert (siehe A b b i l d u n g 1.3(b)). Anschließend sendet Erzeuger sich selbst die Nachricht Inkrement. Dies bewirkt, d a NächsteZahl = 2 geworden ist, Obergrenze a b e r noch nicht erreicht h a t , d a s Senden der Nachricht Init mit d e m A r g u m e n t NächsteZahl an die Klasse Sieb. Die Adresse des O b j e k t e s , das von Sieb d a r a u f h i n erzeugt werden wird, wird der W e r t der Variablen Siebanfang werden. Sieb generiert das e r s t e SiebO b j e k t mit Primzahl = 1 sowie mit Nachfolger = nil, d a noch keine weiteren S i e b - O b j e k t e existieren. A u ß e r d e m sendet dieses O b j e k t die Nachricht Drucke an das A u s g a b e - O b j e k t , u m die erste Primzahl 2 auszugeben (siehe A b b i l d u n g 1.3(c)). Nach der A u s g a b e sendet d a s A u s g a b e - O b j e k t die Nachricht Inkrement an Erzeuger. Erzeuger erhöht NächsteZahl u m 1 u n d sendet, d a Obergrenze noch nicht erreicht ist, die Nachricht Sieben mit dem A r g u m e n t NächsteZahl an das O b j e k t Siebanfang. Das e r s t e Sieb-Objekt wird d a m i t v e r a n l a ß t , NächsteZahl (= 3) d u r c h seinen Wert Primzahl (= 2) zu dividieren. D a der Rest ungleich Null ist u n d noch kein weiteres Sieb-Objekt existiert — d. h. NächsteZahl ist eine P r i m z a h l — sendet d a s S i e b - O b j e k t die Nachricht Init an die Klasse Sieb mit d e m A r g u m e n t NächsteZahl. Sieb generiert d a r a u f h i n ein neues S i e b - O b j e k t , dessen Adresse I n h a l t von Nachfolger i m ersten S i e b - O b j e k t wird. D a s neu erzeugte SiebO b j e k t sendet die Nachricht Drucke z u m A u s g a b e - O b j e k t , welches nach A u s g a b e der P r i m z a h l 3 die Nachricht Inkrement a n Erzeuger sendet (siehe A b b i l d u n g 1.3(d)). Erzeuger inkrementiert NächsteZahl u n d sendet diese als A r g u m e n t der Nachricht Sieben an d a s erste S i e b - O b j e k t . Dieses dividiert NächsteZahl (= 4) d u r c h ihren W e r t Primzahl. D a der Rest gleich Null ist, ist NächsteZahl keine P r i m z a h l , u n d d a s S i e b - O b j e k t sendet die Nachricht Inkrement an Erzeuger (siehe Abbild u n g 1.3(e)). D e r weitere Ablauf sollte den Leserinnen mit Hilfe der bisherigen Schilderungen, der A b b i l d u n g 1.2 u n d der Abbildungen 1.3(f) und (g) verständlich sein, auf seine D a r s t e l l u n g wird deshalb verzichtet. A n diesem Beispiel zeigen sich bereits einige Vorteile des o b j e k t o r i e n t i e r t e n
1 . 3 Ein Beispiel
21
A n s a t z e s g e g e n ü b e r d e m p r o z e d u r a l e n A n s a t z . Das o b j e k t o r i e n t i e r t e P r o g r a m m Eratosthenes ist dynamisch, d. h. im G e g e n s a t z zur prozeduralen Lösung wird die O b e r g r e n z e , bis zu welcher die P r i m z a h l e n berechnet werden, nicht d u r c h d a s P r o g r a m m b e s c h r ä n k t . Die A n z a h l der S i e b - O b j e k t e , die maximal erzeugt werden k ö n n e n , h ä n g t von Systemgrößen ab, e t w a vom zur Verfügung stehenden Speicherp l a t z . In der p r o z e d u r a l e n Lösung m ü ß t e im P r i n z i p für j e d e O b e r g r e n z e die Größe der D a t e n s t r u k t u r , d. h. die Siebgröße, v e r ä n d e r t werden, etwa durch e n t s p r e c h e n d e Ä n d e r u n g einer K o n s t a n t e n . Auf jeden Fall wird eine neue U b e r s e t z u n g des Prog r a m m s notwendig. Die o b j e k t o r i e n t i e r t e Lösung f ü h r t i m übrigen zu Überlegungen für eine dyn a m i s c h e p r o z e d u r a l e Lösung, i n d e m das Sieb nicht durch ein A r r a y fester Größe realisiert wird, sondern durch eine lineare Liste, in welcher die ausgesiebten P r i m zahlen jeweils hinten a n g e h ä n g t werden. J e d e zu testende Zahl wird d u r c h die Liste geschickt u n d dabei durch jedes E l e m e n t der Liste dividiert. Falls der Rest ungleich Null ist, wird die Zahl z u m n ä c h s t e n Element weitergereicht, falls der Rest gleich Null ist, wird die n ä c h s t e Zahl durch die Liste geschickt. Bei dieser p r o z e d u r a l e n Lösung ist die Obergrenze, bis zu welcher die P r i m z a h l e n berechnet w e r d e n k ö n n e n , ebenfalls nicht d u r c h das P r o g r a m m b e s c h r ä n k t , sondern allenfalls d u r c h Systemgrößen wie die Speichergröße. A u ß e r d e m werden nur die P r i m z a h l e n gespeichert, die anderen Zahlen nicht. Die E n t w u r f s - U b e r l e g u n g e n f ü r ein o b j e k t o r i e n t i e r t e s P r o g r a m m f ü h r e n also zu einem insbesondere i m Hinblick auf D y n a m i k u n d Speicherplatz verbesserten prozeduralen P r o g r a m m . Den O b j e k t e n e n t s p r e c h e n hier die E l e m e n t e der Listen, die allerdings nur D a t e n b e i n h a l t e n , auf d e n e n ( " e x t e r n e " ) P r o z e d u r e n operieren. Ein weiterer wichtiger A s p e k t o b j e k t o r i e n t i e r t e r P r o g r a m m i e r u n g , der auch bereits a n diesem Beispiel deutlich wird, ist code sharing. C o d e sharing b e d e u t e t , d a ß d a s Verhalten der O b j e k t e einer Klasse, d. h. das Reagieren auf Nachrichten, welches im wesentlichen gleich f ü r alle O b j e k t e einer Klasse ist, n u r einmal, u n d zwar in der Klassendefinition, festgelegt wird. Der P r o g r a m m c o d e , der dieses Verh a l t e n i m p l e m e n t i e r t , kann von allen jeweils existierenden O b j e k t e n der Klasse b e n u t z t werden. So b e n u t z e n im obigen Beispiel alle S i e b - O b j e k t e den C o d e der in der Definition der Klasse Sieb definierten M e t h o d e Sieben. Dieser A s p e k t verm e i d e t R e d u n d a n z von P r o g r a m m c o d e u n d u n t e r s t ü t z t die P r o g r a m m w a r t u n g , d a n o t w e n d i g e Ä n d e r u n g e n nur an einer Stelle d u r c h g e f ü h r t werden m ü s s e n . Ein Nachteil o b j e k t o r i e n t i e r t e r P r o g r a m m i e r u n g ist, daß wegen der Verwend u n g d y n a m i s c h e r S t r u k t u r e n die Laufzeiten o b j e k t o r i e n t i e r t e r P r o g r a m m e im allg e m e i n e n größer sind als die Laufzeiten e n t s p r e c h e n d e r prozeduraler P r o g r a m m e . Ein weiterer wesentlicher Unterschied zwischen prozeduraler u n d o b j e k t o r i e n t i e r t e r P r o g r a m m i e r u n g wird ebenfalls schon a n diesem Beispiel deutlich. In der p r o z e d u r a l e n P r o g r a m m i e r u n g wird die S t r u k t u r i e r u n g einer A u f g a b e n s t e l l u n g in der Regel nach d e m T o p - D o w n - P r i n z i p v o r g e n o m m e n , d. h. eine A u f g a b e wird in T e i l a u f g a b e n zerlegt, welche w i e d e r u m jeweils in Teilaufgaben zerlegt wird, usw. bis die T e i l a u f g a b e n eine qualitative u n d q u a n t i t a t i v e Größe erreicht h a b e n , die es
22
1 Einführung
unter Berücksichtigung Software-technischer Kriterien erlaubt, diese unmittelbar zu spezifizieren bzw. zu programmieren. Hierbei steht, wie bereits erwähnt, die funktionale Sicht im Vordergrund. Aufgaben werden durch Funktionen spezifiziert, durch Algorithmen definiert und durch Prozeduren implementiert, welche auf (externen) Daten operieren. Abbildung 1.1 verdeutlicht diese Situation für das Eratosthenes-Beispiel. In der objektorientierten Programmierung wird die "Modularisierung" unter anderem gemäß einer dienstleistungsorientierten Sicht vorgenommen. Es ist zu überlegen, welche Einheiten welche Dienstleistungen erbringen und wie die Einheiten miteinander kommunizieren müssen, um die Gesamtaufgabe zu erfüllen. Objekte kapseln dabei sowohl ihren Zustand als auch ihr Verhalten. Sind die erforderlichen Objekte identifiziert, werden ihre Zustandsvariablen und ihr Verhalten in Klassendefinitionen festgelegt. In diesem Sinne folgt der objektorientierte Entwurf eher dem Bottom-Up-Prinzip. Die Abbildungen 1.1 und 1.4 verdeutlichen graphisch den Unterschied zwischen der hierarchischen Modularisierung der prozeduralen Programmierung und der aufgaben- und kommunikationsorientierten Modularisierung der objektorientierten Programmierung. In Kapitel 6 wird detaillierter auf objektorientierte Entwurfsmethoden eingegangen.
23
1.3 E i n B e i s p i e l
A b b i l d u n g 1.4: O b j e k t o r i e n t i e r t e S t r u k t u r des Sieb des
Eratosthenes
24
1 Einführung
L i t e r a t u r hinweise O r i g i n ä r e A r b e i t e n zur P r o g r a m m i e r s p r a c h e Simula sind Dahl u n d N y g a a r d (1966) sowie D a h l et al. (1967). A s p e k t e zur E n t w i c k l u n g von Simula werden in N y g a a r d u n d D a h l (1981) d i s k u t i e r t . Ideen, die zur E n t w i c k l u n g der S m a l l t a l k - S y s t e m e g e f ü h r t h a b e n , werden in Kay (1977) d a r g e s t e l l t , d a s Smalltalk-80-System wird der Öffentlichkeit e r s t m a l s im A u g u s t - H e f t der Zeitschrift Byte des J a h r e s 1981 ausführlich vorgestellt. Ausführliche D a r s t e l l u n g e n der S p r a c h e Smalltalk 80 u n d des Smalltalk-80-Systems erfolgen in G o l d b e r g u n d R o b s o n (1983a, b, 1989). Weitere L i t e r a t u r h i n w e i s e zu objektorientierten P r o g r a m m i e r s p r a c h e n erfolgen a m E n d e von K a p i t e l 5. A n w e n d u n g s b e r e i c h e u n d Anforderungen aus diesen Bereichen, die mit Hilfe von o b j e k t o r i e n t i e r t e n M e t h o d e n und S y s t e m e n zufriedenstellend b e h a n d e l t werden k ö n n e n , werden a u c h in Khoshafian und A b n o u s (1990) sowie in W i n b l a d et al. (1990) a u f g e f ü h r t . Dort werden zudem C h a r a k t e r i s t i k a o b j e k t o r i e n t i e r t e r S y s t e m e d i s k u t i e r t sowie P e r s p e k t i v e n ihrer z u k ü n f t i g e n Entwicklungen aufgezeigt. O b j e k t o r i e n t i e r t e Prinzipien für den Entwurf von E n d b e n u t z e r s y s t e m e n u n d -Schnittstellen werden in N a s t a n s k y (1990) b e h a n d e l t . K o n z e p t e f ü r die E n t w i c k l u n g z u k ü n f t i g e r D a t e n b a n k s y s t e m e , i n s b e s o n d e r e auch o b j e k t o r i e n t i e r t e K o n z e p t e , werden in Vossen und W i t t (1991) b e h a n d e l t . In W i t t (1989b) wird eine E i n f ü h r u n g in o b j e k t o r i e n t i e r t e K o n z e p t e gegeben. Insbesondere wird d o r t a m Beispiel S t a n d a r d - P a s c a l eine P r o g r a m m i e r t e c h n i k vorgestellt, mit d e r e n Hilfe objektorientierte P r o g r a m m e in p r o z e d u r a l e n P r o g r a m miersprachen realisiert werden können. In W i t t (1989a) werden f ü r das Verfahren des E r a t o s t h e n e s ein objektorientiertes P r o g r a m m und ein prozedurales, d y n a m i sches P r o g r a m m , beide in S t a n d a r d - P a s c a l , a n g e g e b e n sowie die Vor- u n d Nachteile dieser Lösungen i m Vergleich zur klassischen p r o z e d u r a l - s t a t i s c h e n Lösung diskutiert. Die Idee, d a s "Sieb des E r a t o s t h e n e s " als Beispiel für die Verdeutlichung objektorientierter P r o g r a m m i e r u n g u n d ihre A b g r e n z u n g g e g e n ü b e r prozeduraler Prog r a m m i e r u n g zu v e r w e n d e n , geht auf A m e r i c a (1986) bzw. W i t t (1989a) zurück.
Kapitel 2 Software-Engineering In der Einleitung, insbesondere im Kapitel 1.2, wurde die Beschäftigung mit objektorientierten Konzepten durch Anforderungen, die von E n d b e n u t z e r i n n e n gestellt werden, motiviert. Ziel ist die Entwicklung von Anwendungssystemen, welche die E n d b n u t z e r l n n e n bei ihrer Arbeit in einfacher und flexibler Weise unterstützen. In diesem Kapitel soll die Beschäftigung mit objektorientierten Systemen aus Software-Entwicklerinnen-Sicht motiviert werden. Es soll also untersucht werden, welchen Problemen im Bereich des Software-Engineering mit Hilfe von objektorientierten Konzepten begegnet werden kann, und zwar besser begegnet werden kann, als mit Konzepten der strukturierten bzw. der prozeduralen Programmierung. Dazu werden zunächst Eigenschaften von Software, die mit Hilfe von SoftwareEngineering-Methoden erreicht werden sollen, definiert. Desweiteren werden einige Vorgehensmodelle für die Herstellung und Wartung von Software vorgestellt, und es wird diskutiert, welche Vorgehensweisen durch objektorientierte Programmierung besonders u n t e r s t ü t z t werden. Ein wesentlicher Schritt bei der Software-Entwicklung ist die Modularisierung, d. h. die Unterteilung einer G e s a m t a u f g a b e bzw. eines Gesamtsystems in Teilaufgaben bzw. in Teilsysteme, den sogenannten Modulen. Im letzten Abschnitt des Kapitels werden die Grundprinzipien der Modularisierung, insbesondere die Datenkapselung, sowie Möglichkeiten zur Spezifikation und Implementierung von Modulen diskutiert.
2.1
Software-Qualität
Software wird erstellt und eingesetzt, um einen bestimmten Nutzen zu erreichen. Dieser kann z. B. erreicht werden • durch die Lösung von Aufgaben, die ohne die Unterstützung mit Software nur schwer oder praktisch nicht lösbar sind. Hierzu gehören etwa die numerische Lösung von großen (Differential-) Gleichungssystemen, die Durchführung von Monte-Carlo-Simulationen oder die Lösung von Optimierungsaufgaben.
26
2 Software-Engineering
• bei der Weiterentwicklung von Produkten oder deren Reproduktion, d. h. bereits existierende Produkte werden durch Einsatz von neuen Hardware- und Softwarekomponenten ("neue Technologien") komfortabler und mit neuen zusätzlichen Eigenschaften versehen. Beispiele hierfür sind etwa die Steuerung industrieller Prozesse oder die digitale Informationsübertragung (z. B. ISDN). • durch die Steigerung des Kosten-/Nutzenverhältnisses bei der Herstellung von Produkten (bessere Planung, Materialeinsparung, kürzere Umlaufzeiten, erhöhte Zuverlässigkeit, Rationalisierung). • durch die Reduzierung physischer und psychischer Arbeitsbelastungen (z. B. durch die Übernahme von gesundheitsgefährdenden Arbeiten durch Handhabungssysteme) . Um den Nutzen zu erreichen, müssen die folgenden beiden Forderungen erfüllt werden: • Die Kosten für den Software-IIerstellungsprozeß dürfen den Nutzen der Software nicht zunichte machen. • Die Qualität der Software muß so gut sein, daß der beabsichtigte Nutzen erreicht wird. Eine kostengünstige Herstellung von Software erfordert einerseits den Einsatz von Projektplanungs- und Projektmanagementmethoden. Diese sind nicht Gegenstand der Betrachtungen in diesem Buch und werden deshalb auch nicht weiter behandelt. Andererseits erfordert eine kostengünstige Herstellung von Software den Einsatz adäquater Methoden und Techniken für den Entwurf, die Implementierung und den Test der Software. Es sind also Methoden und Techniken erforderlich, welche die Produktivität der Entwerferinnen, Programmiererinnen und Testerinnen erhöhen und eine den gestellten Anforderungen entsprechende Herstellung der Software unterstützen. Die Anforderungen an Software können durch Qualitätsmerkmalc beschrieben werden. Wesentliche Software-Qualitätsmerkmale werden im folgenden kurz charakterisiert. Einige Qualitäten können insbesondere durch Methoden und Techniken der objektorientierten Programmierung erreicht werden. Hierauf wird später an entsprechenden Stellen noch detaillierter eingegangen.
Korrektheit Korrektheit bedeutet, daß die zu erstellende Software genau die Funktionen realisiert, die in den Anforderungen und ihren Spezifikationen festgelegt sind. Der Begriff der Korrektheit kann folgendermaßen präzisiert werden: Ein Problem x sei gegeben durch eine Menge F von Fragestellungen und eine Menge A
2.1 Software-Qualität
27
von Antworten sowie durch eine Funktion :F A, welche jeder Fragestellung x € F die korrekte Antwort y = f„(x) zuordnet. 7r = ( F , A , f n ) ist die Problemspezifikation. Eine Prozedur bzw. ein Programm 1 P heißt korrekt bezüglich 7T, falls fp(x) = f-n(x), für alle x £ F, d. h., daß die Prozedur jede Fragestellung richtig beantwortet. Dazu muß noch genauer festgelegt werden, was unter fp, der "Semantik" einer Prozedur, zu verstehen ist. Es gibt Methoden, mit deren Hilfe aus einer gegebenen formalen Spezifikation 1r eine partiell korrekte Prozedur P konstruiert werden kann. Partiell korrekt heißt dabei, daß die Terminierung von Schleifen durch diese Methode nicht garantiert werden kann. Korrektheit ist sicherlich das wichtigste Ziel, das bei der Software-Erstellung zu erreichen ist. Nicht korrekte Programme sind wertlos, auch wenn sie allen weiteren Qualitätsanforderungen genügen sollten. Methoden für den Nachweis korrekter Programme bzw. für die Konstruktion korrekter Programme können bei der objektorientierten Programmierung insoweit Anwendung finden, als daß sie bei der Implementierung der Klassen-Methoden verwendet werden können.
Robustheit Robustheit erfordert, daß Software-Systeme auch dann in einen definierten Zustand gelangen, falls Situationen auftreten, die nicht in den Anforderungen bzw. Spezifikationen festgelegt sind. Ein System muß also bei einer Eingabe x £ F "vernünftig" reagieren, z. B. durch Beendigung mit einem konsistenten Datenbestand oder mit vollständigen Informationen über eingetretene Inkonsistenzen sowie mit Informationen über Aktionen, mit denen das System wieder in einen Normalzustand versetzt werden kann. Robustheit umfaßt also Korrektheit (Korrektheit ist gleich Robustheit für Eingaben x € F). Durch Wartung und Weiterentwicklung von Software kann sich (wegen entsprechender Erweiterung der Fragestellungen) Korrektheit der Robustheit nähern, wird sie im allgemeinen aber nicht erreichen.
Ausfallsicherheit Ausfallsicherheit eines Software-Systems erfordert, daß das System sich in einem definierten Zustand befindet auch dann, wenn zugrunde liegende Systeme wie z. B . Hardware, Peripherie-Geräte, Datenübertragung oder Systemprogramme ausgefallen sind oder fehlerhaft reagieren. 'Im folgenden werden die Begriffe "Prozedur" (procedure) und "Programm" (program) synonym verwendet.
28
2 Software-Engineering
Zuverlässigkeit Unter dem Begriff Zuverlässigkeit werden die Anforderungen nach Korrektheit, nach R o b u s t h e i t u n d nach Ausfallsicherheit zusammengefaßt. Zuverlässigkeit insgesamt ist ein Bereich, in dem noch viele und grundsätzliche Fragen offen sind. Objektorientierte Ansätze haben bisher hier keine wesentlich neuen Beiträge geliefert, sondern eher neue Fragen aufgeworfen. Es reicht nicht, die Zuverlässigkeit von einzelnen Objekten oder von einzelnen Klassen zu erreichen, sondern die Zuverlässigkeit eines dynamischen Systems miteinander kommunizierender O b j e k t e .
Erweiterbarkeit und Anpaßbarkeit Erweiterbarkeit und Anpaßbarkeit stellen die Schwierigkeit dar, Software-Systeme an sich ä n d e r n d e Anforderungen und Spezifikationen anzupassen. Diese Eigenschaften von Software-Systemen sind insbesondere deshalb von großer Wichtigkeit, weil schon seit den sechziger Jahren bis heute der weitaus größte Aufwand von Software-Engineering-Aktivitäten durch W a r t u n g und Änderung von immer größer und u n ü b e r s c h a u b a r e r werdenden, monolithischen Software-Systemen verursacht werden, und deshalb für die Neuentwicklung von P r o g r a m m e n zu wenig Ressourcen zur Verfügung stehen. Diese Situation wird mit Software-Krise bzw. mit Anwendungsstau bezeichnet. W ä h r e n d die Ä n d e r u n g einzelner, kleinerer Prozeduren in der Regel keine großen Schwierigkeiten bereitet, kann eine Änderung eines Teils in einem großen, komplex s t r u k t u r i e r t e n Software-System aufwendige Änderungen anderer Teile des Systems zur Folge h a b e n . Dies gilt insbesondere d a n n , wenn das System nicht streng modularisiert entworfen und implementiert wurde. E n t w u r f s m e t h o d e n und -techniken, welche im Zusammenhang mit der strukturierten P r o g r a m m i e r u n g entstanden sind, haben in den meisten Fällen eine hierarchische Modularisierung eines großen Systems z u m Ziel. Module beschreiben dabei in überschaubarer Weise Teilaufgaben des Systems. In ihren Beschreibungen sollten die nach außen sichtbaren bzw. die von außen benutzbaren Komponenten deutlich getrennt sein von den Modul-Interna, d. h. von der Art und Weise, wie die Dienstleistungen, welche ein Modul erbringen soll, implementiert sind. Es ist gerade ein Ziel objektorientierter Programmierung, Modularisierung und D a t e n a b s t r a k t i o n zu unterstützen. Hierauf wird in Kapitel 2.3 und in Kapitel 3 noch detaillierter eingegangen. Dort wird gezeigt, daß objektorientierter Entwurf und objektorientierte Programmierung von Software-Systemen die Erweiterbarkeit und die Anpaßbarkeit dieser Systeme wesentlich u n t e r s t ü t z e n .
Wiederverwendbarkeit Wiederverwendbarkeit ist die Schwierigkeit, die Ergebnisse, die bei der SoftwareEntwicklung f ü r eine Aufgabenstellung erreicht worden sind, bei anderen Aufgaben-
2.1 Software-Qualität
29
Stellungen nutzbringend zu verwenden. Es k ö n n e n die folgenden A r t e n von W i e d e r v e r w e n d b a r k e i t u n t e r s c h i e d e n werden: •
Wiederverwendbarkeit von Personal: Der wiederholte E i n s a t z von P e r s o n a l in gleichartigen S o f t w a r e - P r o j e k t e n , gleichwohl in der W a r t u n g als auch in der Neuentwicklung, hilft, den A u f w a n d f ü r die S o f t w a r e - E r s t e l l u n g zu vermindern.
•
Wiederverwendbarkeit von Entwürfen: Viele, d u r c h a u s unterschiedliche Anw e n d u n g s b e r e i c h e zeigen, z u m i n d e s t bis zu einem gewissen A b s t r a k t i o n s g r a d , gleiche o d e r sehr ähnliche C h a r a k t e r i s t i k a , die zu gleichen o d e r ähnlichen S o f t w a r e - E n t w ü r f e n f ü h r e n . Die E r r e i c h u n g dieses Zieles wird d u r c h M e t h o den u n t e r s t ü t z t , die in Kapitel 2.3 vorgestellt werden.
•
Wiederverwendbarkeit von Programmen: Der A u f w a n d f ü r die P r o g r a m m i e r u n g , f ü r das Verifizieren u n d Testen von S o f t w a r e - S y s t e m e n wird vereinfacht, wenn K o m p o n e n t e n bereits existierender P r o g r a m m e u n v e r ä n d e r t o d e r o h n e große Ä n d e r u n g e n ü b e r n o m m e n werden k ö n n e n .
Die beiden letzten A s p e k t e sind ebenfalls wesentliche Ziele o b j e k t o r i e n t i e r t e r K o n z e p t e . Bereits im Kapitel 1.3 w u r d e auf d e n Aspekt " c o d e sharing" hingewiesen. Alle O b j e k t e einer Klasse verwenden den C o d e der I m p l e m e n t i e r u n g e n der in der Klassendefinition festgelegten M e t h o d e n . Dieser C o d e wird also m e h r f a c h verw e n d e t . Die Zur-Verfügung-Stellung von ( s t a n d a r d i s i e r t e n ) Klassenbibliotheken ( f ü r b e s t i m m t e A n w e n d u n g e n ) kann tatsächlich zur a u s g e p r ä g t e n Wiederverwend u n g von P r o g r a m m c o d e f ü h r e n .
K o m p a t i b i l i t ä t und Portabilität Die K o m p a t i b i l i t ä t eines P r o g r a m m s gibt an, wie (leicht) das P r o g a m m mit anderen kombiniert werden kann, d. h., wieviel A u f w a n d getrieben werden muß, u m es in a n d e r e S o f t w a r e - S y s t e m e zu integtrieren. P o r t a b i l i t ä t gibt an, wieviel A u f w a n d notwendig ist, u m P r o g r a m m e auf unterschiedlichen U m g e b u n g e n ( H a r d w a r e , Betriebssysteme) zu installieren. K o m p a t i b i l i t ä t u n d P o r t a b i l i t ä t werden wesentlich durch die Verfügbarkeit u n d die Berücksichtigung von S t a n d a r d s bei der Software-Erstellung b e s t i m m t . F ü r die o b j e k t o r i e n t i e r t e P r o g r a m m i e r u n g sind (abgesehen von D e - F a c t o - S t a n d a r d s f ü r die P r o g r a m m i e r s p r a c h e n C + + u n d T u r b o - P a s c a l ) keine S t a n d a r d s v e r f ü g b a r . W i e bereits ö f t e r e r w ä h n t , w ä r e gerade f ü r die o b j e k t o r i e n t i e r t e Software-Entwicklung die Verfügbarkeit von ( z u m i n d e s t a n w e n d u n g s b e z o g e n e n ) s t a n d a r d i s i e r t e n Klassenbibliotheken von großer Wichtigkeit.
30
2 Software-Engineering
Benutzerfreundlichkeit Benutzerfreundlichkeit beschreibt den Aufwand, den Benutzerinnen (Endbenutzerinnen, Systembetreuerinnen, O p e r a t o r i n n e n ) f ü r die Anwendung, die Betreuung bzw. die Installation eines Software-Systems treiben müssen. Benutzerfreundlichkeit beinhaltet verschiedene Aspekte, wie z. B. Lernbarkeit und Verständlichkeit der Schnittstellen, Angemessenheit von Schnittstellen hinsichtlich der mit ihrer Hilfe zu lösenden Aufgaben, angemessenes Fehlerverhalten (auch Fehlertoleranz), auf die hier nicht näher eingegangen werden soll. Benutzerfreundlichkeit ist neben Zuverlässigkeit, Erweiterbarkeit, Wiederverwendbarkeit u n d Portabilität eines der wichtigsten Qualitätsmerkmale von Software-Systemen. Im Abschnitt 1.2.1 wurde bereits darauf hingewiesen, daß objektorientierte Ansätze für die Gestaltung von Benutzeroberflächen geeignet sind.
Effizienz Effizienz im engeren Sinne ist die Belegung von Hardware-Ressourcen durch ein P r o g r a m m . Dies umfaßt die Aspekte Laufzeit (Prozessor-Belegung), benutzter Hauptspeicherplatz sowie die Nutzung peripherer Ressourcen wie z. B. Hintergrundspeicher, Ein- und Ausgabegeräte, Kommunikationsbetriebsmittel (Leitungen, Knotenrechner, Gateways). Diese Ressourcen, insbesondere Prozessor und Hauptspeicher, spielen h e u t e nicht mehr die Rolle, die sie zu Anfang der elektronischen D a t e n v e r a r b e i t u n g gespielt haben, d a sie zumeist so gut wie unbeschränkt zur Verfügung stehen. Höchstens in zeitkritischen Anwendungen spielen Laufzeit und D a t e n ü b e r t r a g u n g s z e i t e n noch eine Rolle. W i r d zur Effizienz eines Programms zudem der Aufwand hinzugezählt, der für Entwurf, Realisierung, Betrieb und W a r t u n g notwendig ist, verliert die Effizienz in engerem Sinne noch mehr an Bedeutung, denn auf Effizienz im engeren Sinne " g e t u n t e " P r o g r a m m e führen oft zu Einbußen bei den anderen Qualitäten.
S c h l u ß b e m e r k u n g zur S oft wäre-Qualität Einige Software-Qualitäten stehen im Konflikt zueinander. So f ü h r t z. B. die Implementierung von M a ß n a h m e n , welche die Benutzerfreundlichkeit erhöhen, unweigerlich zu einer geringeren Effizienz. Steht hingegen die Effizienz mit im Vordergrund der Anforderungen, gehen die entsprechenden Maßnahmen möglicherweise auf Kosten von Benutzerfreundlichkeit oder Zuverlässigkeit. Eine Anforderungsanalyse hat unter anderem die Aufgabe, zu untersuchen und in der Anforderungsspezifikation festzuhalten, welche Qualitäten das zu erstellende Software-System besitzen soll, in welcher Weise sie realisiert werden sollen und welche P r i o r i t ä t e n unter ihnen gelten sollen.
2.2 Vorgehensmodelle
2.2
31
V o r g e h e n s m o d e l l e für die S o f t w a r e - E n t w i c k lung
Ein Vorgehensmodell für die Entwicklung eines System ist eine Strategie, die festlegt, welche Schritte in welcher Reihenfolge die System-Herstellung und die System-Nutzung durchlaufen. In diesem Abschnitt wird kurz auf Vorgehensmodelle eingegangen, die im Software-Engineering eine Rolle spielen: das SoftwareLebenszyklus-Modell, Prototyping und evolutionäre Software-Entwicklung. Insbesondere die beiden zuletzt genannten Vorgehensweisen werden von objektorientierten Konzepten unterstützt.
2.2.1
Software-Lebenszyklus
Ein Software-Lebenszyklus ist ein Vorgehensmodell, das festlegt, welche Schritte in welcher Reihenfolge die Software-Herstellung und die Software-Nutzung durchlaufen. Die Grundannahme dabei ist, daß Software ein "Leben" hat, welches, beginnend bei einem Bedarf für diese Software, mehrere Phasen durchläuft. Weiterhin wird davon ausgegangen, daß ein Software-Leben zyklisch ist, d. h., daß Phasen mehrfach durchlaufen werden können. Jede Phase wird definiert durch • ihr Ziel, • ihren Inhalt, • ihre Organisation, • ihren Umfang. Die Kontrolle, ob eine Phase ihr Ziel erreicht, wird auch als Meilenstein bezeichnet. Meilensteine bieten die Möglichkeit, korrigierend in einen Phasenablauf einzugreifen. Die Phaseneinteilung und die Phasen selbst sind in der Software-EngineeringLiteratur nicht in einheitlicher Weise festgelegt. Ebenso unterscheiden sich die verschiedenen Ansätze in der Art der Zyklen, d. h. in welcher Reihenfolge Phasen und wie oft diese Reihenfolgen durchlaufen werden können. Im folgenden werden wesentliche Phasen, die Bestandteile der meisten Phasenaufteilungen sind — möglicherweise aufgeteilt auf andere Phasen oder anderen Phasen zugeordnet — kurz charakterisiert: • A n f o r d e r u n g s a n a l y s e : Aufgabe der Anforderungsanalyse ist eine möglichst vollständige und eindeutige Beschreibung des zu lösenden Problems unter Berücksichtigung aller wichtigen Rahmenbedingungen. Die Anforderungsanalyse selbst kann sich gliedern in eine Istanalyse, welche den aktuellen Zustand des Problembereichs beschreibt, in ein Sollkonzept, in welchem das zu erstellende Zielsystem festgelegt wird, und in eine Durchführbarkeitsstudie,
32
2 Software-Engineering
in welcher unter technischen, ökonomischen und personellen Aspekten eine Kosten-Nutzen-Analyse für den zu erreichenden Zustand durchgeführt wird. Ergebnisse einer Anforderungsanalyse sind unter anderem eine Anforderungsspezifikation oder ein Pflichtenheft, welche in der Regel zentrale Bestandteile von Verträgen zwischen Auftraggebern und Auftragnehmern von SoftwareProjekten sind. Es gibt eine Reihe von Methoden und Techniken zur Durchführung der Anforderungsanalyse. Eine bekannte und mittlerweile verbreitete Technik ist SADT (Structured Analysis and Design Technique). Sie unterstützt und erfordert die Systemanalyse und -beschreibung sowohl aus der datenorientierten als auch aus der funktionsorientierten Sicht. Komponenten der Betrachtung (Daten bzw. Funktionen) werden schrittweise hierarchisch so in Teilkomponenten untergliedert, daß jede Teilkomponente unabhängig von den anderen der gleichen Abstraktionsebene verfeinert werden kann. Jeder Verfeinerungsschritt soll eine Komponente mindestens in drei und höchstens in sechs Teilkomponenten zerlegen. SADT oder SADT-Varianten sind Bestandteil kommerziell verfügbarer Software-Entwicklungswerkzeuge. Viele andere bekannte Methoden, Techniken und Werkzeuge unterstützen jeweils nur eine Sichtweise, entweder eine datenorientierte oder eine funktionsorientierte Sichtweise. In Kapitel 6 werden objektorientierte Methoden für die Anforderungsanalyse vorgestellt. Dabei wird keine Trennung zwischen Daten und Funktionen gemacht, sondern Daten und Funktionen sind integrale Bestandteile der zu analysierenden und zu beschreibenden Komponenten eines Problembereiches. • Entwurf: Aufgabe des Entwurfs ist die Zielmaschinen-unabhängige formale Beschreibung der Teilsysteme und ihres Zusammenwirkens. Zielmaschinenunabhängig bedeutet, daß Eigenschaften der Programmiersprache und der Systemumgebung, welche bei der Implementierung bzw. beim Betrieb und bei der Nutzung verwendet werden, unberücksichtigt bleiben. Entwürfe sollten invariant gegenüber Änderungen der Zielmaschine sein. Formal bedeutet dies z. B., daß die Syntax- und die Semantik von Entwurfssprachen formal, d. h. mit mathematischen Hilfsmitteln, definiert sind, oder daß Aussagen über Eigenschaften des mit einer Entwurfssprache modellierten Systems (im mathematischen Sinne) beweisbar sind. Es gibt nur wenige Entwurfsmethoden und -techniken, die diesen Kriterien genügen. Zu ihnen gehören z. B. algebraische Spezifikationen und Petri-Netze. Die meisten Methoden und Techniken sind, auch wenn viele von ihnen graphische Darstellungsmöglichkeiten besitzen, eher informeller Natur. • Implementierung: Aufgabe der Implementierung ist die Codierung, das Verifizieren und Testen des zu realisierenden Software-Systems auf der Basis
2.2 Vorgehensmodelle
33
des Entwurfes in der Weise, daß das System die in der Anforderungsspezifikation geforderten Qualitäten erreicht. • B e t r i e b , N u t z u n g u n d W a r t u n g : Aufgabe dieser Phase ist die Installierung des implementierten Software-Systems auf einer Zielmaschine, die Anwendung des Systems, die Erkennung und Behebung von Mängeln sowie die Erweiterung und Anpassung an neu entstehende Anforderungen. Diese Art der Unterteilung eines Software-Lebens in Phasen ist im wesentlichen eine zeitliche Einteilung. Eine hierzu orthogonale Sicht unterteilt die Aktivitäten, die für ein Software-Leben notwendig sind, zeitunabängig nach ihrem Inhalt. Hiernach kann jede Phase etwa unterteilt werden in die Aktiviäten: • A n f o r d e r u n g s s p e z i f i k a t i o n : Hierzu gehören alle Aktivitäten, die bereits oben zur Phase Anforderungsanalyse skizziert wurden, bei dieser Sichtweise jedoch phasenbezogen. Es gibt Methoden und Techniken, die in einigen oder sogar in allen Phasen eingesetzt werden können. SADT z. B. kann nicht nur bei der Spezifikation innerhalb der Anforderungsanalyse, sondern auch bei der Modularisierung und bei Spezifikation von Entwürfen und ebenso in der Wartungsphase eingesetzt werden. • P r o g r a m m i e r e n i m Großen: Phasenbezogene Zielmaschinen-unabhängige Strukturierung eines Gesamtsystems in Teilsysteme (Modularisierung) ist die wesentliche Aufgabe von Programmierung im Großen. • P r o g r a m m i e r e n im K l e i n e n : Hierzu gehören alle Aktivitäten, die das Implementieren, Testen, Installieren und Warten der Module jeweils in der Zielsprache der betreifenden Phase beinhalten. Der Begriff Zyklus in Software-Lebenszyklus rührt daher, daß spätestens in der Wartungs-Phase, und zwar sowohl bei der Mängelbeseitigung als auch im Falle von Erweiterung und Anpassung, die Phasen wieder von vorne und in derselben Reihenfolge durchlaufen werden. Diese Vorstellung, d. h. die Phasen können nur in der oben angegebenen Reihenfolge durchlaufen werden und ein Zyklus entsteht allein durch die Möglichkeit, daß in der Wartungsphase wieder ein Durchlauf dieser Reihenfolge begonnen werden kann, liegt dem "Wasserfallmodell" zugrunde (siehe Abbildung 2.1). Die strikte Einhaltung des Wasserfallmodells hat zur Konsequenz, daß • die Anforderungen vollständig und eindeutig während der Anforderungsanalyse erfaßt und festgelegt werden müssen; • nach Abschluß der Anforderungsanalyse zunächst keine Möglichkeiten mehr bestehen, weitere Anforderungen zu berücksichtigen, erst in der nächsten Wartungsphase besteht dazu wieder Gelegenheit;
34
A b b i l d u n g 2.1: Vorgehensmodell: modell")
2 Software-Engineering
Linearer Software-Lebenszyklus ("Wasserfall-
2 . 2 Vorgehensmodelle
35
• Auftraggeberinnen und Benutzerinnen erst nach Abschluß der Implementierung Ergebnisse der Software-Erstellung zu sehen bekommen. In der Praxis hat sich sehr schnell gezeigt, daß dieses Vorgehen kein realistischer Ansatz ist. Gerade während der D u r c h f ü h r u n g eines Software-Projektes entstehen bei den auftraggebenden Anwenderinnen durch die intensive Beschäftigung mit den Problembereichen und durch den K o n t a k t mit den Software-Erstellerinnen Anderungswünsche und zusätzliche Anforderungen, mit deren Realisierung nicht bis zur nächsten Wartungs-Phase gewartet werden kann. Dies hat dazu geführt, d a ß Zyklen im Software-Leben auch an anderen Stellen zugelassen werden, daß im Prinzip von jeder Phase zu jeder vorhergehenden Phase zurückgekehrt wird, um zusätzliche Anforderungen in den Software-Erstellungsprozeß zu integrieren. Aber auch dieses Vorgehen hat immer noch einige Nachteile. Die Implementierungen der zu erstellenden Software oder auch nur von Teilen davon liegen erst relativ spät vor. Insbesondere d a n n , wenn gerade wegen der Berücksichtigung von zusätzlichen Anforderungswünschen, die ursprünglich nicht vorgesehen waren, die Implementierungs-Phase noch weiter hinausgeschoben worden ist. Da die Uberprüf u n g des Ergebnisses durch die Auftraggeberinnen erst so spät erfolgt, wird es oft auch dann akzeptiert, wenn es nicht mit den ursprünglichen Vorstellungen übereinstimmt. Änderungen zu diesem späten Zeitpunkt führen zu einer nicht vertretbaren Erhöhung des Projektaufwandes, so daß auch ein nicht zufriedenstellendes System in Betrieb genommen und genutzt wird, was aber bei den Anwenderinnen zu geringer Akzeptanz des Systems führen kann. Alle Hoffnung auf eine Verbesserung wird dann auf die Wartung gesetzt. Ein weiterer Nachteil ergibt sich daraus, daß sich die Arbeitsumgebungen, in welcher die zu erstellende Software eingesetzt werden soll, durch den Einsatz der Software verändert, und diese Veränderung zur Änderung vormals bestehender und in die Software-Entwicklung eingegangener Anforderungen oder zu neuen wichtigen Zusatzanforderungen führen. Würden in dieser Hinsicht wichtige K o m p o n e n t e n des zu erstellenden Software-Systems frühzeitig implementiert zur Verfügung stehen, könnten Erfahrungen, welche die Anwenderinnen in ihrer Arbeitsumgebung d a m i t gewinnen, mit in den weiteren Software-Entwicklungsprozeß eingehen. Es ist also wünschenswert, die Anwenderinnen nicht nur in der Anforderungsanalysc zu beteiligen, sondern in allen Phasen der Software-Erstellung. Es gibt noch weitere Nachteile des "klassischen" Software-Lebenszyklus und seiner Varianten. So müssen z. B. Entscheidungen des P r o j e k t m a n a g e m e n t s über die Gestaltung eines Software-Lebenszyklus sehr früh und in den meisten Fällen auf der Basis unzureichender Entscheidungsgrundlagen getroffen werden. Revisionen sind oft erst möglich, wenn bereits ein Großteil der für ein P r o j e k t vorgesehenen Ressourcen verbraucht und für Änderungen notwendige Anpassungen oder gar die Beendigung des Projektes wirtschaftlich nicht mehr vertretbar sind. Um die getätigten Investitionen zu rechtfertigen, wird eher versucht, die Einsatzumgebungen des erstellten mangelhaften Systems (organisatorisch) an dieses System anzupassen.
2 Software-Engineering
36
2.2.2
Prototyping
Ein Vorgehensmodell, das die Anwenderinnen a m Entwurfs- und Entwicklungsprozeß intensiv beteiligt, u m die oben erwähnten Nachteile zu vermeiden, ist das Prototyping. Ein P r o t o t y p ist ein ablauffähiges Modell des Zielsystems, das nicht alle Anforderungen an das Zielsystem erfüllen muß. Durch Prototyping soll unter a n d e r e m • jeweils schnell eine Basis für die Verständigung von Anwenderinnen und Entwicklerinnen über Entwurfs- und Implementierungsentscheidungen bereitgestellt werden; • den Anwenderinnen die Möglichkeit geboten werden, mit verschiedenen Versionen von Systemeigenschaften zu experimentieren und daraus eine Alternative als Basis für die weitere Entwicklung auszuwählen; • schrittweise der Entwurf und die Implementierung des kompletten Systems realisiert werden, wobei dynamisch das (vorab nicht endgültig festgelegte) Zielsystem entsteht. Beim P r o t o t y p i n g findet also eine Vermischung von Anforderungsanalyse, Entwurf, Implementierung und Bewertung s t a t t (siehe Abbildung 2.2). Es werden Teilaspekte des zu erstellenden Systems entworfen und implementiert, bevor die Anforderungsanalyse vollständig abgeschlossen ist. Diese findet parallel zu Entwurf und Implementierung s t a t t , wobei die Bewertung der Prototypen jeweils berücksichtigt wird. Die Erstellung des endgültigen Zielsystems kann beim Prototyping auf die folgenden zwei Arten geschehen: • Evolutionäre Prototypen: Die Prototypen werden schrittweise nach fortschreitender Festlegung aller Anforderungen weiterentwickelt bis ein Software-System erreicht wird, welches den Vorstellungen der Anwenderinnen genügt. •
Wegwerf-Prototypen: Erfahrungen u n d Erkenntnisse, die während der Erstellung von Prototypen zu einem Problembereich gesammelt worden sind, werden, falls das Zielsystem endgültig realisiert werden soll, für die Neuerstellung des Systems (nun möglicherweise einem "klassischen" Vorgehensmodell folgend) genutzt.
2.2.3
E v o l u t i o n ä r e Software-Entwicklung
Die evolutionäre Software-Entwicklung ist vergleichbar mit evolutionärem Prototyping. Das Software-System wird schrittweise, beginnend bei einem Kern, weiterentwickelt, bis ein den Anwenderinnen zufriedenstellendes P r o d u k t erreicht ist.
2.2 Vorgehensmodelle
Abbildung 2.2: Vorgehensmodell: Prototyping
37
38
2 Software-Engineering
Dabei muß nicht von vorneherein exakt feststehen, was zu diesem Zeitpunkt erreicht sein soll, es ergibt sich auf dem Weg dorthin. Entwürfe und Implementierungen beinhalten zwar unterschiedliche Aktivitäten, sie sind aber nicht jeweils unterschiedlichen Phasen zugeordnet, sondern sie finden — möglicherweise mit anderen Aktivitäten (Anforderungsanalyse, Betrieb und Nutzung) — jeweils für die Entwicklung eines Teilsystems (auch Prototyp) parallel und sich ergänzend statt. Die Kommunikation zwischen Anwenderinnen und Entwicklerinnen findet während des gesamten Projektes statt. Ein wichtiger Grundsatz der evolutionären System-Entwicklung ist, daß das gesamte Projekt als Lernprozeß aller Beteiligter aufgefaßt wird und nicht mehr als eine mehr oder weniger formale Transformation einer (gegebenen) Spezifikation in ein Zielsystem. Klärungen von Problemen müssen nicht vorab durchgeführt werden, und es ist ausgeschlossen, daß Probleme, die während der Entwicklungsphasen auftreten, nicht oder erst später oder ohne die Beteiligung der Anwenderinnen geklärt werden. Probleme werden unmittelbar bei ihrem Auftreten von allen Beteiligten geklärt. Ein weiterer Grundsatz evolutionärer Software-Entwicklung ist, daß von vorneherein davon ausgegangen wird, daß ein System nie fertig wird. Dies ist eine den Lebenszyklus-Modellen vollkommen konträre Grundannahme. Sie erfordert, daß das System einen Grad an Offenheit besitzt, welche die ständigen Anpassungen und Erweiterungen, die evolutionären Fortentwicklungen, kostengünstig ermöglichen. Abbildung 2.3 verdeutlicht im Vergleich zu Abbildung 2.1 und auch im Vergleich zu Abbildung 2.2 graphisch die unterschiedliche Vorgehensweise bei der evolutionären Software-Entwicklung.
S c h l u ß b e m e r k u n g zu V o r g e h e n s m o d e l l e n Die Beschäftigung mit objektorientierten Konzepten und ihr Einsatz bei der Software-Entwicklung ist nur dann sinnvoll, wenn sie dazu beitragen, Software-Qualitäten eher und besser zu erreichen, als das mit anderen Konzepten möglich ist. In späteren Kapiteln wird detaillierter darauf eingegangen, wie objektorientierte Konzepte in den einzelnen Lebenszyklus-Phasen bzw. bei Anforderungsspezifikationen, beim Programmieren im Großen und beim Programmieren im Kleinen eingesetzt werden können. Insbesondere wird sich zeigen, daß Prototyping und evolutionäre Software-Entwicklung durch objektorientierte Konzepte unterstützt werden.
2.3
Modularisierung und Datenkapselung
Software-Engineering befaßt sich wesentlich mit der Erstellung von Modellen für Ausschnitte der realen Welt, d. h. die für das zu realisierende Software-System relevanten Aspekte einer Anwendungswelt werden mit Hilfe von (formalen) Sprachen beschrieben. In den Phasen eines Software-Lebenszyklus finden Beschreibungen
2.3 Modularisierung und Datenkapselung
Abbildung 2.3: Vorgehensmodell: Evolutionäre Software-Entwicklung
39
2 Software-Engineering
40
auf unterschiedlichen A b s t r a k t i o n s e b e n e n s t a t t . In der A n f o r d e r u n g s a n a l y s e u n d i m E n t w u r f werden die A s p e k t e i m p l e m e n t i e r u n g s u n a b h ä n g i g beschrieben, d. h. G e s i c h t s p u n k t e der I m p l e m e n t i e r u n g werden nicht berücksichtigt. In der Implem e n t i e r u n g werden die A s p e k t e mit einer P r o g r a m m i e r s p r a c h e b e s c h r i e b e n , so d a ß die d a b e i e n t s t e h e n d e n P r o g r a m m e auf e i n e m Rechnersystem a b l a u f f ä h i g werden. P r i n z i p i e n u n d Techniken, die im Z u s a m m e n h a n g m i t der s t r u k t u r i e r t e n Prog r a m m i e r u n g f ü r den E n t w u r f u n d für die I m p l e m e n t i e r u n g von S o f t w a r e entwickelt w o r d e n sind, betreffen insbesondere die Modularisierung und die D a t e n a b s t r a k t i o n . M o d u l a r i s i e r u n g und D a t e n k a p s e l u n g sind wesentliche G r u n d k o n z e p t e o b j e k t o r i e n t i e r t e r P r o g r a m m i e r u n g u n d werden d e s h a l b im folgenden n ä h e r b e t r a c h t e t .
2.3.1
Modularisierung
M o d u l a r i s i e r u n g b e d e u t e t die Zerlegung einer G e s a m t a u f g a b e o d e r eines G e s a m t s y s t e m s in Teilaufgaben bzw. in Teilsysteme, die Module. Die G r u n d a n n a h m e d a b e i ist, d a ß die f ü r ein S y s t e m geforderten Q u a l i t ä t e n eher erreicht werden, wenn das S y s t e m m o d u l a r erstellt wird. F ü r den Begriff Modul gibt es keine einheitliche, eindeutige Definition. Es k ö n n e n a b e r eine Reihe von Eigenschaften angegeben werden, die M o d u l e erfüllen sollten: • Ein M o d u l ist eine logische Einheit, welche eine klar a b g e g r e n z t e T e i l a u f g a b e eines G e s a m t s y s t e m s realisiert. 2 • Ein M o d u l b e s t e h t aus ( s t r u k t u r i e r t e n ) D a t e n und aus O p e r a t i o n e n . Es stellt a n d e r e n Modulen Dienstleistungen, nämlich D a t e n oder O p e r a t i o n e n o d e r beides, zur Verfügung. Diese Dienstleistungen bilden die Export-Schnittstelle des M o d u l s . N u r diese Schnittstelle ist von außen, d. h. von a n d e r e n Modulen, sichtbar. Wie die Dienstleistungen erbracht werden, bleibt im I n n e r n des M o d u l s , d e m M o d u l - R u m p f , verborgen. Diese Eigenschaft wird mit Verbergen von Information, mit Geheimnisoder Geheimhaltungsprinzip (englisch: information hiding), D a t e n k a p s e l u n g (englisch: data encapsulation) oder Datenabstraktion (englisch: data abstraction) bezeichnet. I m R u m p f können Dienstleistungen a n d e r e r M o d u l e a n g e f o r d e r t werden. Diese Dienstleistungen bilden die Import-Schnittstelle des Moduls. A b b i l d u n g 2.4 zeigt den A u f b a u von M o d u l e n . Ein M o d u l sollte keine Seiteneffekte h a b e n , d. h. Dienstleistungen des M o d u l s d ü r f e n nur ü b e r die E x p o r t - S c h n i t t s t e l l e in Anspruch g e n o m m e n werden, u n d 2 E i n " K r i t e r i u m " für den U m f a n g der A u f g a b e n s t e l l u n g , die von einem M o d u l realisiert werden soll, ist z. B., d a ß die A u f g a b e n s t e l l u n g mit e i n e m Satz beschrieben werden k ö n n e n m u ß . Ein " K r i t e r i u m " für die Größe eines Moduls ist z . B . , daß seine Beschreibung (mit einer Spezifikationssprache oder mit einer P r o g r a m m i e r s p r a c h e ) auf eine DIN A4-Seite passen m u ß .
2.3 Modularisierung und Datenkapselung
41
ein Modul darf Dienstleistungen ausschließlich über seine Import-Schnittstellen in Anspruch nehmen. • Ein Modul kann durch ein anderes Modul mit gleicher Export-Schnittstelle ersetzt werden. Die Semantik des Gesamtsystems wird dadurch nicht verä n d e r t , andere Eigenschaften, wie z. B. Effizienz oder Portabilität, können dadurch verändert werden. • Ein Modul ist unabhängig von anderen Modulen spezifizierbar, implementierbar, test- und verifizierbar. Eine strikte Beachtung dieser Anforderungen erhöht insbesondere die Wiederverwendbarkeit von Modulen, und zwar sowohl die Wiederverwendbarkeit ihrer Spezifikationen als auch die Wiederverwendbarkeit ihrer Implementierungen. Programmiersprachen, die das Modulkonzept unterstützen, sind z. B. Modula-2 und A d a mit dem module- bzw. mit dem package-Konzept. Im nächsten Abschnitt wird ein Beispiel für die a b s t r a k t e Spezifikation sowie für die (Modula-2- bzw. Ada-) Implementierung eines Moduls angegeben. Es können drei Arten von Modulen unterschieden werden: Funktionsmodule, D a t e n m o d u l e und Datentypmodule. Ein Funktionsmodul realisiert die Transformation von Eingabedaten in Ausg a b e d a t e n . Die Transformation hängt dabei nur von den Eingabedaten ab, d. h. bei derselben Eingabe liefert ein Funktionsmodul stets dieselbe Ausgabe. Ein Funktionsmodul realisiert also eine Funktion im "mathematischen" Sinne: Elemente aus dem Definitionsbereich der Funktion werden unabhängig von weiteren P a r a m e t e r n Elementen des Wertebereichs der Funktion zugeordnet. Beispiele sind etwa die Matrixinversion, die Berechnung der Werte von trigonometrischen Funktionen, die Gehaltsberechnung oder die Erstellung von Mahnungen. Ein Datenmodul realisiert in der Regel eine D a t e n s t r u k t u r und Operationen, welche die D a t e n s t r u k t u r verwalten und Zugriffe auf Komponenten der Datens t r u k t u r ermöglichen. Die Ausgabe eines Datenmoduls hängt nicht alleine von den E i n g a b e d a t e n ab, sondern ebenfalls vom aktuellen Zustand der D a t e n s t r u k t u r . Ein D a t e n m o d u l besitzt also ein "Gedächtnis", das bestimmte Aspekte seiner bisherigen I n a n s p r u c h n a h m e für weitere Inanspruchnahmen zur Verfügung hält. Beispiele sind etwa B a u m s t r u k t u r e n , mit deren Hilfe Mengen sortiert werden können oder das Enthaltensein von Daten in Mengen ü b e r p r ü f t werden kann, sowie Keller (Stacks), mit deren Hilfe arithmetische Ausdrücke ausgewertet werden können. Mit Hilfe von D a t e n s t r u k t u r e n , wie z. B. Bäumen oder Kellern, können Daten unterschiedlicher Art organisiert und verwaltet werden. Die S t r u k t u r und die Operationen sind unabhängig davon, ob z. B. mit Hilfe eines Baumes Zahlen, Zeichenketten oder Sätze verwaltet werden. Deshalb liegt es nahe, neben Datenmodulen Datentypmodule, auch generische Datenmodule bezeichnet, zu betrachten. Ein D a t e n t y p m o d u l realisiert eine Menge von Datenmodulen, indem der T y p der
42
2 Software-Engineering
Abbildung 2.4: Modulaufbau
2.3 Modularisierung und Datenkapselung
43
zu verwaltenden E l e m e n t e nicht festgelegt wird. Ein D a t e n m o d u l e n t s t e h t a u s e i n e m D a t e n t y p m o d u l , i n d e m d e r offen gelassene T y p e n t s p r e c h e n d festgelegt wird. I m folgenden A b s c h n i t t ü b e r a b s t r a k t e Spezifikationen u n d I m p l e m e n t i e r u n g von M o d u l e n wird d a r a u f noch einmal beispielhaft eingegangen.
2.3.2
A b s t r a k t e Spezifikationen und I m p l e m e n t i e r u n g v o n Modulen
In diesem A b s c h n i t t wird informal u n d beispielhaft dargestellt, auf welche A r t u n d Weise Module, i n s b e s o n d e r e D a t e n m o d u l e , auf verschiedenen A b s t r a k t i o n s e b e n e n spezifiziert werden k ö n n e n , wobei die I m p l e m e n t i e r u n g eines Moduls als Spezifikation mit Hilfe einer P r o g r a m m i e r s p r a c h e v e r s t a n d e n werden kann. A b s t r a k t b e d e u t e t dabei, wieviel hinsichtlich der I m p l e m e n t i e r u n g eines M o d u l s in der Spezifikation festgelegt ist, d. h., je weniger I m p l e m e n t i e r u n g s a s p e k t e festgelegt sind, d e s t o a b s t r a k t e r ist die Spezifikation. I m einzelnen wird eingegangen auf die Spezifikation von Modulen mit Hilfe von • abstrakten Datentypen, • algebraischen Spezifikationen, • D a t e n s t r u k t u r e n u n d Algorithmen, •
Programm-Modulen.
Als Sprache zur N o t a t i o n von a b s t r a k t e n D a t e n t y p e n u n d algebraischen Spezifikationen werden E l e m e n t e der Sprache der M a t h e m a t i k b e n u t z t , zur Notation von D a t e n s t r u k t u r e n und Algorithmen eine p r o g r a m m i e r s p r a c h e n ä h n l i c h e P s e u d o s p r a che, u n d zur N o t a t i o n von P r o g r a m m - M o d u l e n E l e m e n t e der P r o g r a m m i e r s p r a c h e n Modula-2 und Ada. Den Beispielen zu den E r l ä u t e r u n g e n liegt die folgende Aufgabenstellung zugrunde: Realisiere eine Menge ganzer Zahlen, in die E l e m e n t e eingefügt u n d aus der E l e m e n t e gelöscht werden k ö n n e n . A u ß e r d e m m u ß ein Test auf E n t h a l t e n s e i n einer ganzen Zahl in der Menge d u r c h g e f ü h r t werden können. Abstrakte Datentypen E i n e a b s t r a k t e Datentyp-Spezifikation besteht a u s zwei Teilen. Der erste Teil ist die Signatur, der zweite Teil e n t h ä l t die Axiome. Die S i g a n t u r ist eine s y n t a k t i s c h e Beschreibung des a b s t r a k t e n D a t e n t y p s , die Axiome legen seine B e d e u t u n g fest. In der S i g n a t u r werden ( a b s t r a k t e ) N a m e n f ü r die M e n g e n , auf denen die O p e r a t i o n e n operieren sollen, s o g e n a n n t e Sorten, festgelegt, sowie die O p e r a t i o n e n b e n a n n t u n d
2 Software-Engineering
44
definiert. Die Axiome werden in Form von Gleichungen n o t i e r t u n d setzen die in d e r S i g n a t u r a u f g e f ü h r t e n O p e r a t i o n e n in Beziehung z u e i n a n d e r . Die obige A u f g a b e n s t e l l u n g kann e t w a wie folgt als a b s t r a k t e r D a t e n t y p ( A D T ) spezifiziert werden: adt GanzeZahlenMenge Sorten GanzeZahlenMenge, GanzeZahl, Bool Operationen LeereMenge: Einfügen: GanzeZahlenMenge x GanzeZahl Löschen: GanzeZahlenMenge x GanzeZahl Enthalten: GanzeZahlenMenge x GanzeZahl IstLeer: GanzeZahlenMenge axiome IstLeer(LeereMenge) IstLeer(Einfügen(m,i)) Einfügen(Einfügen(m,i),i) Enthalten(Einfügen(m,i),i) Enthalten(Einfügen(m,j),i)
GanzeZahlenMenge GanzeZahlenMenge GanzeZahlenMenge Bool Bool true false Einfügen(m,i) true Enthalten(m,i),
e n d GanzeZahlenMenge Neben der Sorte GanzeZahlenMenge, die durch den gleichnamigen a b s t r a k t e n D a t e n t y p spezifiziert wird, werden noch die Sorten GanzeZahl u n d Bool v e r w e n d e t , wobei davon ausgegangen wird, daß diese bereits definiert sind o d e r s t a n d a r d m ä ß i g zur V e r f ü g u n g stehen. W i e bereits e r w ä h n t , legen die Axiome die Semantik eines a b s t r a k t e n D a t e n t y p s fest. So beschreiben z. B. die ersten beiden Axiome — u n t e r der A n n a h m e , daß die W e r t e true und false die üblichen aussagenlogischen B e d e u t u n g e n " w a h r " u n d " f a l s c h " besitzen — d a ß LeereMenge die leere Menge erzeugt. Das d r i t t e Axiom beschreibt die Eigenschaft von GanzeZahlenMenge, d a ß eine ganze Z a h l höchstens e i n m a l in der Menge v o r k o m m e n kann. D e r Einsatz a b s t r a k t e r D a t e n t y p e n h a t als ein wesentliches Ziel, d a ß die Defin i t i o n , insbesondere die S e m a n t i k eines a b s t r a k t e n D a t e n t y p s u n a b h ä n g i g von den g e w ä h l t e n Sorten- und O p e r a t i o n e n n a m e n ist. Die S e m a n t i k ergibt sich allein u n d e i n d e u t i g a u s den A x i o m e n . Wenn im Beispiel etwa an Stelle von GanzeZahlenMenge die Bezeichnung Stuhl, an Stelle von GanzeZahl die B e z e i c h n u n g Tisch, an Stelle von Einfügen die Bezeichnung Witt und d a m i t an Stelle der O p e r a t i o n Einfügen:
GanzeZahlenMenge x GanzeZahl —» GanzeZahlenMenge
2.3 Modularisierung und Datenkapselung
45
die Operation Witt:
T i s c h x S t u h l -+ T i s c h
notiert worden wäre, und auch für die anderen Namen andere mehr oder weniger sinnvolle Bezeichnungen gewählt worden wären, wäre der so definierte abstrakte Datentyp ebenfalls eine Spezifikation für die Aufgabenstellung. Denn die Axiome würden für S t u h l , T i s c h , Witt und die anderen Bezeichnungen die "richtigen", gewünschten Bedeutungen festlegen. Natürlich ist es wegen der besseren Lesbarkeit sinnvoll, bedeutungstragende Namen zu wählen. Ebenso wird in abstrakten Datentypen nicht festgelegt, wie die Elemente der Mengen, mit denen die Sorten implementiert werden sollen, repräsentiert werden sollen. Ob etwa die Wahrheitswerte mit "true" und "false" oder mit "ja" oder "nein" oder mit 1 oder 0 etc. implementiert werden, ist für die abstrakte DatentypSpezifikation unerheblich. Die Bedeutung der gewählten Repräsentation ergibt sich aus den Axiomen für die entsprechenden Sorten in der Spezifikation für Boolesche Werte und ihre Verknüpfungen. Eine Problematik abstrakter Datentypen ist z. B., wie Vollständigkeit und Widerspruchsfreiheit einer Spezifikation erreicht werden können. Vollständigkeit bedeutet, daß alle Eigenschaften, welche in der Aufgabenstellung gefordert werden, auch in der Spezifikation beschrieben werden (siehe " . . . " im Beispiel). Widerspruchsfreiheit bedeutet, daß sich die Axiome nicht widersprechen, d. h., daß aus den Axiomen nicht gegensätzliche Eigenschaften für den abstrakten Datentyp herleitbar sind. Mit Fragestellungen in diesem Zusammenhang und weiteren Problematiken abstrakter Datentypen, wie z. B. die Äquivalenz von abstrakten Datentypen, d. h. die Frage, ob zwei abstrakte Datentypen mit verschiedenen Signaturen u n d / o d e r verschiedenen Axiomen dieselbe Bedeutung haben, beschäftigt sich die Theorie abstrakter Datentypen. An dieser Stelle soll nicht weiter darauf eingegangen werden, da hier nur ein Einblick in die Spezifikationsmöglichkeiten von Modulen mit Hilfe abstrakter Datentypen gegeben werden soll. In den Literaturhinweisen am Ende des Kapitels ist weitere Literatur zum vertieften Studium abstrakter Datentypen angegeben.
A l g e b r a i s c h e Spezifikation Den algebraischen Spezifikationen liegt die Annahme zugrunde, daß ein (Daten-) Modul als eine Algebra dargestellt werden kann. Eine Algebra besteht aus einer Menge von (strukturierten) Werten und aus einer Menge von Operationen, welche auf den Werten operieren. In algebraischen Spezifikationen werden die Operationen nicht implizit über Axiome, sondern explizit mit Hilfe von Funktionen definiert. Außerdem werden den Sorten Mengen, sogenannte Trägermengen, zugeordnet. Eine algebraische Spezifikation für obige Aufgabenstellung könnte etwa wie
46
2 Software-Engineering
folgt aussehen: a l g e b r a GanzeZahlenMenge sorten GanzeZahlenMenge, GanzeZahl, Bool Operationen LeereMenge: Einfügen: GanzeZahl GanzeZahlenMenge Löschen: GanzeZahl GanzeZahlenMenge Enthalten: GanzeZahl GanzeZahlenMenge IstLeer: GanzeZahlenMenge träger GanzeZahlenMenge = 'P(Z) funktionen LeereMenge Einfügen(M,i) Lösehen(M,i) Enthalten(M,i) IstLeer(M) e n d GanzeZahlenMenge
GanzeZahlenMenge GanzeZahlenMenge GanzeZahlenMenge Bool Bool
= =
M U{i} M - {i} ( i e M)
=
(M =
=
0)
(Z bezeichne die Menge der ganzen Zahlen, Z = { . . . , - 2 , - 1 , 0 , 1 , 2 , . . . } , und V(M) bezeichne die Menge aller endlichen Teilmengen der Menge M . ) Diese algebraische Spezifikation kann als eine Implementierung des abstrakten Datentyps GanzeZahlenMenge angesehen werden. Die Sorte GanzeZahlenMenge wird hier durch eine Menge repräsentiert, deren Elemente endliche Teilmengen der Menge der ganzen Zahlen sind. Durch die Wahl des Symbols Z wird festgelegt, daß für die Menge der ganzen Zahlen die übliche Repräsentation { . . . , —2, —1,0,1, 2 , . . . } gewählt wird. Es ist einsichtig, daß zu einem abstrakten Datentyp (beliebig) viele algebraische Spezifikationen existieren können, die genau diesen Datentyp implementieren. Fragestellungen, die die Semantik-erhaltende Implementierung von abstrakten Datentypen durch Algebren oder die semantische Äquivalenz von algebraischen Spezifikationen betreffen, sind ebenfalls theoretisch ausführlich behandelt worden. Aus der expliziten Definition der Operationen durch Funktionen ist ihre Implementierung mit einer (prozeduralen) Programmiersprache in der Regel leichter abzuleiten als aus der impliziten Definition mit Axiomen. Aus den Gleichungen für die Operationen müssen explizite Darstellungen der Operationen erst herausgefunden werden, bevor sie als Prozeduren implementiert werden können. Generische Datenmodule können mit algebraischen Spezifikationen ebenfalls beschrieben werden. Gerade hinsichtlich Wiederverwendbarkeit von Entwürfen und Implementierungen ist es sinnvoll, die obige Aufgabenstellung für beliebige
2.3 Modularisierung und Datenkapselung
47
Mengen zu lösen. Wird dann konkret eine Spezifikation für eine Menge mit einem bestimmten T y p von Elementen benötigt, muß diese aus der generischen Spezifikation unter Angabe des Typs erzeugt werden. Eine generische algebraische Spezifikation für Mengen unabhängig vom T y p ihrer Elemente kann wie folgt aussehen: algebra Menge(Item) Sorten Menge, Item, Bool Operationen LeereMenge: Einfügen: Menge x Item Löschen: Menge x Item Enthalten: Menge x Item IstLeer: Menge träger Menge = "P(Item) funktionen LeereMenge Einfügen(M,i ) Löschen(M,i) Enthalten(M, i) IstLeer(M) end Menge
—> —• —> —• —>
Menge Menge Menge Bool Bool
= = = = =
0 M U{i} M - {i} (i e M) (M = 0)
Diese Spezifikation beschreibt also eine "Klasse" von Mengen. Die Spezifikation einer Menge ganzer Zahlen für die obige Aufgabenstellung kann (in einer fiktiven Spezifikationssprache) etwa durch den Aufruf GanzeZahlenMenge := new(Menge(GanzeZahl)); erzeugt werden, wobei GanzeZahl bereits definiert sein muß. Auf diese Weise ist die Erzeugung von Spezifikationen von Mengen mit beliebigen Arten von Elementen möglich. Soll zum Beispiel eine Menge von Prozessen verwaltet werden, und Prozesse seien beschrieben durch die folgende (Pascal-ähnliche) Typdefinition type Prozeß
=
record Id: integer; Beschreibung: end;
dann würde
... ;
2 Software-Engineering
48
ProzeßMenge := new(Menge(Prozeß)); eine e n t s p r e c h e n d e Spezifikation erzeugen.
Datenstrukturen und Algorithmen Bei der I m p l e m e n t i e r u n g in einer (prozeduralen) P r o g r a m m i e r s p r a c h e m u ß festgelegt werden, mit welchen D a t e n s t r u k t u r e n die D a t e n eines M o d u l s dargestellt werden und welche A l g o r i t h m e n die O p e r a t i o n e n realisieren sollen. Es gibt eine R e i h e von Möglichkeiten, geeignete D a t e n s t r u k t u r e n u n d A l g o r i t h m e n auszuwählen. So können Mengen mit Hilfe von linearen Listen, mit Hilfe von Feldern oder mit Hilfe von B ä u m e n d a r g e s t e l l t werden. Bei Listen und Feldern kann z u d e m festgelegt werden, o b die E l e m e n t e sortiert sein sollen o d e r nicht, bei B ä u m e n z. B., ob sie balanciert sein sollen oder nicht. E n t s p r e c h e n d der g e w ä h l t e n D a t e n s t r u k t u r sind die A l g o r i t h m e n zur Realisierung der O p e r a t i o n e n zu beschreiben. Z. B. kann bei einer u n s o r t i e r t e n Liste das Einfügen a m L i s t e n a n f a n g erfolgen, wobei aber vorher zu p r ü f e n ist, ob das e i n z u f ü g e n d e Element b e r e i t s in der Liste e n t h a l t e n ist. Entscheidender G e s i c h t s p u n k t für die Auswahl von D a t e n s t r u k t u r e n u n d Alg o r i t h m e n ist die Effizienz, d. h. insbesondere die Laufzeit des A l g o r i t h m u s , aber auch der Speicherplatz f ü r die D a t e n s t r u k t u r . Weiterhin spielt die D y n a m i k eine Rolle, d. h. die Frage, o b eine D a t e n s t r u k t u r ausreicht, in der nur eine feste Anzahl von Elementen abgelegt werden kann, wie z. B. Felder, o d e r ob eine D a t e n s t r u k t u r erforderlich ist, die i m P r i n z i p beliebig viele E l e m e n t e a u f n e h m e n kann, wie z. B. Listen u n d B ä u m e . Aus der A n w e n d u n g k o m m e n d e A n f o r d e r u n g e n , wie z. B. die m a x i m a l zu e r w a r t e n d e A n z a h l von Elementen oder die Häufigkeiten, mit den die jeweiligen O p e r a t i o n e n b e n ö t i g t werden, sind entscheidende Kriterien f ü r die Auswahl. Es kann sich als sinnvoll erweisen, für die I m p l e m e n t i e r u n g der O p e r a t i o n e n Hilfsoperationen e i n z u f ü h r e n . W i r d z. B. als D a t e n s t r u k t u r ein sortiertes Feld ausg e w ä h l t , kann f ü r das Suchen von E l e m e n t e n , das bei den O p e r a t i o n e n Einfügen, Löschen und Enthalten n o t w e n d i g ist, eine Hilfsfunktion f ü r b i n ä r e Suche eingef ü h r t werden, die von allen drei O p e r a t i o n e n b e n u t z t werden k a n n . Diese Hilfsf u n k t i o n ist eine interne, versteckte Funktion (englisch: hidden function), die nicht nach außen sichtbar ist. Sie erscheint deshalb auch nicht im e n t s p r e c h e n d e n abs t r a k t e n D a t e n t y p bzw. in der entsprechenden algebraischen Spezifikation in der S i g n a t u r ; sie ist kein E l e m e n t der E x p o r t - S c h n i t t s t e l l e des e n t s p r e c h e n d e n Moduls. F ü r das obige Beispiel werden hier keine D a t e n s t r u k t u r u n d keine A l g o r i t h m e n a n g e g e b e n . Es sei den L e s e r i n n e n überlassen, sich verschiedene geeignete Datens t r u k t u r e n u n d A l g o r i t h m e n f ü r GanzeZahlenMenge zu überlegen.
2.3 Modularisierung und Datenkapselung
49
Programm-Module Bei der Implementierung stellt sich die Frage, inwieweit die gewählte Programmiersprache das Modulkonzept bzw. die Implementierung von abstrakten Datentypen oder von algebraischen Spezifikationen unterstützt. Sprachen wie Fortran, Cobol, P L / 1 , Pascal und C bieten hierfür keine Unterstützung. Es bleibt der Programmiertechnik und der Disziplin der Programmiererinnen überlassen, das Modulkonzept auch mit diesen Sprachen so weit wie möglich zu verwirklichen. Andere Programmiersprachen wie Simula, Ada und Modula-2 unterstützen die Implementierung von abstrakten Datentypen oder algebraischen Spezifikationen. Module können z. B. in Modula-2 durch modules implementiert werden. Ein Module besteht aus zwei Teilen, dem definition module und dem implementation module, die im Quellcode eines Programmsystems räumlich getrennt voneinander aufgeführt werden können. Der definition module entspricht der Signatur eines abstrakten Datentyps bzw. einer algebraischen Spezifikation, im implementation module werden die Trägermengen durch Datenstrukturen und die Funktionen durch Prozeduren implementiert. Nur das definition module ist nach außen sichtbar (Export-Schnittstelle), es stellt die Dienstleistungen dar, die dieses Modul erbringt. Das implementation module bleibt verborgen und kann verändert oder gegen ein anderes ausgetauscht werden, ohne daß andere Module des Programmsystems davon betroffen werden. Ein definition module in Modula-2 für obiges Beispiel kann wie folgt aussehen: d e f i n i t i o n m o d u l e GanzeZahlenMenge; e x p o r t LeereMenge, Einfügen, Löschen, E n t h a l t e n , I s t L e e r ; p r o c e d u r e LeereMenge; p r o c e d u r e Einfügen (Element: integer); p r o c e d u r e Löschen (Element: integer); p r o c e d u r e Enthalten(Element: integer): boolean; procedure IstLeer: boolean e n d GanzeZahlenmenge. Im implementation
module
i m p l e m e n t a t i o n m o d u l e GanzeZahlenMenge;
e n d GanzeZahlenMenge. werden die D a t e n s t r u k t u r für die Menge, die Hilfsfunktionen und die im definition module aufgeführten Prozeduren codiert.
50
2 Software-Engineering
Die Implementierung generischer Datenmodule wird von Modula-2 nicht unterstützt. In Ada ist die Implementierung generischer Module mit Hilfe von generic packages möglich. Ohne auf die benutzten Ada-Sprachelemente im einzelnen einzugehen, sei folgende Ada-Implementierung des generischen Datenmoduls Mengen angegeben: generic t y p e Item is p r i v a t e ; p a c k a g e Mengen is t y p e Menge is limited p r i v a t e ; p r o c e d u r e LeereMenge(M: out Menge); p r o c e d u r e Einfügen(M: in o u t Menge; Element: in I t e m ) ; p r o c e d u r e Löschen(M: in out Menge; Element: in I t e m ) ; p r o c e d u r e Enthalten(M: in Menge; Element: in Item) return boolean; p r o c e d u r e I s t L e e r ( M : in Menge) r e t u r n b o o l e a n ; private t y p e Menge is D e k l a r a t i o n der D a t e n s t r u k t u r zur Speicherung d e r Menge, z . B. s o r t i e r t e s
Feld
e n d Mengen; p a c k a g e b o d y Mengen is; Implementierungen der Prozeduren (auf dem s o r t i e r t e n F e l d ) e n d Mengen; Ein spezielles Programm-Modul, z. B. für eine Menge ganzer Zahlen, welches etwa den abstrakten Datentyp bzw. die algebraische Spezifikation GanzeZahlenMenge implementiert, wird erzeugt durch den Aufruf p a c k a g e GanzeZahlenMenge is new Mengen(Item
integer);
Zum Schluß dieses Abschnitts sei bemerkt, daß Spezifikationssprachen in der Entwicklung sind, die ablauffähig sind, d. h. die Implementierung eines Moduls auf einem Rechnersystem kann unmittelbar als abstrakter Datentyp oder als algebraische Spezifikation in einer entsprechenden Spezifikationssprache vorgenommen werden. Die Wahl von geeigneten Datenstrukturen und Algorithmen und die Ubersetzung in ausführbaren Code wird automatisch durchgeführt. Möglicherweise
2.3 Modularisierung und Datenkapselung
51
können zu einem Modul auch mehrere, unterschiedliche Algorithmen und Datenstrukturen bzw. unterschiedliche Prozeduren automatisch generiert werden. Zur Ausführungszeit kann dann die gewünschte, z. B. die für den konkreten Anwendungsfall Laufzeit-effizienteste Version, ausgewählt werden.
52 L i t e r a t u r
2 Software-Engineering
h i n w e i s e
Software-Qualitäten, deren Erreichen das prinzipielle Ziel von Software-Engineering ist, sowie Vorgehensmodelle u n d ihre Phasen werden in allen Büchern über Software-Engineering behandelt. Eine Auswahl davon ist z. B. Balzert (1982), Fairley (1985), Kimm et al. (1979), Nagl (1990), Schulz (1990), Sommerville (1987) und Stetter (1987). D a s Prinzip der Datenkapselung bzw. das Geheimhaltungsprinzip geht wesentlich auf D. L. P a r n a s zurück, der in P a r n a s (1972) grundlegende Kriterien für die Zerlegung von Systemen in Module angibt; in P a r n a s (1977) diskutiert er die wesentliche Bedeutung von Spezifikationen für die Software-Entwicklung. Die in diesen Arbeiten behandelten Aspekte sind unter den Schlagwörtern Abstrakte Datentypen und Algebraische Spezifikationen in vielfältiger Weise, insbesondere theoretisch, weiterentwickelt worden. Grundlegende Arbeiten dazu sind z. B. Zilles (1974), Liskov und Zilles (1975), Guttag (1977) sowie Goguen et al. (1977). Ergebnisse der Entwicklungen sind etwa in Ehrig und Mahr (1985, 1990), Bauer et al. (1985, 1987) sowie in Liskov u n d G u t t a g (1986) zu finden. Eingegangen sind diese Ideen in die Programmiersprachen Modula-2 (Wirth 1986) und Ada (Nagl 1982, Booch 1987a,b) mit dem module- bzw. mit dem package-Konzept. Der Entwurf von Software-Architekturen, bei d e m Module eine wesentliche Rolle spielen, ist ein zentrales T h e m a von Nagl (1990). Der ModulbegrifF und M o d u l a r t e n werden dort ausführlich aus Software-technologischer Sicht behandelt. Methoden und Techniken für den Software-Entwurf werden in Schulz (1990) vorgestellt. D a t e n s t r u k t u r e n und Algorithmen werden umfassend in O t t m a n n und Widmayer (1990) behandelt. Abgrenzungen zwischen a b s t r a k t e n D a t e n t y p e n , algebraischen Spezifikationen, Datenstrukturen und Algorithmen sowie P r o g r a m m Modulen werden in Güting (1990) vorgenommen.
Kapitel 3 Grundkonzepte objekt orientierter Programmierung In diesem Kapitel werden die grundlegenden Konzepte und Begriffe der objektorientierten Programmierung vorgestellt. Einheitliche, endgültige Definitionen der im Z u s a m m e n h a n g mit Objektorientierung verwendeten Begriffe können zur Zeit noch nicht gegeben werden. Fraglich ist, ob ü b e r h a u p t einheitliche Definitionen zu erwarten sind. Allein der Begriff Objekt wird in verschiedenen Bereichen der Informatik (Software-Engineering, Programmierung und Programmiersprachen, Datenbanksysteme, Wissensrepräsentation) unterschiedlich definiert und verwendet. Selbst innerhalb der genannten Bereiche existieren unterschiedliche Sichtweisen auf die Begriffe u n d d a m i t unterschiedliche Auffassungen und Definitionen, obwohl in letzter Zeit beobachtet werden kann, daß innerhalb einiger der genannten Bereiche Versuche hin zu einheitlichen Begriffsbildungen u n t e r n o m m e n werden. Im folgenden werden die Konzepte • Modularisierung und Datenkapselung, nun aus objektorientierter Sicht, • Objekt-Identität, • Nachrichten u n d Uberladen (Polymorphismus) sowie • Klassen, Vererbung und Exemplare näher betrachtet. Diese werden im allgemeinen aus der Sicht der objektorientierten P r o g r a m m i e r u n g als grundlegend angesehen. Dabei muß bemerkt werden, daß von den erwähnten Konzepten nur Klasse und Vererbung spezifisch objektorientierte Konzepte sind, alle anderen Konzepte sind — möglicherweise mit anderen Bezeichnungen versehen — auch in anderen Programmierstilen und Programmiersprachen zu finden oder generell grundlegende Konzepte von P r o g r a m m i e r m e t h o d e n u n d Programmiersprachen.
54
3 Grundkonzepte
Die G r u p p i e r u n g e n der Begriffe in der — deutlicher wird d a s d u r c h Querverweise welche die K o n z e p t e in der Reihenfolge der einzeln o h n e Bezüge a u f e i n a n d e r dargestellt liegen die drei A s p e k t e •
obigen A u f z ä h l u n g d e u t e n bereits an zwischen den folgenden A b s c h n i t t e n , Auflistung b e h a n d e l n — d a ß sie nicht werden k ö n n e n . Der obigen Einteilung
Objekt,
• K o m m u n i k a t i o n bzw. K o o p e r a t i o n u n d •
Abstraktion
zugrunde.
3.1
Objekte
W i e bereits i m einleitenden K a p i t e l e r w ä h n t u n d a n einem Beispiel gezeigt, sind O b j e k t e a t o m a r e E i n h e i t e n , die mit Hilfe von Nachrichten m i t e i n a n d e r k o m m u nizieren (siehe A b b i l d u n g 3.1). I m G e g e n s a t z zur prozeduralen Sicht, welche Software als aus zwei Teilen besteh e n d b e t r a c h t e t , nämlich einerseits aus passiven, zu b e a r b e i t e n d e n D a t e n u n d a n d e r e r s e i t s a u s A k t i o n e n ( P r o z e d u r e n ) , welche die B e a r b e i t u n g e n d u r c h f ü h r e n , k e n n t die o b j e k t o r i e n t i e r t e Sicht nur eine Einheit, nämlich d a s O b j e k t , welches D a t e n u n d P r o z e d u r e n u m f a ß t . Ein O b j e k t , d. h. der Z u s t a n d eines O b j e k t e s , k a n n wie D a t e n in P r o g r a m m e n prozeduraler Sprachen v e r ä n d e r t werden. Die V e r ä n d e r u n g e n können allerdings nur von O b j e k t - e i g e n e n P r o z e d u r e n , in o b j e k t o r i e n t i e r t e n P r o g r a m m i e r s p r a c h e n auch Methoden g e n a n n t , a u s g e f ü h r t werden. 1 O b j e k t o r i e n t i e r u n g verlangt die E i n h a l t u n g des M o d u l k o n z e p t s bzw. das Konz e p t der D a t e n k a p s e l u n g in seiner strengsten Form (siehe K a p i t e l 2.3). Ein O b j e k t g e s t a t t e t a n d e r e n O b j e k t e n keinen direkten Zugriff auf seine D a t e n u n d O p e r a t i o n e n . Die Dienstleistungen eines O b j e k t e s sind nur ü b e r Nachrichten a n f o r d e r b a r . Die Dienstleistungen werden d u r c h die M e t h o d e n des O b j e k t e s realisiert. O b j e k t e kapseln also ihren Z u s t a n d u n d ihr Verhalten zu einer a t o m a r e n E i n h e i t . Der A u f b a u von M e t h o d e n in objektorientierten P r o g r a m m i e r s p r a c h e n u n d von P r o z e d u r e n in prozeduralen P r o g r a m m i e r s p r a c h e n ist, abgesehen von j e nach Prog r a m m i e r s p r a c h e m e h r oder weniger großen syntaktischen Unterschieden, ähnlich (Folgen von Anweisungen bzw. Folgen von Nachrichten). Ein wesentlicher Unterschied besteht d a r i n , daß M e t h o d e n sich nicht, im G e g e n s a t z zu P r o z e d u r e n , gegenseitig a u f r u f e n können u n d das M e t h o d e n höchstens lokale D a t e n , d. h. die D a t e n des O b j e k t e s , dessen Bestandteil sie sind, ä n d e r n k ö n n e n . M e t h o d e n können also nicht isoliert, sondern n u r über O b j e k t e angestoßen werden. ' I m folgenden werden, wenn im Kontext nicht anders verwendet, Prozeduren im Zusammenh a n g m i t prozeduraler Programmierung "Prozeduren" und Prozeduren im Z u s a m m e n h a n g mit objektorientierter P r o g r a m m i e r u n g " M e t h o d e n " genannt.
55
3.1 Objekte
Abbildung 3.1: G r u n d s t r u k t u r objektorientierter Systeme
56
3 Grundkonzepte
Objekt-Identität
Daten Nachrichten von anderen Objekten
i
-i
Nachrichten an andere Objekte
Methoden
Abbildung 3.2: Objekt-Aufbau Objekte besitzen drei wesentliche Eigenschaften (siehe Abbildung 3.2): • Ein Objekt ist ein Exemplar (eine Instanz) einer Klasse, damit besitzt es einen (zeitinvarianten) Typ. Klassen werden ausführlich in Kapitel 3.3 betrachtet. • Jedes Objekt besitzt einen (zeitvarianten) Zustand, der durch die aktuellen Werte seiner Zustandsvariablen festgelegt ist (siehe Abschnitte 3.1.1 und 3.1.2). • Jedes Objekt besitzt eine eindeutige (zeitinvariante) Identität schnitt 3.1.3).
(siehe Ab-
In den meisten der zur Verfügung stehenden objektorientierten Programmiersprachen und Anwendungssysteme weichen die Eigenschaften der in diesen Systemen erzeugbaren Objekte von den hier genannten ab. Darauf wird später an den entsprechenden Stellen genauer eingegangen.
3.1 Objekte
3.1.1
57
Zustand von Objekten
Der Z u s t a n d eines O b j e k t e s wird d u r c h Zustandsvariable — auch Instanzvariable o d e r Attribute g e n a n n t — u n d d u r c h deren Werte festgelegt. Z u s t a n d s v a r i a b l e legen d a b e i die zeitinvarianten Eigenschaften des O b j e k t e s fest, d. h. die Zustandsvariablen eines O b j e k t e s sowie ihr T y p können nicht v e r ä n d e r t werden. Ihre Werte sind hingegen zeitvariant, d. h. diese können (durch die M e t h o d e n ) v e r ä n d e r t werden. So wird e t w a im Beispiel z u m Sieb des Eratosthenes im K a p i t e l 1.3 der zeitinvariante Z u s t a n d des E r z e u g e r - O b j e k t e s durch die Variablen NächsteZahl: integer; Obergrenze: integer; Siebanfang: Sieb; festgelegt. Die d u r c h die M e t h o d e n v e r ä n d e r b a r e n W e r t e der Z u s t a n d s v a r i a b l e n k ö n n e n im allgemeinen von beliebigem T y p sein. Es können a t o m a r e W e r t e sein, wie im Beispiel integer-Werte f ü r die Variablen NächsteZahl und Obergrenze, es k ö n n e n s t r u k t u r i e r t e W e r t e sein wie Felder, Mengen o d e r Records, o d e r es können O b j e k t e sein, wie i m Beispiel O b j e k t e der Klasse Sieb f ü r die Variable Siebanfang. In einem " d u r c h g e h e n d " o b j e k t o r i e n t i e r t e n System, wie z. B. Smalltalk-80, sind alle Variablenwerte O b j e k t e , also auch integer-Zahlen oder komplex a u f g e b a u t e D a t e n s t r u k t u r e n (Felder, Listen, B ä u m e ) . Dieser A s p e k t , d. h., o b Zustandsvariable als W e r t e O b j e k t e , also E l e m e n t e von Klassen, oder Werte, also E l e m e n t e der Wertebereiche von T y p e n h a b e n d ü r f e n , wird später g e n a u e r b e t r a c h t e t . Welche A r t von Werten die Z u s t a n d s v a r i a b l e n eines O b j e k t e s h a b e n k ö n n e n , wird in der Definition der Klasse festgelegt, von der dieses O b j e k t ein E x e m p l a r ist. Der Zugriff auf den W e r t einer Z u s t a n d s v a r i a b l e n ist i m s t r e n g objektorientiert e n Sinn der Aufruf einer M e t h o d e bzw. das Senden einer Nachricht, die den aktuellen Wert der Variablen zurückliefert. So ist die Anweisung Obergrenze := Obergrenze + 1; s t r e n g o b j e k t o r i e n t i e r t b e t r a c h t e t wie folgt zu verstehen: " 1 " ist der N a m e (die O b j e k t - I d e n t i t ä t ) des integer-Objektes, das als Wert die integer-Zahl 1 b e s i t z t . D e r " A u f r u f " von " 1 " , d. h. das Senden dieser Nachricht a n " 1 " , liefert diesen Wert zurück. "Obergrenze" ist eine Nachricht, die den Wert der Variablen Obergrenze zurückliefert. Dieser W e r t sei beispielsweise das O b j e k t mit der I d e n t i t ä t " o " , welches als Wert ( Z u s t a n d ) die integer-Zahl 3 besitze. Die Nachricht "+" bewirkt die " A d d i t i o n " der W e r t e der O b j e k t e "1" und "o". Das R e s u l t a t , das O b j e k t "4" m i t d e m Wert 4, wird m i t der Nachricht " :=" an das O b j e k t gesendet, welches die Nachricht "Obergrenze" auf der linken Seite von " :=" zurückliefert. D a s Ergebnis ist, d a ß das O b j e k t "o" als neuen Z u s t a n d die integer-Zahl 4 b e s i t z t . F ü r die Zuweisung von W e r t e n zu Z u s t a n d s v a r i a b l e n werden in den verschiede-
58
3 Grundkonzepte
nen objektorientierten Programmiersprachen unterschiedliche Symbole benutzt. Die folgende Tabelle zeigt die Symbole für die im Kapitel 5 näher betrachteten Programmiersprachen: Simula Smalltalk C++ Turbo-Pascal
:=
:=
Allein das Symbol von Smalltalk veranschaulicht, daß, wie oben erwähnt, die Zuweisung im streng objektorientierten Sinn das Versenden einer Nachricht bedeutet. Auf die Unterschiede zwischen Objekt und Wert, auf Objekt-Identität sowie auf verschiedene Gleichheitsbegriffe für Objekte (identische Objekte, gleiche Objekte) wird in den folgenden beiden Abschnitten detaillierter eingegangen, indem diese Begriffe präziser definiert werden.
3.1.2
W e r t e und Objekte
In diesem Abschnitt werden die Begriffe Wert und Objekt formal, d. h. mit mathematischen Hilfsmitteln definiert. Diese Begriffe liegen den weiteren Betrachtungen zugrunde. Grundmengen Es wird davon ausgegangen, daß die folgenden Mengen zur Verfügung stehen: • Eine endliche Menge von Wertebereichen Di,..., Dn, n > 1, welche jeweils darstellbare (druckbare) Zeichen einer Art umfassen, z. B. Zahlenmengen, Alphabete, Mengen von Zeichenketten, graphische Symbole. Es sei D = Ui '—
IO IT) O T—
O co O) '—
m CO (35 i—
O
in
•—
o
Ol
*—
o 00 C!
*—
Abbildung 5.1: Entwicklungslinien objektorientierter Programmiersprachen
LO CO o *—
5 Programmiersprachen
140
E i g e n s c h a f t e n ( D a t e n ) sowie die prozeduralen Anweisungen, mit d e n e n Zustandsä n d e r u n g e n d u r c h g e f ü h r t werden können. A u ß e r d e m kann eine a c t i v i t y noch die kontrollierte Z u s t a n d ä n d e r u n g durch a n d e r e activities g e s t a t t e n . D a m i t waren die folgenden o b j e k t o r i e n t i e r t e n K o n z e p t e bereits in Simula I realisiert: • D a t e n u n d O p e r a t i o n e n bilden eine Einheit. • E s b e s t e h t eine klare T r e n n u n g zwischen den O b j e k t e n u n d ihrer Beschreib u n g . Zu einer Beschreibung können beliebig viele gleichartige E x e m p l a r e e r z e u g t werden. • E i n O b j e k t kapselt sein Inneres. Ä n d e r u n g e n des eigenen Z u s t a n d e s von a u ß e n finden nur indirekt über d a s O b j e k t selbst s t a t t . In S i m u l a 67 k a m als wesentliche E r g ä n z u n g d a s Klassenkonzept hinzu. Mit i h m k ö n n e n G e m e i n s a m k e i t e n verschiedener O b j e k t b e s c h r e i b u n g e n herausgelöst u n d n u r einmal übergreifend beschrieben werden.
5.1.1
O b j e k t e und Klassen
Ein O b j e k t u m f a ß t ( s t r u k t u r i e r t e ) D a t e n , O p e r a t i o n e n ( P r o z e d u r e n ) u n d Aktionen. Variablen u n d O p e r a t i o n e n werden in Simula A t t r i b u t e g e n a n n t . E i n e Klasse ist ein M u s t e r f ü r eine Menge von O b j e k t e n , welche dieselben A t t r i b u t e und Anweisungen e n t h a l t e n . Deklaration von Klassen Die D e k l a r a t i o n class Klasse; Spezifikationsteil; Klassenkörper; ist ein M u s t e r f ü r gleichartige O b j e k t e . Jedes nach diesem Muster e r z e u g t e O b j e k t wird d e r Klasse zugehörig g e n a n n t . Somit kann eine Klasse (zur Laufzeit eines S i m u l a - P r o g r a m m s ) auch als Menge m i t den aktuell existierenden O b j e k t e n als E l e m e n t e a u f g e f a ß t werden. Deklaration von Unterklassen Superklasse class Klasse; Spezifikationsteil; Klassenkörper;
5.1 Simula
141
definiert die Unterklasse Klasse zur Klasse Superklasse. S u p e r k l a s s e und Spezifikationsteil können weggelassen werden; fehlt Superklasse, wird eine "normale" Klasse definiert. Das in Kapitel 3.4 betrachtete Beispiel einer Klassenhierarchie aus der Fahrzeugwelt kann in Simula folgendermaßen definiert werden: class Motorfahrzeuge; Spezifikationsteil; Klassenkörper; Motorfahrzeuge class Pkw; Spezifikationsteil; Klass enkörpe r; Motorfahrzeuge class Lkw; Spezifikationsteil; Klassenkörper; Motorfahrzeuge class Motorräder; Spezifikationsteil; Klass e nkörpe r; Pkw class Ford; Spezifikationsteil; Klassenkörpe r; Ford class S c o r p i o ; Spezifikationsteil; Klassenkörper; usw.
Jedes Objekt einer Unterklasse erbt die in seinen hierarchisch übergeordneten Oberklassen definierten Attribute und besitzt diese neben den in der Unterklasse definierten. Es ist erlaubt, Attribute in Unterklassen zu überschreiben. Klassenkörper Der Klassenkörper besteht aus den Deklarationen der Attribute sowie aus einem Aktionsteil: class K l a s s e ;
142
5 Programmiersprachen
Spezifikationsteil; begin Deklaration der Aktionsteil; end
Attribute;
Der Aktionsteil wird immer dann ausgeführt, wenn ein Objekt generiert wird. Mit ihm können also insbesondere Initialisierungen neu erzeugter Objekte implementiert werden. Die Deklarationen der Attribute können aus jeder erlaubten Simula-Deklaration bestehen, d. h. (strukturierte) Variablen, Prozeduren und Klassen können für die Definition neuer Attribute genutzt werden. Spezifikationsteil Der Spezifikationsteil einer Klasse umfaßt drei Listen: • eine Liste der formalen Parameter, • eine Liste der geschützten Attribute und • eine Liste der virtuellen Attribute. Geschützte Attribute werden durch die Angabe von p r o t e c t e d vor dem Zugriff von außen bzw. durch die Angabe von h i d d e n vor dem Zugriff von Unterklassen geschützt. Durch die Angabe von hidden und p r o t e c t e d werden Attribute vor dem Zugriff sowohl von außen als auch vor dem Zugriff von Unterklassen geschützt. Die Spezifikation eines Attributes als virtuell durch die Angabe von virtual erlaubt die Uberschreibung dieser Unterklassen. Eine Klassendeklaration hat also — detaillierter betrachtet — folgendes Aussehen: class Klasse (Liste Spezifikation der Spezifikation der Spezifikation der begin Deklaration der Aktionen; end;
der formalen Parameter); formalen Parameter; virtuellen Attribute; geschützten Attribute; Attribute;
Beispiel: class Motorfahrzeuge;
5.1 Simula
143
protected Brennstoff; h i d d e n p r o t e c t e d Fahrzeuglnfo; begin text Brennstoff; p r o c e d u r e Fahrzeuglnf o; end; Motorfahrzeuge class Pkw; hidden Brennstoff; begin outtext(Brennstoff); ! Ist erlaubt, da B r e n n s t o f f nur in Superklasse Motorfahrzeuge ! geschützt ist, h i d d e n hat nur Effekt auf Unterklassen FahrzeugInfo; ! Ist nicht erlaubt, da in Superklasse p r o t e c t e d und h i d d e n . end; Pkw class Ford; begin outtext(Brennstoff); / Ist nicht erlaubt, da B r e n n s t o f f in Pkw h i d d e n . end;
5.1.2
Erzeugung von Objekten
Ein Objekt der Klasse K l a s s e wird durch die Aktion n e w Klasse erzeugt. Die Aktion bewirkt • daß ein zusammenhängender, nicht belegter Speicherbereich für die in Klasse deklarierten Variablen allokiert wird; • daß diese Variablen mit einem Defaultwert initialisiert werden, der von ihrem T y p abhängt; • daß die Adresse des Speicherbereiches (Referenz, Zeiger, Pointer) benutzt werden kann, u m auf die Variablen des generierten Objektes zugreifen zu können (siehe Abschnitt 5.1.4). Für die anderen Teile eines Objektes, Prozeduren, Klassen und Aktionen, wird kein Speicherbereich allokiert. Da diese für alle Objekte einer Klasse identisch sind,
144
5 Programmiersprachen
benutzen alle erzeugten Objekte die entsprechenden Definitionen in der Klasse (code sharing).
5.1.3
R e f e r e n z t y p e n und -variable
Mit der Definition einer Klasse steht ein Datentyp, Referenztyp genannt, zur Verfügung. Der Referenztyp ref gehört neben integer, real, character, b o o l e a n und t e x t zu den sechs Simula-Standardtypen. Für die Klasse Klasse lautet die Referenztypdeklaration ref(Klasse); Eine Variable RefVar von diesem Typ wird durch r e f ( K l a s s e ) RefVar; definiert, ein Feld RefFeld von diese Typ durch r e f ( K l a s s e ) RefFeld; Eine Referenzvariable des Typs ref(Klasse) enthält als Wert entweder none, d. h. keine Refenrenz, oder sie enthält die Adresse eines mit new (KLasse) erzeugten Objektes (siehe unten). Beispiele: r e f ( S c o r p i o ) MeinAuto; deklariert die Variable MeinAuto vom Referenztyp Sorpio. ref(Scorpio)
ScorpioHalle(l:10);
deklariert ein Feld ScorpioHalle mit zehn Komponenten, wobei jede Komponente vom Typ Sorpio ist, d. h. eine Adresse eines Objektes der Klasse Scorpio aufnehmen kann.
5.1.4
R e f e r e n z z u w e i s u n g e n und -ausdrücke
Mit der Referenzzuweisung RefVar : - RefAusdruck; wird der Referenzvariablen RefVar der Wert des Ausdrucks Ref Ausdruck zuge-
145
5.1 Simula
wiesen. Der Wert dieses Ausdrucks muß n o n e sein oder eine Referenz auf ein Objekt, das in der Klasse oder in einer Unterklasse der Klasse liegt, zu der RefVar definiert worden ist. In Simula gibt es keine Möglichkeit, den Wert einer Referenz zu verändern oder auszugeben, oder einer Referenzvariablen eine Konstante (außer n o n e ) zuzuweisen. Eine Referenzausdruck kann sein • die Konstante n o n e , die "keine Referenz (Adresse)" bedeutet. Mit diesem Wert werden Referenzvariablen defaultmäßig initialisiert. • eine Referenzvariable. Ihr Wert ist die zuletzt zugewiesene Referenz bzw. n o n e , wenn keine Referenz oder zuletzt n o n e zugewiesen wurde. • die Erzeugung
eines Objektes durch
new Klasse; Der Wert dieses Ausdrucks ist die Adresse des erzeugten Objektes. Der mit r e f ( S c o r p i o ) MeinAuto; definierten Referenz variablen MeinAuto wird durch die Referenzzuweisung MeinAuto : - n e w Scorpio; die Adresse eines neu erzeugten Scorpio-Objektes zugewiesen. Die Zustandsa t t r i b u t e dieses Objektes, d. h. die Variablen, werden bei der Erzeugung mit den entsprechenden, typabhängenden Defaultwerten belegt. • ein Funktionsaufruf,
der als Wert eine Referenz zurückliefert.
• ein lokales Objekt, welches durch this Klasse; bestimmt wird. Die Klasse Motorfahrzeuge sei definiert durch: class Motorfahrzeuge; hidden protected Brennstoff; Virtual: p r o c e d u r e I n f o ;
146
5 Programmiersprachen
begin p r o c e d u r e Fahrzeuginfo; begin end; procedure Info; Fahrzeuglnfo; end; Die Unterklasse Pkw von Motorfahrzeuge sei definiert durch: Motorfahrzeuge class Pkw; begin p r o c e d u r e Fahrzeuglnfo; begin end; procedure Info; Fahrzeuglnfo; end; Das Attribut Fahrzeuglnfo wird in Pkw also überschrieben.
Mit dem Referenzausdruck t h i s Motorfahrzeuge.Fahrzeuglnfo in der Prozedur Pkw. Fahrzeuglnfo wird das Attribut Motorfahrzeuge . Fahrz e u g l n f o aufgerufen.
• ein bedingter Referenzausdrauck
der Form
if BoolescherAusdruck t h e n UnbedingterReferenzausdruck eise Referenzausdruck; Hat BoolescherAusdruck den Wert t r u e , wird UnbedingterReferenzAusdruck ausgewertet, ansonsten ReferenzAusdruck.
147
5.1 Simula
5.1.5
Zugriff auf A t t r i b u t e
Auf die Attribute eines Objektes kann, wenn überhaupt erlaubt, von außen auf zwei Arten zugegriffen werden: durch den Selektorzugriff und durch die i n s p e c t Anweisung. Selektorzugriff Mit RefAusdruck.Attributname; wird auf das Attribut Attributname des Objektes zugegriffen, das Wert des Referenzausdrucks Ref Ausdruck ist. Ref Ausdruck muß dabei ein Objekt referenzieren, welches das Attribut Attributname besitzt, sonst führt der Zugriff Ref Ausdruck. Attributname zu einem Laufzeitfehler. inspect-Anweisung inspect-Anweisungen der Form i n s p e c t Ref Ausdruck d o Anweisungl; bzw. der Form i n s p e c t Ref Ausdruck d o Anweisungl otherwise Anweisung2; werden wie folgt ausgewertet: Wenn der Wert von RefAusdruck eine Referenz auf ein Objekt der Klasse Kl ist, wird Anweisungl ausgeführt. Innerhalb von Anweisungl kann jedes in Kl deklarierte Attribut (außer den geschützten) benutzt werden. Wenn der Wert von RefAusdruck n o n e ist, wird Anweisung2 ausgeführt, falls die inspect-Anweisung einen otherwise-Teil enthält. Eine inspect-Anweisung der Form i n s p e c t RefAusdruck d o w h e n Kl d o Anweisungl w h e n K2 d o Anweisung2
148
5 Programmiersprachen
w h e n Kn d o Anweisungn otherwise AnweisungSonst; wird wie folgt ausgewertet: Wenn der Wert von Ref Ausdruck ein Objekt der Klasse Ki oder einer ihrer Unterklassen ist, wird Anweisungi ausgeführt. Wenn sich der Wert von Ref Ausdruck auf kein Objekt der Klassen Kl, . . . , Kn und auf kein Objekt der Unterklassen dieser Klassen bezieht, wird, falls vorhanden, AnweisungSonst ausgeführt. Zu beachten ist, daß die Reihenfolge der when-Zweige wichtig für die Auswirkung einer inspect-Anweisung sein kann. Ist z. B. Kj eine Unterklasse von Ki und i < j , dann wird niemals die Anweisung Anweisungj ausgeführt werden.
5.1.6
D a s Sieb des Eratosthenes
in Simula
Eine prozedurale Lösung Eine prozedurale Lösung des Sieb des Eratosthenes plementiert werden: begin i n t e g e r Max; p r o c e d u r e Sieben; i n t e g e r Zahl, Zaehler; b o o l e a n array Prira(2 :Max) ; begin for Zaehler := 2 s t e p 1 until Max do Prira(Zaehler) := true; Zaehler := 1; Zahl := 1; w h i l e ( Z a h l * Zahl) < Max d o begin Zahl := Zahl + 1; for Zaehler := 2 s t e p 1 until Max d o w h i l e (Zaehler * Zahl) < Max d o begin Prim(Zaehler * Zahl) := false; Zaehler := Zaehler + 1; end; end; for Zaehler := 2 s t e p 1 until Max do
kann mit Simula wie folgt im-
5 . 1 Simula
149
if Prim(Zaehler) then outint(Zaehler) end; inimage; Max : = inint; Sieben; end;
Eine objektorientierte Lösung Eine objektorientierte Lösung des Sieb des Eratosthenes,
die der im K a p i t e l 1.3
mit Hilfe der Pseudosprache angegebenen Lösung entspricht, kann mit Simaula wie folgt implementiert werden:
begin class Sieb(Zahl) ; integer Zahl; protected Zahl, Prim, Next; begin integer Prim; r e f ( S i e b ) Next; procedure Sieben(Zahl) ; integer Zahl; begin if mod (Zahl, Prim) =/= 0 then begin if Next =/= none then Next.Sieben(Zahl) else Next : - new Sieb (Zahl) ; end; end; Prim := Zahl; outint (Prim) ; end; r e f ( S i e b ) StartSieb; integer Zaehler, Max; inimage; Max := inint; StartSieb : - new S i e b ( 2 ) ; for Zaehler := 3 step 1 until Max do
5 Programmiersprachen
150 StartSieb.Sieben(Zaehler); end;
Laufzeitvergleiche zeigen, daß jeweils für dieselbe Siebgröße die objektorientierte Version deutlich mehr CPU-Zeit benötigt als die prozedurale.
5.1.7
Schlußbemerkungen
Simula unterstützt • die prozedurale Programmierung: Variablen repräsentieren den Inhalt von Speicherplätzen, Typen legen Wertemengen fest und Programme bestehen aus Folgen von Anweisungen (elementare Anweisungen, Kontrollanweisungen, zusammengesetzte Anweisungen, Prozeduraufrufe), die auf Variablen operieren und Variableninhalte verändern. • die objektorientierte Programmierung: Ein Simula-Programm besteht im wesentlichen aus Klassendefinitionen. Im "Hauptprogramm" wird lediglich eine Anfangskonfiguration von Objekten hergestellt. Der weitere Programmablauf wird durch die (über Referenzvariable und Referenzausdrücke) kooperierenden Aktionen der existierenden Objekte bestimmt. • die Beschreibung abstrakter Datentypen: Mit Hilfe von Klassendefinitionen können in Simula abstrakte Datentypen beschrieben werden. Der Spezifikationsteil beschreibt die Signaturen und die Exportmöglichkeiten, im Klassenkörper werden die Operationen implementiert. G r ü n d e dafür, warum die Programmiersprache Simula trotz ihrer "modernen" Konzepte keine Verbreitung gefunden hat, sind in Kapitel 1.1 aufgeführt und sollen an dieser Stelle nicht wiederholt werden.
5.2
Smalltalk
Die Ideen und Ziele, die Ausgangspunkt für die Entwicklung des Smalltalk-80Systems waren, wurden in Abschnitt 1.2.1 vorgestellt und sollen an dieser Stelle nicht wiederholt werden. Vorgestellt werden in diesem Kapitel grundlegende Eigenschaften der objektorientierten Programmiersprache Smalltalk. Die Eigenschaften des Smalltalk-80Systems als Arbeitsplatz- und Programmentwicklungssystem werden nicht behandelt. Die Grundkonzepte von Smalltalk sind: • Objekt (object): Jede Komponente im Smalltalk-80-System ist ein Objekt. Ein Objekt besteht aus einem lokalen Speicher und einer Menge von Operationen (Methoden). Der lokale Speicher repräsentiert den aktuellen Zustand
5.2 Smalltalk
151
des Objektes. Die Operationen sind abhängig von der Klassenzugehörigkeit des Objektes. O b j e k t e können — Nachrichten an O b j e k t e senden, — Nachrichten von Objekten empfangen, — ihren Z u s t a n d ändern. • Nachricht (auch: Botschaft; message): O b j e k t e kommunizieren miteinander durch d a s Versenden von Nachrichten. Ein Sender fordert über eine Nachricht vom E m p f ä n g e r die Ausführung einer Operation. Die Art und Weise, wie die O p e r a t i o n ausgeführt wird ist in einer entsprechenden Methode des Empfängers implementiert, nach außen nicht sichtbar und schon gar nicht von außen beeinflußbar. • Methode (method): Eine Methode ist die Implementierung einer Operation. Das E m p f a n g e n einer Nachricht veranlaßt ein Objekt, eine entsprechende Operation auszuführen. Methoden können — den Zustand eines Objektes ändern, — Nachrichten versenden, — den Zugriff auf (Instanz-) Variablen realisieren. • Klasse (class): Eine Klasse beschreibt die gleichartigen Eigenschaften einer Menge von O b j e k t e n . • Exemplar (auch: Instanz; instance): Exemplare sind die existierenden Obj e k t e einer Klasse. Jedes Objekt ist Exemplar einer Klasse, jede Klasse ist E x e m p l a r einer Metaklasse. Die Ausprägungen dieser Konzepte sowie Vererbung (inheritance) morphismus (pohjmorphism) werden im folgenden n ä h e r betrachtet.
5.2.1
und
Poly-
Grundelemente
Literale Literale (auch: K o n s t a n t e n ) sind nicht veränderbare Objekte. Sie umfassen • Zahlen (numbers): Zahlen sind numerische O b j e k t e und antworten auf Nachrichten, die die Ausführung arithmetischer Operationen anfordern. Numerische O b j e k t e sind — ganze Zahlen wie z. B. 7 ,
-13
152
5 Programmiersprachen
— rationale Zahlen wie z. B. 4/7 — reelle Zahlen wie z. B. 4 . 8 7 E - 5 - Oktalzahlen wie z. B . 8R17 - Hexadezimalzahlen wie z. B. 16R1AF3 • Zeichen
(characters):
Zeichen werden in der Form
$ASCII-Zeichen dargestellt, wobei ASCII-Zeichen jedes der 256 8-Bit-ASCII-Symbole sein kann. Beispiele:
$A, $ 7 ,
$*
Eine Nachricht, die an ein Zeichenobjekt geschickt werden kann, ist z. B. a s c i i V a l u e . Sie liefert den ASCII-Wert, dem das Empfängerobjekt entspricht. So liefert $A a s c i i V a l u e das ganze Zahl-Objekt 65. Umgekehrt ergibt die Nachricht asCharacter, an eine ganze Zahl zwischen 0 und 256 gesendet, das entsprechende ASCIISymbol. 65 a s C h a r a c t e r ergibt also $A. • Zeichenketten (strings): Eine Folge von Zeichen (ohne $), eingeschlossen in Hochkommata ist ein Zeichenketten-Objekt. Beispiele:
'Smalltalk',
' e i n s plus eins g l e i c h zwei'
Nachrichten, die an Zeichenketten-Objekte geschickt werden können, sind z. B. s i z e , welche die Länge einer Zeichenkette liefert, oder a t und put, die den Zugriff bzw. die Veränderung von Symbolen einer Zeichenkette ermöglichen. So liefert 'drei'
size
den Wert (das ganze Zahl-Objekt) 4 und ' I c h und du' a t : 9 put:
$D
verändert die Zeichenkette 'Ich und du' zu ' I c h und Du'.
153
5.2 Smalltalk
• Symbole
(symbols):
Symbole sind Objekte, die in der Form
zeichenkette notiert werden, wobei z e i c h e n k e t t e eine Zeichenkette ist. Im Gegensatz zu Zeichenketten können Symbole aber nicht verändert werden. • Felder (arrays): Felder sind Objekte, die aus einer endlichen Folge von Objekten bestehen. Sie werden in der Form #(eleraenti element2 . . . e l e m e n t n ) notiert, i ,
1 < i < n, ist der Index des i-ten Elementes element,.
Beispiele: # ( 1 2 3) # ( ' e i n s ' 'zwei' 'acht' 'Small') « ( ' s i e b e n ' ( v i e r 17) $B h e u t e ) Die Elemente eines Feldes müssen nicht vom selben Typ sein. Symbole und Felder innerhalb eines Feldes brauchen nicht mit # versehen zu werden. Für Feldobjekte stehen eine Reihe von Nachrichten zur Verfügung, die den Zugriff auf ihre Elemente sowie die Manipulation ihrer Elemente ermöglichen. Variablen Variablen können als symbolische Namen (auch: Behälter) für Objekte aufgefaßt weiden. Diese O b j e k t e sind Zeiger (pointer), d. h. Speicheradressen. Die Speicherverwaltung wird komplett vom Smalltalk-System geleistet. Variable erhalten vor einer expliziten Wertzuweisung defaultmäßig den Wert (das Objekt) n i l zugewiesen. Eine Konstante bezieht sich immer auf dasselbe Objekt, d. h. dieselbe Adresse. Ein Variableninhalt kann verändert werden, d. h. Variablen können nacheinander verschiedene Objekte referenzieren. Die Anweisung var