© Martin Korneffel, Stuttgart 2005 +++ email: trac@n.zgs.de +++ web: www.s-line.de/homepages/trac

Objektorientierte Programmierung

»Die Idee der objektorientierten Programmierung wurde nicht von einer einzigen Firma oder gar Person entwickelt, sondern ist Zeichen des Fortschritts innerhalb der Softwareentwicklung, die immer auch an die Möglichkeiten der Hardware gebunden war.« (Q: Kai Baufeld, Rolf Mäuers: Java / Data BeckerGmbH & Ko KG, 1 Aufl. 1996)

Inhalt

  1. Die prozedurale Sichtweise

    1. Beispiel: Bibliothek für Transaktionen auf Bankkonten

    2. Nachteil der prozeduralen Sichtweise

  2. Die Dynamik großer Softwareprojekte

  3. Wie Daten und Prozeduren miteinander verbunden sind

  4. Die objektorientierte Sichtweise

    1. Definition: Objekt

    2. Klassen

    3. Definition: Klasse

    4. Konstruktoren

    5. Destruktoren

  5. Vorteile der objektorientierten Sichtweise

  6. Objektdiagramm

  7. Objektorientierte Analyse und Programmentwurf am Beispiel des Simulationsspiels Seeschlacht


Die prozedurale Sichtweise

Die Frühzeit der Computerevolution fand großteils im wissenschaftlich- technischen Bereich statt. Eingesetzt wurden hier die Computer häufig zur nummerischen Lösung von Gleichungssystemen (Differentialgleichungen etc.). Der Schwerpunkt bei der Programmierung liegt dabei im Definieren und Implementieren der Verarbeitungsprozedur, die aus einer Reihe von Startwerten schrittweise die Lösung berechnet.

Das Datenmaterial beschränkt sich auf Zahlenkolonnen. Zu ihrer programmtechnischen Darstellung reichen die elementaren Datentypen Fest- und Gleitkommazahl sowie das Datenfeld (Array) als einziger Strukturtyp für aus Zahlenwerte aufgebaute Listen.

Der Schwerpunkt »Verabreitungsprozedur« prägte eine Generation von Programmierern und führte zur Sichtweise der prozeduralen Programmierung. In dieser Zeit entstanden Programmiersprachen wie FORTRAN, BASIC und C , die von dieser Sichtweise geprägt wurden: sie unterstützen direkt die prozedurale Programmierung mit sprachlichen Ausdrucksmitteln (z.B.: Anweisungen zur Definition von Prozeduren und zur Steuerung des Programmablaufes).



Beispiel: Bibliothek für Transaktionen auf Bankkonten, prozedurale Sichtweise

Aus prozeduraler Sichtweise entspricht jede Transaktion auf einem Bankkonto einer Verarbeitungsprozedur. Es können folgende Prozeduren definiert werden (Prozedurnamen sind ein F, und Daten ein D vorangestellt):

F:konto_eröffnen
  Eingabe: kontonummer, name_inhaber, ..., Liste_Kontodatensätze;

F:einzahlen_auf_konto
  Eingabe: Kontonummer, Betrag_in_Euro, Liste_Kontodatensätze;
  Ausgabe: modifizierte_Liste_Kontodatensätze;

F:abheben_von_konto
  Eingabe: Kontonummer, Betrag_in_Euro, Liste_Kontodatensätze;
  Ausgabe: modifizierte_Liste_Kontodatensätze;

F:kontostand
  Eingabe: Kontonummer, Liste_Kontodatensätze
  Ausgabe: Guthaben

F:konto_schliessen
   Eingabe: Kontonummer, Liste_Kontodatensätze;
   Ausgabe: modifizierte_Liste_Kontodatensätze;

Die Liste_Kontodatensätze in den Prozeduren kann eine tabelarische Liste mit folgendem Aufbau sein:

Kontonummer

Name_Inhaber

Adresse_Inhaber

Guthaben

Eröffungsdatum

Datum der letzten Ein/Auszahlung.



Nachteile der prozeduralen Sichtweise

