Zusammenfassung des Buchs Objektorientiertes Plug und Play
© Prof. Dr. Andreas Solymosi

Sprachunabhängiger Programmierunterricht "von oben"

"Programmieren im Großen" vor "Programmierern im Kleinen"

Einführung

Die Zeiten des Programmierens sind vorbei.

Vor einem Jahrzehnt hatten Informatiker mit intensiven Programmierkenntnissen durchaus gute Berufschancen; heute sind solche nicht mehr gefragt. Nur ein kleiner Teil der berufstätigen Informatiker beschäftigt sich mit der Formulierung neuer Algorithmen. Eine größere Anzahl wird eingesetzt, um Hardwarebausteine und Softwarekomponenten zusammenzufügen sowie den aktuellen Bedürfnissen anzupassen. Sie müssen die Interna dieser Bausteine nicht kennen - sie betrachten nur ihre Schnittstelle. Sie müssen sich entscheiden, inwiefern die gebotene Funktionalität für die aktuelle Aufgabe relevant ist und wie sie mit anderen Komponenten zusammenspielen kann. Das altbekannte Programmieren von Verzweigungen und Schleifen, Integers und Characters, Bits und Bytes, kommt nur am Rande vor.

Diese Erfahrung bringt Hochschulen mit Informatikstudiengängen dazu, dem Fach Software Engineering ("Programmierung im Großen") mehr Gewicht zu geben. Trotzdem wird es erst in höheren Semestern angeboten, während Studienanfänger das Fach Programmieren ("Programmierern im Kleinen") belegen und sich Fähigkeiten, gebunden an eine bestimmte Programmiersprache, aneignen. Das Paradigma der ersten Unterrichtssprache prägt sie dann auch im Laufe ihres späteren Studiums und Berufslebens.

Diese Überlegung ist die Quelle des Konzepts, Programmiersprachen "von oben" zu unterrichten. Hierbei sollen nicht mehr die sprachlichen Strukturen, sondern das Zusammenfügen vorhandener Programmbausteine im Vordergrund stehen. Dies bedeutet einerseits, daß der Student zu Beginn die "höheren" Sprachkonzepte (wie Module, Klassen, generische Einheiten, usw.) kennenlernt. Konventionell programmiert man überwiegend mit Hilfe der "niedrigen" Sprachelemente (wie Bits, Bytes, Ganzzahlen, Verzweigungen, Schleifen); das Programmieren von oben fördert das abstrakte Denken: Die Benutzung von Programmbausteinen ist auch ohne die Kenntnis ihres Innenlebens möglich.

Die Einführung von Verzweigungen, Wiederholungen und sogar der Ganzzahlen wird so weit wie möglich nach hinten geschoben. Dafür werden Module, abstrakte Datentypen und Schablonen gleich zu Anfang benutzt, ohne dabei zu erläutern, was sich hinter ihnen versteckt. Die Studenten sollen sich - im Gegensatz zur üblichen Praxis - daran gewöhnen, zuerst nach vorhandenen Bausteinen zu suchen, und erst wenn sie keine finden, selber zu programmieren.

Das vorgestellte Unterrichtskonzept und Programmierparadigma sind an sich sprachunabhängig. Bis jetzt wurden Lehrbücher für die Sprachen Ada, C++ und Pascal entwickelt; andere Sprachen (wie Oberon und Java) sind in Vorbereitung. Eine Voraussetzung hierfür ist Objektorientierung, auch wenn Vererbung und Polymorphie erst zum Schluß als Höhepunkte wirklich ausgenutzt werden. Einer der Hauptgedanken des objektorientierten Programmieren, die Wiederverwendbarkeit, wird jedoch von Anfang an stark betont.

Überblick über das Curriculum

Die vorgeschlagene Gliederung des Curriculums ist folgende:

  1. Informelle Einführung
  2. Abstrakte Datenobjekte und -typen (Module, Klassen, Prozeduren)
  3. Werte
  4. Ereignissteuerung (Menüs und Auswahllisten)
  5. Standard-Aufzählungstypen (Character und Boolean)
  6. Definition von Aufzählungstypen
  7. Abstrakte Multibehälter (Mengen, Säcke, Folgen u.a.)
  8. Konglomerate (Felder und Verbunde)
  9. Dynamische Datentypen (Verweise)
  10. Ganzzahlen (Integer u.a.)
  11. Brüche (Float u.a..)
  12. Steuerstrukturen (Verzweigungen, Wiederholungen, Rekursion)
  13. Vererbung und Polymorphie
  14. Nebenläufigkeit