In der prozeduralen Sichtweise erscheinen Daten als Ein- und Ausgaben der Verarbeitungsprozeduren. Für jede Prozedur muß der Programmierer die notwendigen Ein- und Ausgabedatenlisten zu definieren. Diese Definitionen voraussetzend, implementiert er die Prozeduren. Daraus entsteht eine starke Bindung an die in den Datenlisten verwendeten Datenformate.

Ein weitsichtiger Programmierer wird immer versuchen, Datenformate bei der Definition der Prozeduren zu verwenden, die auch in Zukunft viel Spielraum für die Daten lassen. So kann die Kontonummer im obigen Beispiel 6- stellig ausgelegt werden, obwohl zum Zeitpunkt der Programmentwicklung nur 5- stellige Kotonummern bei der Bank im Gebrauch waren. Es zeigte sich aber, daß die zu verarbeitenden Daten sich mit der Zeit in einer Art und Weise ändern können, die auch der weitsichtigste Programmierer nicht voraussehen konnte.

Das obige Beispiel ist nur für Konten ausgelegt, die ohne Verzinsung im Haben geführt werden. Nun möchte die Bank mit dem Programm auch die Sparkonten ihrer Kunden verwalten. Das Problem stellt sich in der Liste mit den Kontodatensätzen. Sie muß um die drei Felder D:Kontotyp, D:Zinssatz und D:Datum_letzte_Verzinsung erweitert werden. Dabei wird über das Feld D:Kontotyp das gewöhnliche Konto vom Sparkonto unterschieden.

Die Erweiterung der Tabelle erfordert eine Neuprogrammierung aller Verarbeitungsprozeduren ! Denn die ursprünglichen Prozeduren haben von den neuen Datenfeldern keine Ahnung, und können sie folglich nicht auswerten.

Mag der Aufwand der Neuprogrammierung im Beispiel sich noch in Grenzen halten, bei richtig großen Programmbibliotheken kann der Aufwand exorbitant werden ! Die Ursache für das Problem liegt in dem aus prozeduraler Sichtweise erfolgten Bibliotheksentwurf, der unflexibel gegenüber Änderungen an den Ein- und Ausgabedaten der Prozeduren ist.



Die Dynamik großer Softwareprojekte

Die Leistungsfähigkeit der Computer wuchs und wuchs. Angestachelt von diesem Wachstum wagte sich die Programmier an immer größere Projekte, und scheiterten dabei nicht selten.

Aus den Siegen und Niederlagen gewannen die Entwickler wichtige Erkenntnisse über den Entwicklungsprozess großer Softwareprojekte.

Eine ist, daß der Einsatz großer Softwarepakete sich über sehr lange Zeiträume erstrecken kann. Dabei müssen sie stets an die aktuellen Erfordernisse angepasst werden.

Z. B. muss das tadellos funktionierende Kontoführungsprogramm einer Bank, entwickelt mitte der 80- er Jahre, im Jahr 2001 auf die neue Währung Euro umgestellt werden.

Eine andere Erkenntniss ist, das Fehler bei der Programmierung nie zu 100% vermeidbar sind. Nicht selten treten sie erst beim Einsatz des Softwarepaketes auf. Wichtig ist dann, daß sie schnell lokalisiert und behoben werden können. Mit der Zeit erreicht man so eine nahezu fehlerfreie Software.

Ein reales Kontoführungsprogramm ist sicher kein kleines Softwarepaket. Wenn die Währungsumstellung eine Neuprogrammierung erfordert, ist dies nicht nur mit großen finaziellen Aufwendungen für die Programmierung verbunden: der Zustand einer nahezu fehlerfreien Software als Ergebnis eines langen Prozesses der Fehlerbeseitigung ist nicht mehr gegeben - die Neuprogram­mierung bringt einen Rohling mit völlig neuem Fehlerpotenzial hervor !

Mit der prozeduralen Sichtweise können keine großen Softwarepakete entworfen und implementiert werden, die die Anforderungen der Kunden zufriedenstellen. Eine neue Sichtweise mußte entwickelt werden, die die neu gewonnenen Erkentnisse berücksichtigte.



Wie Daten und Prozeduren miteinander verbunden sind

Daten sind Informationspakete, die in einem thematisch abgegrenzten Bereich, dem Kontext, aus einer endlichen Menge von Dingen eine Auswahl treffen. Z.B. bestimmt im Kontext »Bankkonto« das Datenfeld D:Guthaben aus der Menge aller möglichen Vermögen, die ein Bankkunde haben könnte, das Vermögen, welches der Bankkunde momentan auf seinem Bankkonto hat.

Das Guthaben eines Bankkunden ändert sich mit der Zeit. Diese Veränderungen werden durch Prozesse bewirkt. Prozeduren bilden im Computer diese Prozesse nach. Man kann in diesem Zusammenhang Prozeduren auch ähnlich wie Daten betrachten: eine Prozedur ist ein Informationspaket, die in einem thematisch abgegrenzten Bereich aus einer endlichen Menge von Prozessen einen auswählt. Die Prozedur F: abheben (Eingabe: Kontonummer=1234, Betrag=100, Liste_Kontodatensätze= konten.dat) bestimmt aus der Menge aller möglichen Abhebungen, die ein Bankkunde versuchen könnte, jenen, der tatsächlich von im durchgeführt wird.

Aus dieser Betrachtung wird deutlich, daß Daten und Prozeduren strukturell ein und dasselbe sind: Informationspakete im Kontext eines Themengebietes. Damit können Prozeduren ähnlich wie Daten verwaltet werden und umgekehrt. In der Technik wird dies schon seit langem ausgenutzt: die klassische von Neumann - Rechnerarchitektur definiert einen gemeinsamen Arbeitsspeicher für Prozeduren und Daten.

Daten und Prozeduren können nicht losgelöst vom Kontext betrachtet werden. Die Prozedur F:abheben(...) bezeichnet im Computerspiel »Mondlandung« etwas völlig anderes als die Prozedur F:abheben(...) im Kontoführungsprogramm. Dieser Aspekt wird in der prozeduralen Sichtweise nicht berücksichtigt, und ist auch die Ursache für ihr Versagen in großen Softwareprojekten.



Die objektorientierte Sichtweise

In der objektorientierten Sichtweise wird der Kontext durch Einführung des Objektbegriffs berücksichtigt. Daten und Prozeduren, die einen gemeinsammen Kontext besitzen, werden in einem Objekt zusammengefasst.

Definition

Objekt

Ein Objekt fasst Prozeduren und Daten zusammen, die Dinge und Prozesse beschreiben, welche in einem unmittelbaren Zusammenhang stehen.

In der objektorientierten Sichtweise werden die Prozeduren eines Objektes als Methoden, und die Daten als Elemente bezeichnet.



Nach der objektorientierten Sichtweise konzentriert sich der Programmierer beim Entwurf zunächst auf die Bestimmung der verschiedenen Kontexte, die in der Aufgabenstellung auftreten. Für jeden Kontext wird ein Objekt definiert. Anschließend werden die Dinge und Prozesse in den Kontexten den Daten und Prozeduren in den korrespondierenden Objekten zugeordnet. (Objekte werden im Folgenden durch das vorangestellte Symbol O: gekennzeichnet)

Im Beispiel der Kontoführung durch ein Bankinstitut findet man die Kontexte Bank, Giro- und Sparkonten. Für diese können Objekte definiert werden wie O:bankhaus_dagobert, O:girokonto_von_donald, O:girokonto_von_daisy und O: sparkonto_von_dussel. Die Prozsse einzahlen, abheben und die Dinge wie Guthaben und ontonummer aus dem Kontext »Girokonto« werden Daten und Prozeduren in den Giro- und Spakontoobjekten zugeordnet.

O:girokonto_von_donald     O:girokonto_von_daisy
  D:kontonummer              D:kontonummer
  D:guthaben                 D:guthaben
  F:einzahlen                D:einzahlen
  F:abheben                  D:abheben

Zum Sparkonto- Kontext gehören noch weitere Dinge wie Zinssatz, Datum der letzten Verzinsung und der Prozess »Verzinsen«.

O:sparkonto_von_dussel
  D:kotonummer
  D:guthaben
  F:einzahlen
  F:abheben
  D:zinssatz
  D:datum_letzte_verzinsung
  F:verzinsen

Prozesse wie »Neues Konto eröffnen« und »Konto schließen« scheienen zunächst zum Kontext Bank zu gehören. Folglich werden sie Prozeduren aus dem Objekt O:bankhaus_dagobert zugeordnet.