Hier fällt auf, daß - im Gegensatz zu üblichen Curricula - die abstrakten Konzepte immer vor den konkreten (d.h. von der Programmiersprache angebotenen) Konzepten vorgestellt werden: abstrakte Datenobjekte (realisiert durch Datenabstraktionsmodule) vor den konkreten ("Variablen"), importierte Werte (eines Aufzählungstyps) vor eigendefinierten, abstrakte Multibehälter (Mengen, Warteschlangen, usw.) vor den konkreten Sprachelementen wie Felder und Verbunde. Aus ähnlichen Überlegungen werden die Ganzzahlen und Brüche erst gegen Ende des Kurses definiert; die abstrakteren Aufzählungen werden bevorzugt verwendet.

Die nicht programmierte Abweichung von der linearen Programmausführung (wie Ausnahmebehandlung und Ereignissteuerung) wird ebenso bevorzugt gegenüber traditionellen Steuerstrukturen (wie if und while). Hierdurch soll die Programmierung kurzer Einheiten (wie Rückrufprozeduren und Blöcke) gefördert werden.

In der Gliederung wechseln sich sprachunabhängige und sprachnahe Inhalte ab, wobei die ersten den Vorrang haben: Die Themen Abstrakte Datenobjekte und -typen, Werte und Ereignissteuerung können unabhängig von der aktuellen Programmiersprache behandelt werden, während das Thema Standard- und eigendefinierte Aufzählungstypen eng an die Sprachelemente geknüpft ist. Ähnlich sind abstrakte Multibehälter sprachunabhängig; Konglomerate und dynamische Datentypen bieten ihre Implementierungsmöglichkeiten in den jeweiligen Sprachen an. Ganzzahlen und Brüche werden zuerst ebenfalls als abstrakte Datentypen untersucht, erst anschließend die dazugehörigen Sprachelemente. Die hinausgezögerte Vorstellung der Steuerstrukturen wird allgemein behandelt; die Realisierung in der verwendeten Sprache ist nur exemplarisch. Selbst im noch unausgereiften Bereich der Vererbung und Polymorphie können zuerst allgemeingültige Begriffe wie Modulvererbung und Typvererbung unterrichtet werden, bevor man zu den Implementierungsmechanismen wie Virtualität oder markierte Datentypen kommt.

Unterrichtskonzepte

In diesem Abschnitt werden die Konzepte und Unterrichtsziele der einzelnen Abschnitte des Kurses vorgestellt, um zu zeigen, auf welche Weise das Gesamtziel erreicht werden kann.

1. Informelle Einführung

Die voraussetzungslose Didaktik bietet auch Anfängern eine (stellenweise naive) Begriffbildung über Algorithmen und (konstante oder variable) Daten, über die Entstehung eines Programms (wie Editieren, Übersetzen, Binden, Ausführen), über die Idee der Syntax und Semantik, über Fehler und Testverfahren, usw.

2. Abstrakte Datenobjekte und -typen

Zu Beginn wird der einfachste Algorithmus, nämlich der leere, und seine Implementierung als Hauptprogramm in der aktuellen Sprache vorgestellt. Hierdurch können Grundelemente einer Programmiersprache wie Übersetzungseinheit, Zeichen, Prozedur, Block, Bezeichner o.a. eingeführt werden.

Unmittelbar danach wird das Konzept der Programmbausteine eingeführt; insbesondere wird gezeigt, wie ein leeres Modul (ohne Funktionalität) eingebunden wird.

Anschließend realisiert ein Modul mit leerer Schnittstelle, jedoch mit einem Initialisierungsteil das erste "Hallo-Programm" - motivationshalber mit einer bunten Bildschirmanimation.

Ein Datenabstraktionsmodul mit Initialisierungsteil wird auch benutzt, um die Idee eines Datenbehälters in Form eines animierten Eimers einzuführen. Dieser kann mit (parameterlosen) Operationsaufrufen aus der Modulschnittstelle gefüllt und entleert werden. Ein elementarer Algorithmus besteht aus einem Operationsaufruf, während ein sequentieller Algorithmus aus einer Reihe von Aufrufen besteht.