Die Spar- und Girokonten werden in der Bank verwaltet. Damit gehören zum Kontext Bank auch die Spar- und Girokonto- Objekte! Das ist ein analoges Prinzip wie bei reinen Datenstrukturen der prozeduralen Programmiersprachen: Datenstrukturen können dort aus elementaren Daten wie Zahlenwerte und Zeichenketten, aber auch aus vorher definierten Datenstrukturen zusammengesetzt werden.

O:bankhaus_dagobert
  F:neues_konto_eröffnen
  F:konto_schließen
  O:girokonto_von_donald
  O:girokonto_von_daisy
  O:sparkonto_von_dussel



Klassen

Die Objekte O:girokonto_donald und O:girokonto_daisy haben den gleichen Aufbau aus Daten und Prozeduren. Alle Objekte, die Girokonten entsprechen, werden den gleichen Aufbau haben! Diese Beobachtung führt zum Konzept der Klasse.

Definition

Klasse

Eine Klasse ist eine Menge von Objekten, die einen gemeinsammen strukturellen Aufbau aus Daten und Prozeduren haben. Die Menge kann durch eine Klassendeklaration beschrieben werden. Eine Klassendeklaration listet Deklarationen von Datenelementen und Prozeduren auf, die die Objekte der Klasse enthalten.



Mittels Klassen kann die Deklaration von Objekten vereinfacht werden. Es reicht bei der Objektdeklaration die Klasse anzugeben, zu der das Objekt gehört.

Im Folgenden werden Klassennamen durch ein vorangestelltes K gekennzeichnet. Bei Objektdeklarationen werden dem Objektnamen der Name der Klasse vorangestellt, zu der das Objekt gehört. Als Beispiel wird die Klasse K:Girokonto deklariert, und anschließend die Girokonten von Donald und Daisy.

K:GiroKonto               
  D:kontonummer              
  D:guthaben                 
  F:einzahlen                
  F:abheben

GiroKonto:girokonto_von_donald
GiroKonto:girokonto_von_daisy



Konstruktoren

Nehmen wir an, im Bankhaus Dagobert möchte ein Herr namens Carlo ein Sparkonto eröffnen. Er handelt mit der Bank einen Zinssatz aus, unterschreibt den Sparvertrag, und die Bank eröffnet das Konto. Auch eine Frau namens Holle eröffnet bei Dagobert ein Konto, diesmal jedoch ein Girokonto.

Im ersten Augenblick scheint es, als ob die Eröffnung eines Kontos vollständig durch die Prozedur F:neues_konto_eröffnen aus dem Objekt O:bankhaus_dagobert realisiert wird. Doch dazu muß diese Funktion genauso gut für Giro- wie für Sparkonten funktionieren. Wenn die Verwaltung der Sparkonten nicht von Beginn an Bestandteil des Kontoführungsprogrammes sind, sondern diese erst später hinzugefügt werden, dann muß die altbewährte Prozedur F:neues_konto_eröffnen jedesmal neu programmiert werden. Dieses Problem sollte jedoch vermieden werden.

Das Problem besteht darin, das im obigen Ansatz der Prozess der Kontoeröffnung im Kontext der Bank erscheint. Die Eröffnungsprozess ist aber vom Typ des Kontos abhängig. Wird ein neues Sparkonto eingerichtet, was im Kontoführungs­programm der Erzeugung eines neuen Sparkonto- Objektes entspricht, dann muß die spezialisierte Eröffnungsprozedur für Sparkonten gestartet werden. Und wenn ein neues Girokonto- Objekt erzeugt wird, dann ist die Eröffnungsprozedur für Girokonten zu starten.

Benötigt werden spezielle Prozeduren, die immer dann starten, wenn ein neues Objekt erzeugt wird. Sie heißen Konstruktoren. Konstruktoren können wie normale Prozeduren eingaben verarbeiten, jedoch können sie keine Ausgaben vornehmen ! Im Beispiel sind die Eröffnungsprozeduren als Konstruktoren für die einzelnen Konto- Objekte auszulegen. Durch diese Vorgehensweise können nachträglich beliebig viele neue Kontotypen dem Programm hinzugefügt werden, ohne daß es notwendig wird, eine bereits altbewährte Prozedur wie F:neues_konto_eröffnen neu zu programmieren.

Im Folgenden werden die Konstruktoren durch eine Prozedurnamen, die gleichlautend den Klassennamen sind, gekennzeichnet.

K:GiroKonto
  F:GiroKonto    
   Konstruktor von GiroKonto- Objekten 
   Eingabe: kontonummer, startguthaben
 
  D:kontonummer              
  D:guthaben                 
  F:einzahlen                
  F:abheben

K:SparKonto
  F:SparKonto
    Konstruktor von SparKonto- Objekten
    Eingabe: kontonummer, startguthaben, zinssatz

  D:kotonummer
  D:guthaben
  F:einzahlen
  F:abheben
  D:zinssatz
  D:datum_letzte_verzinsung
  F:verzinsen



Destruktoren

So wie das Eröffnen eines Girokontos verschieden vom Eröffnen eines Sparkontos ist, so verschieden sind auch die Prozesse beim Schließen der Konten von beiden Typen. In beiden Fällen könnten eventuelle Restgutghaben auf ein Vermögenskonto der Bank gutgeschrieben werden. Jedoch ist beim Sparkonto vorher das Restguthaben zu verzinsen.

Aufgaben dieser Art können durch spezielle Prozeduren gelöst werden, die immer unmitterlbar vor dem Löschen eines Objektes starten. Sie werden Destruktoren genannt.

In der objektorientierten Programmiersprache C++ kann eine Klasse höchstens einen Destruktor besitzen. Allgemeingültig ist diese Einschränkung nicht, kann man sich doch leicht Beispiele finden, in denen es wünschenswert wäre, Objekte auf verschiedene Art und Weise aus der Welt zu schaffen.

Im Folgenden werden Destruktoren durch Namen gekennzeichnet, die gleich dem Klassennamen sind, und denen das Zeichen #vorangestellt ist.

K:GiroKonto
  F:GiroKonto    
   Konstruktor von GiroKonto- Objekten 
   Eingabe: kontonummer, startguthaben
  F:#GiroKonto
   Destruktor von GiroKonto- Objekten
 
  D:kontonummer              
  D:guthaben                 
  F:einzahlen                
  F:abheben

Vorteile der objektorientierten Sichtweise

Durch die objektorientierte Sichtweise entstehen Programme, deren Quelltexte hauptsächlich in Klassen gegliedert sind. Die Klassen können beim Entwurf oft dierekt aus der Struktur des Weltausschnittes abgeleitet werden, der durch das Programm automatisiert werden soll. Damit widerspiegelt der objektorientierte Entwurf in einer Art und Weise plastisch den Weltausschnitt, wie es mit einem rein prozeduralen Entwurf nicht möglich wäre. Dadurch können auch große Softwareentwürfe überschaubar gehalten werden. Weiter unterstützt die Gliederung in Klassen, daß Änderungen an Strukturen im Weltausschnitt häufig nur Änderungen an den Klassen nach sich ziehen, die die betroffenen Strukturen beschrieben. Die komplette Neuprogrammierun die bei prozedural entworfene Software oft notwendig wird, wenn Änderungen an den Eingabedaten anstehen, kann bei objektorientiert Software häufig vermieden werden.

Objektdiagramm


Objektorientierte Analyse und Programmentwurf am Beispiel des Simulationsspiels Seeschlacht

Mehrere Parteien Bekämpfen sich auf dem Ozean. Jede verfügt über eine Flotte von Kriegs und Handelsschiffen. Sieger ist, wer nach einer gegebenen Zeitspanne über die meisten Schiffe verfügt.

Im folgenden werden Spielfiguren und Spielbrett durch Objekte beschrieben:

Objektmodell eines Schiffes


Objektmodell einer Waffe (z.B. Schiffsgeschütz)


Objektmodell eines Schlachtschiffes


Entwickeln einer Klassenhierarchie

Die Objektmengen müssen zu Klassen zusammengefasst werden. Für jede Klasse ist eine Klassendeklaration erforderlich.


Gemeinsammkeiten in den Klassendeklarationen in die Basisklasse Cfigur auslagern


Schlachtschiff als spezialisierung eines Schiffes modelieren