Hieran können die Folgen eines Programmfehlers gezeigt werden: Ein voller Eimer kann nicht gefüllt werden. Das Programm bricht jedoch nicht ab, wenn Ausnahmebehandlung vorliegt. Dies führt zum Konzept des Blocks.

Ein Datenabstraktionsmodul mit zwei animierten Eimern stellt die Idee von Objekten vor; der Schritt zu abstrakten Datentypen ist nicht mehr groß. Seine Operationen erfordern die Angabe von aktuellen Parametern; hierzu müssen konkrete (benannte) Datenobjekte ("Variablen") angelegt werden. Diese müssen in die Schnittstelle passen. Die Einführung von Klassen und Klassenobjekten ist nur noch eine syntaktische Frage.

Wiederholt auszuführende Sequenzen leiten das Konzept von lokalen Prozeduren ein - sowohl als Makros ("inline-Prozeduren") wie auch mit Aufrufen. Sie können auch (zunächsteinmal nur mit Objekten) parametrisiert werden. Die Übergaberichtungen (wie in bzw. out) und Übergabemechanismen (Werteparameter bzw. Referenzparameter) werden an dieser Stelle erläutert.

Damit stehen alle Sprachelemente bereit, die formale Schnittstelle der benutzten Module und Klassen zu untersuchen. Hier wird auch die Spezifikation eines Moduls und einer Klasse vorgestellt, die neben der Schnittstelle auch private Abschnitte (Teil der Implementierung) - je nach Sprache verborgen oder nicht - enthält.

Der nächste Schritt ist, den Rumpf (den anderen Teil der Implementierung) eines einfach programmierbaren Moduls vorzustellen: Ein Datenabstraktionsmodul wird mit Hilfe eines abstrakten Datentyps (gleicher Funktionalität) implementiert. Das Gedächtnis des Moduls ist dabei ein Objekt vom ADT. Ähnlich einfach wird mit Hilfe des ADT eine Klasse implementiert.

Zur Erweiterung der Schnittstelle eines Moduls durch neue Operationen führen zwei Wege: Das Urmodul kann importiert und seine Leistung durchgereicht werden, wobei sie in den neuen Operationen ergänzt wird. Eine Alternative hierzu ist die Modulvererbung.

3. Werte

In diesem Abschnitt werden die Operationsaufrufe mit (zunächsteinmal abstrakten) Werten parametrisiert: Ein Eimer kann mit Wasser oder mit Wein gefüllt werden. Der Inhalt eines Datenbehälters kann mit Hilfe von Informatoren abgefragt werden; im Gegensatz zu den Mutatoren verändern diese den Zustand des Objekts nicht. Ähnlich wie die Prozeduren als lokales Gegenstück der Mutatoren, werden jetzt die lokalen Funktionen eingeführt; sie müssen in ihrem Rumpf eine Ergebnisanweisung ausführen, deren Syntax von Sprache zu Sprache unterschiedlich ist. Der Prototyp eines Informators in der Schnittstelle eines Moduls sieht dem einer Funktion ähnlich aus. Einfache Informatoren können nun auch programmiert werden.

Informatoren sind geeignet, um Werte von einem Behälter in einen anderen zu kopieren. Im Gegensatz zur traditionellen Zuweisung zeigt diese Vorgehensweise den Zustandswechsel eines Eimers am Bildschirm an.

Jetzt können komplexere Prozeduren (mit mehreren Parametern) programmiert und auch geschachtelt aufgerufen werden. Dies führt zum Blockkonzept, zur Sichtbarkeit, zum Verständnis von lokalen und globalen Objekten, sowie ihrer Speicherung auf dem Systemstapel. Dies ist die Voraussetzung für die Programmierung rekursiver Algorithmen, deren primitivste Form mit einer Ausnahme unterbrochen wird. An diesem Beispiel kann auch die Ausbreitung einer Ausnahme untersucht werden.

4. Ereignissteuerung

Der Trend bei modernen Programmentwicklungswerkzeugen, Programmrahmen ("frameworks") mit Rückrufprozeduren ("call back procedures") zu versorgen, ermöglicht oft, auf (Ereignis-)Schleifen und Verzweigungen (case) zu verzichten. Ein einfaches Menü, in das der Student seine (ebenfalls einfachen) Prozeduren einhängt, verhilft ihn dazu, diese interaktiv anzusteuern und ausführen zu lassen. Die Idee einer Modulstruktur wird auch vorgestellt:

Zunächsteinmal wird eine funktionslose Menüprozedur vorgestellt, deren Menüpunkte nach ihrem Aufruf nichts bewirken, bloß die Auswahl von "Ende" verläßt den Aufruf. Die 4 Menüpunkte einer weiteren Menüprozedur sind fest belegt: Sie füllen und entleeren zwei Eimer; die Aktionen werden am Bildschirm animiert. Eine dritte Menüprozedur hat einen fünften, unbelegten Menüpunkt; hier kann der Student seine eigene (parameterlose) Prozedur einhängen, indem er ihren Namen als Prozedurparameter beim Aufruf der Menüprozedur angibt. Die Auswirkung unterschiedlicher Rückrufprozeduren bei aufeinanderfolgenden Aufrufen wird am Bildschirm beobachtet.

Der Unterschied zwischen dynamischem und statischem Rückruf wird (sofern die Unterrichssprache wie Ada dies zuläßt) untersucht; dies für zur Idee der generischen Instanziierung.

Die nächste Menüprozedur hat zwar die Menütexte fest, ihre 4 Menüpunkte sind aber offen: Bei ihrem Aufruf müssen 4 (i.a. unterschiedliche) Rückrufprozeduren übergeben werden. Die Ausnahmen, die infolge von Fehlbedienung auftreten (wenn z.B. beim vollen Eimer der Menüpunkt "Füllen" ausgewählt wird), werden in den Rückrufprozeduren aufgefangen.

Eine weitere Steigerung wird durch einen einfachen Menügenerator erlebt: Er erzeugt den Programmtext einer Menüprozedur mit angegebenen Menütexten und Struktur. Der Student kann sie übersetzen, einbinden, aufrufen und mit Parametern (seinen eigenen Rückrufprozeduren) versehen.

Soll ein Objekt (wie ein Eimer) mit Werten (wie Wasser oder Wein) versorgt werden, kann dieser über eine Auswahlliste zur Laufzeit bestimmt werden; eine entsprechende, zur Verfügung gestellte Prozedur muß hierfür in der Rückrufprozedur aufgerufen werden.

Wenn eine Rückrufprozedur unterschiedliche Datenobjekte bearbeitet, kann das gewünschte Objekt durch eine vorangehende Menüauswahl vorgewählt werden: Dies ist ein Anlaß für die Einführung von Verweisobjekten (Zeigern). Auch der Unterschied wird gezeigt, wie (statische bzw. dynamische) Stapel- und Haldenobjekte referiert werden.

5. Standard-Aufzählungstypen

Datenbehälter (wie die animierten Eimer) können nicht nur mit Flüssigkeiten (wie Wasser oder Wein) gefüllt werden, sondern auch mit Buchstaben, Meßwerten oder Verweisen auf andere Behälter. Der Standard-Datentyp Character wird mit Hilfe eines animierten Eimers eingeführt, der mit Zeichen gefüllt werden kann. Die Ähnlichkeiten und Unterschiede zwischen Klassenobjekten (mit Zugriffsmethoden) und skalaren Objekten (einfache, standardmäßige Operationen wie Zuweisung) werden untersucht. Ähnlich zu bedienen sind Objekte des Standard-Datentyps Boolean.

Die Werte von Boolean sind syntaktisch den Werten Wasser und Wein ähnlich; die Werte, die in Character-Objekte geladen werden können, werden jedoch durch (nur syntaktisch unterschiedliche) Literale dargestellt. Mit ihrer Hilfe können Vorbesetzungswerte für Objekte, auch für konstante Objekte, angegeben werden.

Für Standard-Aufzählungstypen stehen verschiedene, von der Sprache abhängige Operationen zur Verfügung. Hierzu gehören die Vorgänger- und Nachfolgerfunktionen, Gleichheit und Ordnungsoperatoren. Der Operator ist dabei nur ein syntaktischer Sonderfall einer Funktion. Am Beispiel der logischen Operatoren (and, or, not) wird die Unterscheidung zwischen diadischen und monadischen Operatoren vorgestellt. Aus Operatoren werden Ausdrücke gebaut; hier wird ihr Vorrang verwendet.

6. Definition von Aufzählungstypen

Andere Aufzählungstypen als die Standard-Aufzählungstypen können aus Modulen importiert werden; mit ebenfalls importierten Operationen können Aufzählungsobjekte manipuliert werden. Noch interessanter ist es aber, selber Aufzählungstypen samt Werten zu definieren und die von der Sprache mitgelieferten Operationen (Vorgänger- und Nachfolgerfunktionen, Gleichheit und Ordnungsoperatoren, usw.) kennenzulernen.

Im Besitz der Aufzählungstypen ist es schon möglich, die Logik von Modul- und Klassenrümpfen (wie die für Eimer) ohne Inanspruchnahme eines anderen Moduls (etwa eines ADT's) selber zu programmieren.

7. Abstrakte Multibehälter

Während die bis jetzt kennengelernten Objekte (so auch die skalaren Objekte) als Unibehälter beim Beschreiben ihren Inhalt verlieren (sie haben ein kurzes Gedächtnis), speichern Multibehälter mehr Information. Hierzu gehören Mengen (mit ihren Operationen), Säcke (Multimengen), Folgen (wie FIFO und LIFO), Sortierkanäle, Assoziativspeicher, sequentielle und direkte Dateien. Sie werden als abstrakte Datentypen oder Klassen vorgestellt. Zunächsteinmal wird nur ihre Schnittstelle und ihre Anwendung vorgestellt, wobei die bis jetzt verwendete Technik der Menüsteuerung und Ausnahmebehandlung nach wie vor kurze Programmeinheiten ermöglicht.

Die Unabhängigkeit vom Basistyp des Multibehälters wird durch Schablonen (generische Module), die Erweiterung der Schnittstelle durch Modulvererbung ermöglicht. Die Persistenz eines Multibehälters wird entweder durch geeignete Operationen (wie Speichern und Laden) explizit angeboten, oder aber ist implizit (wie bei Dateien). Der Iterator ist eine andere Operation, die eine (statisch oder dynamisch übergebene) Rückrufoperation für jedes Element des Multibehälters ausführt.

Der abstrakte Datentyp positionierbare Liste ist geeignet, mit seiner Hilfe andere Multibehälter zu implementieren. Ihr Spezialfall ist die sequentielle Datei. Der abstrakte Datentyp Assoziativspeicher führt zum Konzept der direkten Datei.

8. Konglomerate

Die abstrakten Multibehälter können nur mit Hilfe der von der Sprache bereitgestellten Reihungen (Felder) und Verbunde (Komposition) implementiert werden. Hier werden zuerst auch durch Module oder Klassen implementierte abstrakte Reihungstypen samt Operationen vorgestellt; ihre Operationen entsprechen denen der konkreten Multibehälter, der Konglomerate. Mehrdimensionale Reihungen (wie Matrizen) sind hierbei nur ein Beispiel.

An der einfachen Implementierung eines abstrakten LIFO-Objekts als Datenabstraktionsmodul wird die Anwendung von Reihungen dargestellt. Die Indizierung der verwendeten Reihung erfolgt dabei etwas primitiverweise durch Aufzählungstypen. Bequemlichkeitshalber werden auch Ganzzahltypen eingeführt, wobei nur ihre mit den Aufzählungstypen gleichen Eigenschaften (als diskrete Typen) ausgenutzt werden.

Eine Menge mit einem diskreten Basistyp wird als Reihung aus Boolean implementiert; ein Sack als eine ähnliche Reihung aus Integer. Eine Reihung als Character mit zusätzlichen Operationen (wie Konkatenation) ist ein String. Konkrete (d.h. von der Sprache bereitgestellte) sequentielle und direkte Dateien sind Spezialfälle von Folgen und Assoziativspeichern. Mit ihrer Hilfe können die Pesistenzoperationen implementiert werden. Die Typvererbung ist eine weitere Anwendung.

9. Dynamische Datentypen

Die Eigenschaften der früher schon ansatzweise eingeführten Verweisobjekte und -typen werden hier ausführlich untersucht, insbesondere ihre Fähigkeit, rekursive Datentypen (Kettenelemente) zu definieren. Dadurch stehen weitere Implementierungstechniken für Multibehälter wie rückwärts, vorwärts und doppelt verkettete Listen sowie Bäume zur Verfügung. Solche eine rekursive Datenstrukturen sind für die Steuerung von rekursiven Implementierungen der Operationen (z.B. des Iterators) geeignet, die typischerweise als Schleifen programmiert werden.

10. Ganzzahlen

Die über die bis jetzt verwendeten Eigenschaften von Ganzzahltypen (als diskrete Datentypen) hinausgehende arithmetische Eigenart wird zuerst an abstrakten Ganzzahlbehältern untersucht. Ganzzahlliterale werden (ähnlich wie Zeichenliterale) nur als Namen für Werte mit einer besonderen Syntax betrachtet. Wichtig für die Typsicherheit eines Programms ist die Verwendung abgeleiteter (zweckdefinierter) Ganzzahltypen.

11. Brüche

Die interne Darstellung von abstrakten Bruchtypen ist nur für die Effizienz der Operationen von Bedeutung. Sie können als das Verhältnis zweier Ganzzahlen (rationale Zahlen), als Gleitkomma-Dezimalbrüche, Festkomma-Dezimalbrüche oder als Binärbrüche gespeichert werden; i.a. sind diese aber, genauso wie die Ergebnisse arithmetischer Operationen, nur Annäherungen. Effizienzüberlegungen führen zur hardwarenahen Darstellung, wobei das Problem der Genauigkeit und Portabilität betrachtet werden soll. Die mathematische Theorie der Modellarithmetik ist eine Lösungsmöglichkeit.

Als Erweiterung arithmetischer Datentypen werden auch die Vektor- und Matrixalgebra (zunächsteinmal ohne Implementierungsalgorithmen) eingeführt.

12. Steuerstrukturen

Die allgemeine Fallunterscheidung ist die Mehrweg-Alternative (case); Spezialfälle hiervon sind die Einweg-Alternative (if-then) und Zweiweg-Alternative (if-then-else). Mit ihrer Hilfe können Ausnahmesituationen innerhalb eines Moduls erkannt und außerhalb eines Moduls vorgebeugt werden.

Der einfachste Fall der Wiederholungen ist eine Festschleife, deren Durchlaufzahl zur Übersetzungszeit feststeht; sie ist nur die Abkürzung einer (i.a. längeren Sequenz). Die Durchlaufzahl der Zählschleife steht beim Eingang in die Schleife fest. Mit ihrer Hilfe können Algorithmen der Vektor- und Matrixalgebra implementiert werden.

Endlosschleifen werden nur beim Auftreten einer Ausnahme (wie das Ausschalten des Rechners) unterbrochen. Rumpfgesteuerte Schleifen enthalten eine programmierte Abbruchbedingung; der davor stehende Eins-Block wird mindestens einmal, der danach stehende Null-Block möglicherweise keinmal ausgeführt. Der Spezialfall mit einem leeren Eins-Block ergibt die kopfgesteuerte (pre-check-) Schleife, mit einem leeren Null-Block die fußgesteuerte (post-check-) Schleife.

Die drei letzten Arten von Schleifen sind gleichwertig (jeweils durch die anderen simulierbar), die Zählschleife ist schwächer. Verschiedene Programmiersprachen bieten für ihre Implementierung dieser Schleifenarten verschiedene Sprachelemente (manchmal gar keine) an, wobei auf den Unterschied zwischen Fortsetzungsbedingung und Abbruchbedingung geachtet werden muß.

Struktogramme sind Darstellungsmittel für strukturierte Algorithmen, wobei für geübte Programmierer eine konsequente Einrückung des Programmtextes von kurzen Einheiten fast gleichwertig, dafür weniger aufwendig ist. Sie zeigen jedoch den Unterschied zwischen Einsetzung und Aufruf eines weiteren Struktogramms, der zum Konzept der Rekursion führt. An den Standardbeispielen der Fakultät, Fibonacci-Zahlen und Türmen von Hanoi werden die Konzepte der Zeit- und Speicherkomplexität eines Algorithmus demonstriert.

Unstrukturierte Programme können strukturiert werden, indem die Sprünge eliminiert und durch Steuerstrukturen ersetzt werden; dies ist jedoch nur in einfacheren Fällen trivial. Die Steuerstrukturen werden jedoch mit Hilfe von bedingten Sprüngen übersetzt; im Endeffekt entstehen strukturierte Programme ohne Steuerstrukturen.

Die traditionellen Verzweigungen (if und case) sind also nur eine Alternative zur Ausnahmebehandlung, zur Ereignissteuerung und zur Polymorphie. Ebenso sind die traditionellen Wiederholungen (while und for) nur eine Alternative zum Iterator, zur Rekursion und zur Ereignisschleife. Über die Wahl soll in erster Linie die Lesbarkeit (und somit Wartbarkeit) eines Programms und erst danach seine Effizienz, am wenigsten die Tradition entscheidend sein.

13. Vererbung und Polymorphie

Im letzten Abschnitt des Kurses wird der Weg zur Vererbung geebnet: Ein Modul bzw. eine Klasse (wie der Stapel) wird zuerst durch eine neue Komponente (Summe aller gespeicherten Zahlen) textuell erweitert, dann durch eine neue Operation (die dieselbe errechnet). Die Idee der Wiederverwendbarkeit wird dargestellt, indem statt textueller Erweiterung das vorhandene Modul importiert und verwendet wird ("Einkaufen", "Komposition"). Durch den Mechanismus der Modulvererbung (Erweiterung der Schnittstelle durch eine neue Operation) wird hierbei Schreibarbeit gespart. Die Typvererbung erweitert das Modul durch eine neue Komponente, die allerdings durch Überladen der Operationen gepflegt werden muß.

Am Beispiel von grafischen Objekten wird der Unterschied zwischen früher und später Bindung gezeigt; verschiedene Sprachen bieten verschiedene Implementierungsmechanismen hierfür wie Virtualität oder markierte Datentypen an. Mit ihrer Hilfe wird zum Schluß eine polymorphe Datenstruktur entwickelt, in die Objekte unterschiedlicher Datentypen (Ganzzahl, Zeichenkette, Verbund), allerdings alle Nachfolger eines aufgeschobenen Datentyps (einer abstrakten Klasse), gespeichert werden können.

14. Nebenläufigkeit

Dieser Kapitel wurde nur für Sprachen (wie Ada) hinzugefügt, die Parallelität als Sprachelement unterstützen. Nach der konzeptionellen Einführung von kritischen Abschnitten und klassischen Synchronisationswerkzeugen (wie Semaphore und Monitore) werden die sprachbasierten Elemente (wie Rendez-vouz in Ada) vorgestellt. Den krönenden Abschluß stellen die Prozeßtypen (mit dynamisch erzeugbaren Prozeßobjekten) und die geschützten Objekte (mit Zugriffsoperationen unter gegenseitigen Ausschluß) dar.

Erfahrungen

Das vorgestellte Unterrichtskonzept wurde im Studiengang Allgemeine Informatik an der Technischen Fachhochschule Berlin einige Male versuchsweise eingesetzt. Die Version für die Sprache Ada wird für Studienanfänger, die Version für C++ im dritten Semester (als zweite Sprache) verwendet. Die Erfahrungen hiermit können folgendermaßen kurz zusammengefaßt werden:

Zusammenfassend kann man behaupten, daß die Erfahrungen ermutigend sind, wenn auch das vorhandene unterstützende Programmaterial noch nicht ausgereift ist. Einzelne Abschnitte (wie z.B. der zum Thema Parametereinsetzung) bedürfen eine didaktisch feinere Ausarbeitung.

Literatur

G. Pfeiffer und J. Raasch: Ein Jahr Erfahrung im Unterricht von C++ für Anfangssemester (SEUH '94, Teubner Verlag)

A. Solymosi: Objektorientiertes Plug and Play - Ein Programmierlehrbuch für Wiederverwendbarkeit und Software-Qualität in C++ (Vieweg Verlag, 1997)

A. Solymosi: Objekte von Anfang an  - Ein Programmierkurs für Anfänger und Fortgeschrittene in Ada95 (TFH Berlin, 1995; Neuauflage 1997)

A. Solymosi: Objektorientiertes Programmieren von oben - Ein Programmierkurs für Anfänger und Fortgeschrittene in Object Pascal (TFH Berlin, 1996)

A. Solymosi: Objektorientiertes Plug and Play - Programmierlehrbuch in Java (in Vorbereitung)


© Prof. Dr. Andreas Solymosi