Grundkonzepte Objektorientierter Programmierung

Werbung
Grundkonzepte
Objektorientierter Programmierung
Beispiele in
Java, Oberon-2, Python und Delphi
Projektarbeit im Studiengang Lehramt Informatik
Sommersemester 2006
Friedrich-Schiller-Universität Jena
erstellt von
Nicole Himmerlich
2006
Inhaltsverzeichnis
1 Konzepte und Begrie der Objektorientierung
1
2 Beispiel: Konto
3
2.1
Aggregierung und enge Kopplung . . . . . . . . . . . . . . . . . . . . . . . . .
3
2.2
Datenkapselung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
2.3
Konstruktor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10
2.4
Klassen- und instanzspezische Komponenten . . . . . . . . . . . . . . . . . .
13
2.5
Manipulierbarkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
2.6
Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19
2.7
Reimplementierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25
2.8
Polymorphismus
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
28
2.9
Überladen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30
3 Beispiel: Mediendatenbank
33
4 Beispiel: geometrische Figuren
46
A Python-Quellcode
58
A.1
Beispiel: Konto
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
58
A.2
Beispiel: Mediendatenbank . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
63
A.3
Beispiel: geometrische Figuren . . . . . . . . . . . . . . . . . . . . . . . . . . .
65
B Delphi-Quellcode
67
B.1
Beispiel: Konto
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
67
B.2
Beispiel: Mediendatenbank . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
79
B.3
Beispiel: geometrische Figuren . . . . . . . . . . . . . . . . . . . . . . . . . . .
82
1 Konzepte und Begrie der Objektorientierung
Die Prädicate der Erscheinung können dem Objecte selbst beigelegt werden, in Verhältniÿ auf unseren Sinn, z.B. der Rose die rothe Farbe, oder der Geruch; [...]
1
Immanuel Kant
Wie wir uns räumliche Gegenstände überhaupt nicht auÿerhalb der Zeit denken
können, so können wir uns keinen Gegenstand auÿerhalb der Möglichkeiten seiner
Verbindung mit anderen denken.
2
Ludwig Wittgenstein
Die obigen Zitate zeigen, dass objektorientiertes Denken keine Erndung der Informatik ist,
vielmehr gehen wir alle täglich mit Objekten um. [Somit ist Objektorientierung] für uns
Menschen eine natürliche Betrachtungsweise. Wir wissen, dass Objekte Eigenschaften und
ein Verhalten besitzen und aus anderen Objekten zusammengesetzt sein können. Wir betrachten von einem Objekt nur gerade soviel, wie uns im Moment interessiert und fassen
gleichartige Objekte in Gruppen zusammen.
3
Die Informatik hat diese Betrachtungsweise aufgegrien und zu einem Programmierparadigma entwickelt.
Der Begri Objekt umfasst Gegenstände, Personen, Sachverhalte oder Ereignisse. Jedes einzelne Objekt lässt sich durch drei Aspekte charakterisieren: es besitzt einen eindeutigen Zustand,
ein wohldeniertes Verhalten und eine Identität.
Der Zustand eines Objektes repräsentiert alle seine relevanten Eigenschaften und ist durch eine Menge von Attributen und deren Werte bestimmt. Durch sie wird die Struktur der Objekte
vereinbart. Die Attributmenge eines Objektes ändert sich normalerweise nicht, die konkreten
Werte sind jedoch veränderlich.
Das Verhalten wird in der objektorientierten Programmierung durch eine Menge von Metho-
den beschrieben. Sie bestimmen die Art und Weise, in der ein Objekt in Abhängigkeit von
seinem Zustand, auf Einwirkungen reagiert und können Attributwerte manipulieren.
Die Identität eines Objektes ist diejenige Eigenschaft, die es von allen anderen Objekten
unterscheidet. Sie stellt sicher, dass alle Objekte unterscheidbar sind, auch wenn sie zufällig
identische Attributwerte besitzen. Dabei muss gerade in der Softwareenticklung zwischen dem
Namen eines Objektes und seiner Identität dierenziert werden, da der Name der Adressierung dient und ein einzelnes Objekt über verschiedene Namen angesprochen werden kann.
Objekte können nach bestimmten Kriterien gruppiert werden. Statt jedes Objekt einzeln zu
beschreiben, werden gemeinsame Merkmale in Klassen zusammengefasst. Eine Klasse ist somit eine abstrakte Beschreibung der Struktur und des Verhaltens von gleichartigen Objekten.
Ein konkretes Objekt wird dann auch als Instanz einer Klasse bezeichnet.
1 Quelle ??
2 Quelle ??
3 Lüscher/Straubinger
1996 [LS96, S.4].
1
Zu den Grundprinzipien der objektorientierten Programmierung gehören noch weitere Konzepte:
ˆ
Aggregierung
ˆ
Kopplung
ˆ
Kapselung und Information Hiding
ˆ
Konstruktoren und Destruktoren
ˆ
Instanz- und klassenspezische Komponenten
ˆ
Vererbung
ˆ
Mehrfachvererbung
ˆ
Reimplementierung
ˆ
Polymorphismus
ˆ
Dynamisches Binden
ˆ
Abstrakte Klassen
ˆ
Generische Klassen
ˆ
Assoziation
ˆ
Aggregation
ˆ
Komposition
Einige dieser Konzepte werden im Folgenden anhand einfacher Beispiele näher erläutert. Auf
eine ausführliche Darstellung der anderen Prinzipien wird verzichtet. Sie sind hier nur erwähnt,
um die Vielfalt objektorientierter Programmierung zu verdeutlichen.
2
2 Beispiel: Konto
Zur näheren Erläuterung zentraler Eigenschaften objektorientierter Programmierung soll zunächst ein Bankkonto schrittweise modelliert werden.
Jedes Bankkonto besitzt üblicherweise eine eindeutige Kontonummer und einen aktuellen Kontostand. Natürlich sind noch weitere Kennzeichen - z.B. Kontoinhaber - denkbar. Sie werden
jedoch zunächst vernachlässigt, damit die Quelltexte überschaubar bleiben. Typische Aktionen, die auf einem Konto ausgeführt werden, sind das Ein- bzw. Auszahlen von Geldbeträgen
und das Drucken eines Kontoauszugs.
2.1 Aggregierung und enge Kopplung
In der prozeduralen Programmierung werden zunächst die für die Beschreibung der Daten
notwendigen Datenstrukturen geschaen und danach werden die Funktionen entwickelt, mit
deren Hilfe die Inhalte der vorhandenen Datenstrukturen ausgegeben bzw. manipuliert werden können. Obwohl sich die Funktionen auf die Datenstrukturen beziehen, können Daten und
Funktionen nicht als eine Einheit betrachtet werden. Insbesondere wenn mehrere Datenstrukturen deklariert werden müssen, ist die Zuordnung der Funktionen nicht sofort ersichtlich.
In der objektorientierten Programmierung werden zusammengehörige Attribute (Daten) und
Methoden (Funktionen) zu einer manipulierbaren Gröÿe - einer Klasse - zusammengefasst.
4
Sie bilden sozusagen ein Aggregat , dessen Komponenten aufeinander Bezug nehmen können.
In unserem Beispiel werden alle notwendigen Attribute und Methoden für ein Bankkonto in
der Klasse Konto zusammengefasst, so dass sich das in Abb. 1 gezeigte Modell ergibt.
Die Methoden einzahlen(betrag) und auszahlen(betrag) verändern den Kontostand eines Bankkontos, d.h. sie müssen auf das Attribut saldo zugreifen und dessen Wert verändern. Die Methode auszug() bezieht sich sogar auf die beiden Attribute der Klasse, da sie kontonummer
und saldo ausgibt. Daran zeigt sich die enge Kopplung zwischen Attributen und Methoden.
Abbildung 1: Klasse Konto
4 In
der Literatur wird häug der Begri Kapselung verwendet. Um Verwechselungen mit dem Begri
Datenkapselung - im Sinne des Information Hiding - zu vermeiden, wird hier der Begri Aggregierung (ein
Aggregat bilden) benutzt.
3
Java
In Java wird ein Klasse über das Schlüsselwort
class
deniert. Alle Variablen, Funktionen
bzw. Prozeduren, die innerhalb der geschweiften Klammern deniert werden, gehören damit
zur Klasse und bilden deren Attribute und Methoden. Innerhalb einer Klasse können Attribute
und Methoden direkt über ihren Namen angesprochen werden. Eine Quelldatei sollte nur eine
Klassendenition enthalten und den gleichen Namen wie die Klasse tragen.
//Datei: Konto1/Konto.java
//Beispiel : Aggregierung und Kopplung
// −−> Aggregierung
class Konto{
// Attribute
int kontonummer;
double saldo;
// Methoden
void einzahlen(double betrag){
}
saldo = saldo + betrag; // −−> Kopplung
void auszahlen(double betrag){
}
saldo = saldo − betrag; // −−>Kopplung
void auszug(){
}
}
System.out.println("Kontonummer: "+kontonummer+" Saldo: "+saldo+" Euro");
Java ist eine rein objektorientierte Sprache, so dass jedes Programm eine Klasse deniert.
Deshalb wird das Testprogramm für die Klasse Konto in eine Klasse KontoTest eingebettet,
die nur die Methode main enthält.
Um von der Klasse Konto ein Objekt bzw. eine Instanz zu erzeugen, wird eine Variable vom
Typ Konto deklariert. Der Operator
new legt, mit Hilfe der vom Compiler erzeugten Methode
Konto(), ein neues Konto -Objekt an, das anschlieÿend der Variablen meinKonto zugewiesen
wird. Dabei gilt: Variablen, deren Typ eine Klasse ist, enthalten nicht das eigentliche Objekt,
sondern nur einen Adressverweis auf dessen Speicherplatz. Insbesondere bedeutet dies, dass
in meinKonto nur die Speicheradresse des Objektes abgelegt ist (siehe Abb. 2).
Für den Zugri auf Attribute oder Methoden eines Objektes wird eine Punktnotation verwendet. Attribute können mittels Referenz.Attribut gelesen und deren Werte verändert werden.
Methoden werden durch Referenz.Methode() aufgerufen.
Abbildung 2: Objektreferenz
4
// Datei: Konto1/KontoTest.java
// Beispiel : Aggregierung und Kopplung
public class KontoTest{
public static void main(String[] args){
Konto meinKonto; // Variable vom Typ Konto
meinKonto = new Konto(); // new erzeugt Instanz der Klasse Konto
meinKonto.kontonummer = 4531088; // Zugri auf Attribute
meinKonto.saldo = 200.00;
System.out.println("Kontonummer: "+meinKonto.kontonummer +" Saldo: "+ meinKonto.saldo);
meinKonto.einzahlen(300.00); // Methodenaufruf
meinKonto.auszug();
System.out.println ();
}
}
Konto deinKonto = new Konto(); // deinKonto zeigt auf neue Instanz der Klasse Konto
deinKonto.kontonummer = 1733065;
deinKonto.saldo = 1000.00;
deinKonto.auszug();
deinKonto.auszahlen(100.00);
deinKonto.auszug();
Oberon-2
Oberon-2 verwendet sogenannte typgebundene Prozeduren, um die Zugehörigkeit der Methoden zur Klasse deutlich zu machen.
Die Attribute einer Klasse werden als Felder eines Recordtyps dargestellt. Da Oberon nicht
automatisch mit Referenzvariablen arbeitet, wird auÿerdem ein Referenztyp zum Recordtyp
der Klasse deniert.
Die Implementierung der Methoden muss zusätzlich einen speziellen Parameter vor dem Prozedurnamen besitzen. Dieser Parameter wird Empfänger genannt und repräsentiert das Objekt, für das die Methode aufgerufen wird. Der Typ des Empfängers gibt an, zu welcher Klasse
die Methode gehört. Im Prozedurrumpf wird der Empfänger genutzt, um auf die Attribute der
Klasse zuzugreifen. Dabei wird wieder die Punktnotation (Empfänger.Attribut ) angewendet.
In Oberon-2 müssen zunächst alle Datentypen deklariert werden, bevor Prozeduren implementiert werden können. Deshalb sollte jede Klasse in einem separaten Modul beschrieben
werden. Sind mehrere Klassendenitionen in einem Modul enthalten, geht schnell die Übersichtlichkeit verloren und es ist nicht mehr klar erkennbar, welche Methoden zu welchem
Datentyp gehören.
(* Datei: Konto1/Konto.mod *)
(* Beispiel : Aggregierung und Kopplung *)
MODULE Konto;
IMPORT Out;
TYPE
(* Attribute *)
Konto* = RECORD
kontonummer* : LONGINT;
saldo * : REAL
END;
5
(* zugehoeriger Referenztyp *)
KontoRef* = POINTER TO Konto;
(* Methoden *)
(* −−> Aggregierung durch typgebundene Prozeduren*)
PROCEDURE (k : KontoRef) einzahlen*(betrag : REAL);
BEGIN
k.saldo := k.saldo + betrag; (* −−> Kopplung *)
END einzahlen;
PROCEDURE (k : KontoRef) auszahlen*(betrag : REAL);
BEGIN
k.saldo := k.saldo − betrag; (* −−> Kopplung *)
END auszahlen;
PROCEDURE (k : KontoRef) auszug*();
BEGIN
Out.String("Kontonummer: ");
Out.Int(k.kontonummer, 10);
Out.String(" Saldo: ");
Out.Real(k.saldo , 10);
Out.String(" Euro");
Out.Ln;
END auszug;
BEGIN
END Konto.
Um eine Instanz der Klasse Konto zu erzeugen, wird eine Variable vom Typ KontoRef deklariert. Der Aufruf
NEW(meinKonto)
legt ein Objekt vom Typ Konto an, d.h. er stellt
entsprechenden Speicherplatz zur Verfügung. Zudem bewirkt er, dass meinKonto auf dieses
Objekt verweist. Über die Variable meinKonto können die Attribute der Instanz angesprochen
werden (meinKonto.Attribut ). Für den Aufruf von Methoden wird ebenfalls die Punktnotation (meinKonto.Methode() ) verwendet.
(* Datei: Konto1/KontoTest.mod *)
(* Beispiel : Aggregierung und Kopplung *)
MODULE KontoTest;
IMPORT Konto, Out;
PROCEDURE ProgMain*();
VAR meinKonto, deinKonto : Konto.KontoRef; (* Variablen vom Referenztyp Konto *)
BEGIN
NEW(meinKonto); (* NEW erzeugt Instanz der Klasse Konto *)
meinKonto.kontonummer := 4531088; (* Zugri auf Attribute *)
meinKonto.saldo := 200.00;
Out.String("Kontonummer: ");
Out.Int(meinKonto.kontonummer, 10);
Out.String(" Saldo: ");
Out.Real(meinKonto.saldo, 10);
Out.Ln;
meinKonto.einzahlen(300.00); (* Methodenaufruf *)
meinKonto.auszug();
Out.Ln;
NEW(deinKonto); (* deinKonto zeigt auf neue Instanz der Klasse Konto *)
deinKonto.kontonummer := 1733065;
deinKonto.saldo := 1000.00;
deinKonto.auszug();
6
deinKonto.auszahlen(100.00);
deinKonto.auszug();
END ProgMain;
BEGIN
END KontoTest.
2.2 Datenkapselung
5
Das Prinzip der Datenkapselung wurde bereits 1972 von David Parnas propagiert . Er prägte
damals den Begri Information Hiding. Im Deutschen wird oft die Bezeichnung Geheimnis-
prinzip verwendet.
Für den Benutzer einer Klasse ist relevant, wie er eine Komponente verwenden kann, nicht
wie sie realisiert ist. Deshalb wird die Struktur eines Objektes und die Implementierung der
Methoden nicht oen gelegt, sondern in der Klasse verborgen. Der Zugri auf die gekapselten Attribute und deren Manipulation ist dann ausschlieÿlich über die Methoden der Klasse
durchführbar. So wird sichergestellt, dass Attributwerte nicht willkürlich geändert und nur
zulässige Werte gespeichert werden.
Das Verstecken der Implementierungsdetails vermindert die Fehleranfälligkeit, da ein unerwarteter Zugri unmöglich ist. Änderungen der Implementierung haben keine Auswirkung
auf den Nutzer, da dieser die Zugrismethoden weiterhin verwenden kann.
Im Fall der Klasse Konto sollen die Attribute kontonummer und saldo vor unkontrolliertem
Zugri geschützt werden. Da das Lesen und Verändern der Attribute aber zunächst noch
möglich sein soll, werden neue Methoden benötigt. Die Methoden getKontonummer() und
setKontonummer(nummer) ermöglichen Lese- und Schreibzugri auf das Attribut kontonummer. Für die Manipulation von saldo wurden die Operationen getSaldo() und setSaldo(betrag)
deniert.
Java
Die Zugrisrechte werden in Java über sogenannte Modizierer festgelegt.
Wird ein Attribut ohne Zusatz deklariert, so ist es in allen Klassen des deklarierenden Paketes
sichtbar, nicht jedoch in anderen Paketen. Das Schlüsselwort
public
6
erlaubt lesenden und
schreibenden Zugri, d.h. die Attribute sind in allen Paketen sichtbar. Sollen Attribute nur
innerhalb der Klasse sichtbar sein, so müssen sie mit dem Modizierer
private gekennzeichnet
werden.
Da die Attribute kontonummer und saldo versteckt werden sollen, sind sie ab sofort mit dem
Vorsatz
private
versehen.
Modizierer regeln auch die Sichtbarkeit von Methoden. Da die Methoden der Klasse Konto
in jedem Paket zugänglich sein sollen, müssen sie als
5 Das
public deklariert werden.
Konzept der Datenkapselung ist ein wichtiges Prinzip der Informatik, das nicht nur in der objektori-
entierten Programmierung eingesetzt wird.
6 Pakete
bestehen aus mehreren Klasse, die zu einer gröÿeren Einheit zusammengefasst werden. Um eine
Klasse einem bestimmten Paket zuzuordnen muss die Anweisung
im Quellcode stehen.
7
package
Paketname; als erste Anweisung
Das Schlüsselwort
public kann auch auf Klassen angewendet werden. Klassen, die mit public
gekennzeichnet sind, können überall benutzt werden. In einer Quelldatei darf jedoch höchstens
eine Klasse als
public deklariert werden.
//Datei: Konto2/Konto.java
//Beispiel : Datenkapselung
public class Konto{
private int kontonummer; // −−> Datenkapselung
private double saldo;
// neue Methoden fuer sicheren Zugri auf Attribut kontonummer
public int getKontonummer(){
return kontonummer;
}
public void setKontonummer(int nummer){
}
kontonummer = nummer;
// neue Methoden fuer sicheren Zugri auf Attribut saldo
public double getSaldo(){
return saldo;
}
public void setSaldo(double betrag){
}
saldo = betrag;
// urspruengliche Methoden
public void einzahlen(double betrag){
}
saldo = saldo + betrag;
public void auszahlen(double betrag){
}
saldo = saldo − betrag;
public void auszug(){
}
}
System.out.println("Kontonummer: "+kontonummer+" Saldo: "+saldo+" Euro");
Auÿerhalb der Klasse Konto ist ein direkter Zugri auf die Attribute kontonummer und
saldo nicht mehr möglich. Der Befehl meinKonto.kontonummer = 4531088 führt zu einem
Fehler bei der Übersetzung. Sogar der Versuch, lesend auf die Variable zuzugreifen erzeugt
eine Fehlermeldung. Die Attribute können nur über entsprechende Methoden angesprochen
werden.
//Datei: Konto2/KontoTest.java
//Beispiel : Datenkapselung
public class KontoTest{
public static void main(String[] args){
Konto meinKonto = new Konto();
/* meinKonto.kontonummer = 4531088; !! Fehler
System.out.println(meinKonto.kontonummer); !! Fehler */
meinKonto.setKontonummer(4531088); // schreibender Zugri auf Attribute ueber entsprechende Methoden
meinKonto.setSaldo(200.00);
System.out.print("Kontonummer: "+meinKonto.getKontonummer()); // lesender Zugri
System.out.println(" Saldo: "+meinKonto.getSaldo()+" Euro");
}
}
8
Oberon-2
In Oberon-2 werden Attribute und Methoden nur dann exportiert, wenn sie in ihrer Deklaration mit einem Stern (*) markiert sind. Wird die Markierung weggelassen, so sind die Namen
nur innerhalb des Moduls sichtbar, in der die Klasse implementiert wird. Andere Module, die
das Modul importieren, können nur auf die exportierten Attribute und Methoden zugreifen.
In der Klasse Konto wird, der Stern an den Attributen kontonummer und saldo entfernt. Der
Name des Record, der zugehörige Referenztyp und die Methoden sollen weiterhin sichtbar
sein und behalten deshalb ihre Markierung.
(* Datei: Konto2/Konto.mod *)
(* Beispiel : Datenkapselung *)
MODULE Konto;
IMPORT Out;
TYPE
Konto* = RECORD
kontonummer : LONGINT; (* −−> Datenkapselung *)
saldo : REAL
END;
KontoRef* = POINTER TO Konto;
(* neue Methoden fuer sicheren Zugri auf Atrribut kontonummer *)
PROCEDURE (k : KontoRef) getKontonummer*() : LONGINT;
BEGIN
RETURN k.kontonummer;
END getKontonummer;
PROCEDURE (k : KontoRef) setKontonummer*(nummer : LONGINT);
BEGIN
k.kontonummer := nummer;
END setKontonummer;
(* neue Methoden fuer sicheren Zugri auf Atrribut saldo *)
PROCEDURE (k : KontoRef) getSaldo*() : REAL;
BEGIN
RETURN k.saldo;
END getSaldo;
PROCEDURE (k : KontoRef) setSaldo*(betrag : REAL);
BEGIN
k.saldo := k.saldo + betrag;
END setSaldo;
(* urspruengliche Methoden *)
PROCEDURE (k : KontoRef) einzahlen*(betrag : REAL);
BEGIN
k.saldo := k.saldo + betrag;
END einzahlen;
PROCEDURE (k : KontoRef) auszahlen*(betrag : REAL);
BEGIN
k.saldo := k.saldo − betrag;
END auszahlen;
PROCEDURE (k : KontoRef) auszug*();
BEGIN
Out.String("Kontonummer: ");
Out.Int(k.kontonummer, 10);
Out.String(" Saldo: ");
Out.Real(k.saldo , 10);
Out.String(" Euro");
Out.Ln;
9
END auszug;
BEGIN
END Konto.
Die Namen kontonummer und saldo werden vom Modul Konto nicht mehr exportiert. Die
Verwendung dieser Namen im Modul KontoTest führt deshalb zu der Fehlermeldung Er-
ror 83: undened record eld . Um die Attribute trotzdem zu bearbeitet, benutzt man die
set- und get-Methoden.
(* Datei: Konto2/KontoTest.mod *)
(* Beispiel : Datenkapselung *)
MODULE KontoTest;
IMPORT Konto, Out;
PROCEDURE ProgMain*();
VAR meinKonto : Konto.KontoRef;
BEGIN
NEW(meinKonto);
(* meinKonto.kontonummer = 4531088; !! Fehler
Out.Int(meinKonto.kontonummer); !! Fehler *)
meinKonto.setKontonummer(4531088); (* schreibender Zugri auf Attribute ueber entsprechende Methoden *)
meinKonto.setSaldo(200.00);
Out.String("Kontonummer: ");
Out.Int(meinKonto.getKontonummer(),10); (* lesender Zugri *)
Out.String(" Saldo: ");
Out.Real(meinKonto.getSaldo(),10);
Out.String(" Euro");
END ProgMain;
BEGIN
Out.Open
END KontoTest.
2.3 Konstruktor
Konstruktoren sind spezielle Methoden einer Klasse, die immer dann zum Einsatz kommen,
wenn ein neues Objekt erzeugt werden soll. Sie sind eine elegante Möglichkeit, Objekte zu
initialisieren, können aber auch individuelles Verhalten implementieren.
Die Klasse Konto erhält einen Konstruktor, der als Parameter eine Nummer übergeben bekommt. Diese Nummer dient dem Konstruktor als Initialwert für das Attribut kontonummer.
Die Methode setKontonummer(nummer) wird gelöscht, da eine Kontonummer einmalig bei
der Erönung des Kontos vergeben wird. Der Wert von saldo wird durch den Konstruktor
auf 0.00 gesetzt. Änderungen des Kontostandes sollen anschlieÿend nur über die Methoden
einzahlen(betrag) und auszahlen(betrag) möglich sein, weshalb setSaldo(betrag) entfernt wird.
Java
In Java ist der Konstruktor eine Methode, die den gleichen Namen, wie die zugehörige Klasse trägt. Er unterscheidet sich bei der Denition von anderen Methoden dadurch, dass kein
10
Objekttyp für die Rückgabe speziziert werden darf.
Der Konstruktor wird bei der Erzeugung eines Objektes nach dem Operator
new
aufgerufen.
Ein expliziter Aufruf der Form: Referenz.Konstuktor() ist nicht möglich.
Ist für eine Klasse kein Konstruktor explizit deklariert, dann baut der Compiler einen Standardkonstruktor ohne Parameter ein. Dieser Konstruktor initialisiert alle Attribute mit NULL.
//Datei: Konto3/Konto.java
//Beispiel : Konstruktor
public class Konto{
private int kontonummer;
private double saldo;
// −−> Konstruktor
public Konto(int nummer){
kontonummer = nummer;
saldo = 0.0;
}
public int getKontonummer(){
return kontonummer;
}
// setKontonummer(nummer) entfernt
public double getSaldo(){
return saldo;
}
// setSaldo(betrag) entfernt
public void einzahlen(double betrag){
}
saldo = saldo + betrag;
public void auszahlen(double betrag){
}
saldo = saldo − betrag;
public void auszug(){
}
}
System.out.println("Kontonummer: "+kontonummer+" Saldo: "+saldo+" Euro");
Bisher wurden die Instanzen der Klasse Konto mit dem Standardkonstruktor Konto() erstellt.
Dieser wird jetzt durch den neuen Konstruktor Konto(nummer) ersetzt. Er übernimmt die
Initialisierung der Attribute, so dass dies nicht mehr explizit gesetzt werden müssen.
//Datei: Konto3/Konto.java
//Beispiel : Konstruktor
public class KontoTest{
public static void main(String[] args){
Konto meinKonto;
/* bisher :
meinKonto = new Konto(); // Objekt erzeugen
meinKonto.setKontonummer(4531088); // Kontonummer initialisieren
meinKonto.setSaldo(0.00); // Kontostand initialisieren */
/* jetzt :
*/
meinKonto = new Konto(4531088); // Konstruktor erzeugt Objekt und initialisiert Attribute
11
}
}
meinKonto.auszug();
Oberon-2
Im Sprachkonzept von Oberon-2 sind Konstruktoren nicht enthalten. Sie können aber durch
entsprechend implementierte Methoden nachgebaut werden.
Im Beispiel der Klasse Konto wurde eine Methode Konto(nummer) deniert, die als Konstruktor verwendet wird. Diese Methode ist verantwortlich für die Initialisierung der Attribute
kontonummer und saldo.
(* Datei: Konto3/Konto.mod *)
(* Beispiel : Konstruktor *)
MODULE Konto;
IMPORT Out;
TYPE
Konto* = RECORD
kontonummer : LONGINT;
saldo : REAL
END;
KontoRef* = POINTER TO Konto;
(* −−> Konstruktor *)
PROCEDURE (k : KontoRef) Konto*(nummer : LONGINT);
BEGIN
k.kontonummer := nummer;
k.saldo := 0.0;
END Konto;
PROCEDURE (k : KontoRef) getKontonummer*() : LONGINT;
BEGIN
RETURN k.kontonummer;
END getKontonummer;
(* setKontonummer(nummer) entfernt *)
PROCEDURE (k : KontoRef) getSaldo*() : REAL;
BEGIN
RETURN k.saldo;
END getSaldo;
(* setSaldo(betrag) entfernt *)
PROCEDURE (k : KontoRef) einzahlen*(betrag : REAL);
BEGIN
k.saldo := k.saldo + betrag;
END einzahlen;
PROCEDURE (k : KontoRef) auszahlen*(betrag : REAL);
BEGIN
k.saldo := k.saldo − betrag;
END auszahlen;
PROCEDURE (k : KontoRef) auszug*();
BEGIN
Out.String("Kontonummer: ");
Out.Int(k.kontonummer, 10);
Out.String(" Saldo: ");
Out.Real(k.saldo , 10);
Out.String(" Euro");
12
Out.Ln;
END auszug;
BEGIN
END Konto.
Instanzen einer Klasse werden weiterhin durch den Operator
NEW
erzeugt. Die Methode
Konto(nummer) initialisiert lediglich die Attribute. Da Konto(nummer) eine normale Methode ist, kann sie - im Gegensatz zu Konstruktoren in Java - uneingeschränkt genutzt und
mittels Referenz.Konto(nummer) aufgerufen werden. Der Programmierer muss selbst darauf
achten, dass sie nur nach der Generierung eines neuen Objektes verwendet wird, da sie sonst
eventuell die Daten eines bereits bestehenden Objektes überschreibt.
7
(* Datei: Konto3/KontoTest.mod *)
(* Beispiel : Konstruktor*)
MODULE KontoTest;
IMPORT Konto, Out;
PROCEDURE ProgMain*();
VAR meinKonto : Konto.KontoRef;
BEGIN
NEW(meinKonto); (* Objekt erzeugen *)
(* bisher :
meinKonto.setKontonummer(4531088); // Kontonummer initialisieren
meinKonto.setSaldo(0.00); // Kontostand initialisieren *)
(* jetzt :
*)
meinKonto.Konto(4531088); (* Konstruktor initialisiert Attribute *)
meinKonto.auszug;
END ProgMain;
BEGIN
Out.Open
END KontoTest.
2.4 Klassen- und instanzspezische Komponenten
Für Klassen können statische Variablen und Methoden deniert werden. Das besondere an
diesen Komponenten ist, dass sie nicht auf die Existenz von Instanzen der Klasse angewiesen
sind.
Von Klassenvariablen existiert, unabhängig von der Anzahl der Instanzen, nur ein Exemplar,
das von allen Objekten der Klasse gemeinsam genutzt wird. Sie werden beim Laden der
Klasse erzeugt und erst freigegeben, wenn das Programm endet. Während der Zugri auf
Instanzvariablen durch den Befehl Instanz.Variablenname erfolgt, werden klassenspezische
Variablen in der Form: Klassenname.Variablenname angesprochen. Wenn ein Attribut für alle
Instanzen einer Klasse den gleichen Wert haben soll, ist die Verwendung von Klassenvariablen
angebracht. Anstatt den Wert in jedem einzelnen Objekt anzulegen, wird er in der Klasse
7 Eine
weitere Variante der Konstruktormethode für Oberon-2-Programme, die den NEW-Operator bein-
haltet, wird in den Beispielen
Mediendatenbank und geometrische Figuren vorgestellt.
13
gespeichert.
Klassenmethoden werden eingesetzt um Verhalten zu implementieren, dass für alle Instanzen
einer Klasse gleich ist. Man muss jedoch darauf achten, dass sie nicht auf Instanzvariablen
zugreifen oder Instanzmethoden aufrufen, da sie auch aufgerufen werden dürfen, wenn keine
Instanz existiert. Zudem wäre in solch einer Situation nicht eindeutig festgelegt, welche Instanz
verwendet werden muss. Klassenmethoden werden - genau wie Klassenvariablen - über den
Name der Klasse aufgerufen.
Die Klasse Konto könnte zum Beispiel einen Instanzenzähler enthalten, der festhält, wie viele
Objekte vom Typ Konto erzeugt wurden. Der Wert der Variablen muss also immer dann
um 1 erhöht werden, wenn ein neues Objekt angelegt wird. Deshalb sollte die entsprechende
Anweisung im Konstruktor implementiert werden. Zu Beginn der Programm-Abarbeitung
steht der Zähler auf 0, da noch keine Instanz existiert.
Java
Klassenvariablen und -methoden werden in Java durch das Schlüsselwort
static gekennzeich-
net.
Im Beispiel der Klasse Konto wird das Attribut zaehler durch den Vorsatz
static
zu einer
Klassenvariablen. Unabhängig von der Anzahl der Konto -Objekte existiert nur ein Exemplar
dieser Variablen. Die Attribute kontonummer und saldo sind Instanzvariablen. Sie werden für
jede Instanz der Klasse Konto neu angelegt (siehe Abb.3).
//Datei: Konto4/Konto.java
//Beispiel : Klassen− und Instanzvariablen
public class Konto{
static int zaehler = 0; // −−> Klassenvariable
private int kontonummer; // −−> Instanzvariablen
private double saldo;
public Konto(int nummer){
}
zaehler = zaehler + 1;
kontonummer = nummer;
saldo = 0.00;
public int getKontonummer(){
return kontonummer;
}
public double getSaldo(){
return saldo;
}
public void einzahlen(double betrag){
}
saldo = saldo + betrag;
public void auszahlen(double betrag){
}
saldo = saldo − betrag;
public void auszug(){
}
}
System.out.println("Kontonummer: "+kontonummer+" Saldo: "+saldo+" Euro");
14
Abbildung 3: Instanz- und Klassenvariablen
Für die Variable zaehler wird beim Laden der Klasse Konto Speicherplatz belegt, der mit 0
vorinitialisiert ist. Nachdem durch den Konstruktor eine Instanz erzeugt wurde, ist der Wert
von zaehler 1. Das Anlegen einer weiteren Instanz bewirkt eine erneute Erhöhung.
// Datei: Konto4/KontoTest.java
// Beispiel : Klassenvariablen
public class KontoTest{
public static void main(String[] args){
// Zugri auf Klassenvariablen ueber Klassennamen
System.out.println("Anzahl Konten: "+Konto.zaehler);
Konto meinKonto = new Konto(4531088);
System.out.println("Anzahl Konten: "+Konto.zaehler);
}
}
Konto deinKonto = new Konto(1733065);
System.out.println("Anzahl Konten: "+Konto.zaehler);
Oberon-2
In Oberon-2 gibt es keine statischen Komponenten im eigentlichen Sinne. Die Datenfelder des
Recordtyps sind Instanzvariablen und werden für jedes Objekt neu angelegt. Klassenvariablen
können somit nicht im Record deklariert werden. Stattdessen werden sie im Variablendeklarationsteil des Moduls deniert. Man muss jedoch beachten, dass diese Variablen nicht an
eine Klasse gebunden sind. Viel mehr sind es Modulvariablen, die von auÿen mittels Mo-
dulname.Variablenname angesprochen werden. Dies ist insbesondere dann wichtig, wenn der
Klassenname von Namen des Moduls abweicht oder mehrere Klassen in einem Modul implementiert sind.
Ähnliches gilt für Methoden. Instanzmethoden werden durch einen Empfängerparameter dem
15
Recordtyp zugeordnet. Wird dieser Parameter weggelassen, so sind die Prozeduren statisch.
Sie können in Modulen, über den Modulnamen aufgerufen werden.
In unserem Beispiel wird im Modul Konto eine Integer-Variable zaehler deklariert. Der Konstruktor der Klasse Konto verwendet diese Variable, um die Anzahl der Instanzen zu zählen.
Um auf den Wert der Variable zuzugreifen genügt innerhalb des deklarierenden Moduls der
Variablenname.
(* Datei: Konto4/Konto.mod *)
(* Beispiel : Klassen− und Instanzvariablen *)
MODULE Konto;
IMPORT Out;
TYPE
Konto* = RECORD
kontonummer : LONGINT; (* −−> Instanzvariablen *)
saldo : REAL
END;
KontoRef* = POINTER TO Konto;
(* −−> Modulvariable, ersetzt Klassenvariable *)
VAR zaehler* : INTEGER;
PROCEDURE (k : KontoRef) Konto*(nummer : LONGINT);
BEGIN
zaehler := zaehler + 1;
k.kontonummer := nummer;
k.saldo := 0.0;
END Konto;
PROCEDURE (k : KontoRef) getKontonummer*() : LONGINT;
BEGIN
RETURN k.kontonummer;
END getKontonummer;
PROCEDURE (k : KontoRef) getSaldo*() : REAL;
BEGIN
RETURN k.saldo;
END getSaldo;
PROCEDURE (k : KontoRef) einzahlen*(betrag : REAL);
BEGIN
k.saldo := k.saldo + betrag;
END einzahlen;
PROCEDURE (k : KontoRef) auszahlen*(betrag : REAL);
BEGIN
k.saldo := k.saldo − betrag;
END auszahlen;
PROCEDURE (k : KontoRef) auszug*();
BEGIN
Out.String("Kontonummer: ");
Out.Int(k.kontonummer, 10);
Out.String(" Saldo: ");
Out.Real(k.saldo , 10);
Out.String(" Euro");
Out.Ln;
END auszug;
BEGIN
zaehler := 0;
END Konto.
16
(* Datei: Konto4/KontoTest.mod *)
(* Beispiel : Klassen− und Instanzvariablen*)
MODULE KontoTest;
IMPORT Konto, Out;
PROCEDURE ProgMain*();
VAR meinKonto, deinKonto : Konto.KontoRef;
BEGIN
Out.String("Anzahl Konten: ");
(* Zugri auf Klassenvariablen ueber Modulnamen *)
Out.Int(Konto.zaehler,2);
Out.Ln;
NEW(meinKonto);
meinKonto.Konto(4531088);
Out.String("Anzahl Konten: ");
Out.Int(Konto.zaehler,2);
Out.Ln;
NEW(deinKonto);
meinKonto.Konto(1733065);
Out.String("Anzahl Konten: ");
Out.Int(Konto.zaehler,2);
END ProgMain;
BEGIN
Out.Open
END KontoTest.
2.5 Manipulierbarkeit
Instanzen einer Klasse können wie andere Werte manipuliert werden. Dazu gehört unter anderem, dass sie einer anderen Referenzvariable vom selben Typ zugewiesen werden können.
Im Programmbeispiel werden drei Referenzvariablen angelegt, aber nur zwei Instanzen erzeugt. Die Zuweisung sorgt dafür, dass die beiden Variablen meinKonto und unserKonto auf
dasselbe Objekt verweisen, während deinKonto die zweite Instanz referenziert (siehe Abb. 4).
Auÿerdem können Objekte auf Gleichheit und Ungleichheit getestet werden. Wichtig ist dabei, dass nur Zeigervergleiche und keine Wertvergleiche stattnden. Obwohl die Kontonummer
und der Kontostand von meinKonto und deinKonto identisch sind, liefert der Vergleich den
Wert FALSE. Nur der Vergleich von meinKonto und unserKonto ergibt TRUE.
Es ist möglich, Prozeduren oder Funktionen zu denieren, die Klasseninstanzen als Parameter
haben. Für die Klasse Konto wurde eine Prozedur ueberweisen implementiert, die als Argumente zwei Konto -Objekt nimmt und einen vorgegebenen Betrag von einem auf das andere
überweist.
Abbildung 4: Zuweisung und Vergleich von Objekten
17
Java
//Datei: Konto5/Konto.java
//Beispiel : Manipulierbarkeit
public class KontoTest{
static void ueberweisen(double betrag, Konto kontoA, Konto kontoZ){
}
kontoA.auszug();
kontoZ.auszug();
kontoA.auszahlen(betrag);
kontoZ.einzahlen(betrag);
System.out.print("Von Konto "+kontoA.getKontonummer());
System.out.print(" wurden "+betrag+" Euro ");
System.out.println("auf Konto "+kontoZ.getKontonummer()+" ueberwiesen.");
kontoA.auszug();
kontoZ.auszug();
public static void main(String[] args){
Konto meinKonto = new Konto(4531088);
meinKonto.einzahlen(300.00);
Konto deinKonto = new Konto(4531088);
deinKonto.einzahlen(300.00);
Konto unserKonto;
// Zuweisung
unserKonto=deinKonto;
// Vergleich
if (meinKonto == deinKonto)
System.out.println("Mein Konto und dein Konto sind identisch.");
else
System.out.println("Mein Konto und dein Konto sind verschieden.");
if (unserKonto == deinKonto)
System.out.println("Unser Konto und dein Konto sind identisch.");
else
System.out.println("Unser Konto und dein Konto sind verschieden.");
}
}
// Parameter
ueberweisen(30.00, meinKonto, deinKonto);
Oberon-2
(* Datei: Konto5/KontoTest.mod *)
(* Beispiel : Manipulierbarkeit*)
MODULE KontoTest;
IMPORT Konto,Out;
PROCEDURE ueberweisen(betrag : REAL; kontoA, kontoZ : Konto.KontoRef);
BEGIN
kontoA.auszug();
kontoZ.auszug();
kontoA.auszahlen(betrag);
kontoZ.einzahlen(betrag);
Out.String("Von Konto ");
Out.Int(kontoA.getKontonummer(),10);
Out.String(" wurden ");
Out.Real(betrag,10);
Out.String(" Euro auf Konto ");
Out.Int(kontoZ.getKontonummer(),10);
Out.String(" ueberwiesen."); Out.Ln;
18
kontoA.auszug();
kontoZ.auszug();
END ueberweisen;
PROCEDURE ProgMain*();
VAR meinKonto, deinKonto, unserKonto : Konto.KontoRef;
BEGIN
NEW(meinKonto);
meinKonto.Konto(4531088);
meinKonto.einzahlen(300.00);
NEW(deinKonto);
deinKonto.Konto(1733065);
deinKonto.einzahlen(100.00);
(* Zuweisung *)
unserKonto := deinKonto;
(* Vergleich *)
IF meinKonto = deinKonto
THEN Out.String("Mein Konto und dein Konto sind identisch."); Out.Ln;
ELSE Out.String("Mein Konto und dein Konto sind verschieden."); Out.Ln;
END;
IF unserKonto = deinKonto
THEN Out.String("Unser Konto und dein Konto sind identisch."); Out.Ln;
ELSE Out.String("Unser Konto und dein Konto sind verschieden."); Out.Ln;
END;
(* Parameter *)
ueberweisen(30.00, meinKonto, deinKonto);
END ProgMain;
BEGIN
Out.Open
END KontoTest.
2.6 Vererbung
Oft modellieren Klassen Dinge der realen Welt, die zwar in verschiedenen Varianten vorkommen, aber auch grundlegende Gemeinsamkeiten haben. In einer Bank gibt es z.B. verschiedene
Kontoarten. Ein Sparkonto ist ein Konto mit Kontonummer und Kontostand; zusätzlich zum
normalen Konto werden jedoch Zinsen auf das Guthaben gezahlt. Auÿerdem gibt es noch
Girokonten, die über ein bestimmtes Kreditlimit verfügen. Für beide Konten sind das Einzahlen und Auszahlen von Geldbeträgen, sowie das Drucken eines Kontoauszuges typische
Handlungen.
Diese Methoden sind bereits in der Klasse Konto implementiert und können durch Kopieren
in die Klassen Sparkonto und Girokonto übernommen werden. Damit müssen jedoch drei Versionen des selben Algorithmus gepegt werden. Eine Möglichkeit, diese Code-Duplizierung zu
vermeiden ist Vererbung.
Vererbung ist eines der mächtigsten und interessantesten Konzepte des objektorientierten
Programmierens. Sie ermöglicht, dass neue Klassen auf bereits vorhandenen Klassen und deren Funktionalität aufbauen.
Haben mehrere Klassen gleiche Eigenschaften, so werden diese in einer allgemeinen Superklasse zusammengefasst. Wird von dieser Klasse eine Subklasse abgeleitet, so erbt sie alle
19
Komponenten der übergeordneten Klasse, d.h. Attribute und Methoden werden automatisch
übernommen und müssen nicht erneut implementiert werden. Die Subklasse kann anschlieÿend die für sie relevanten Attribute und Methoden hinzufügen.
Auf diese Art und Weise ist es möglich, hierarchische Beziehungen zwischen einzelnen Klassen
herzustellen. So können z.B. mehrere Klassen von einer Superklasse erben, und eine Subklasse
kann wiederum selbst Superklasse weiterer Subklassen sein.
Von der Klasse Konto soll zunächst nur die Klasse Sparkonto abgeleitet werden. Diese erbt
die Attribute kontonummer und saldo, benötigt aber noch ein weiteres Attribut zinssatz, in
dem festgehalten wird viele Prozent Zinsen gezahlt werden. Auf das Attribut kann über die
Methoden getZinssatz() und setZinssatz(zins) zugegrien werden. Für die Buchung der Zinsen auf das jeweilige Konto, wird eine zusätzliche Methode verzinsen() implementiert. Alle
weiteren Methoden werden von der Superklasse übernommen. Insgesamt ergibt sich damit
das in Abb. 5 dargestellte Klassendiagramm.
Abbildung 5: Vererbung
Java
Um in Java eine neue Klasse aus einer bestehenden abzuleiten, wird im Kopf der Klassenbe-
extends zusammen mit dem Namen der Superklasse verwendet.
Die Superklasse gibt dann die Variablen und Methoden, die public oder protected deklariert sind, an die abgeleiteten Klassen weiter. Komponenten, die als private eingestuft sind,
schreibung das Schlüsselwort
werden nicht vererbt. Sollen Attribute oder Methoden für andere Klassen nicht sichtbar sein,
aber trotzdem an die Subklasse vererbt werden, so müssen sie mit den Schlüsselwort
ted
gekennzeichnet
und saldo von
8
sein.
private
in
protec-
Deshalb ändert sich der Modizierer der Attribute kontonummer
protected. Die Methoden der Klasse Konto
behalten den Zusatz
public, damit sie auch für andere Klassen sichtbar sind.
//Datei: Konto6/Konto.java
//Beispiel : Vererbung
public class Konto{
8 Komponenten
für die kein Modizierer angegeben ist, werden nur innnerhalb eines Paketes an Subklassen
vererbt.
20
static int zaehler = 0;
protected int kontonummer; // Attribute sollen vererbt werden
protected double saldo;
public Konto(int nummer){
}
zaehler = zaehler + 1;
kontonummer = nummer;
saldo = 0.00;
// Implementierung der Methoden hier ausgelassen
}
public int getKontonummer(){}
public double getSaldo(){}
public void einzahlen(double betrag){}
public void auszahlen(double betrag){}
public void auszug(){}
Die Klasse Sparkonto wird durch
extends von der Klasse Konto abgeleitet. Dadurch erbt sie
alle Eigenschaften der übergeordneten Klasse und kann diese im Programmcode verwenden.
Alle Deklarationen die im Rumpf der Klasse gemacht werden, sind Erweiterungen von Konto.
Konstruktoren werden nicht vererbt, aus diesem Grund benötigt die Klasse Sparkonto einen
eigenen Konstuktor Sparkonto(nummer, zins). In diesem wird mit dem Befehl
super(nummer)
zunächst der Konstruktor der Superklasse aufgerufen, d.h. alle Anweisungen von Konto(nummer)
werden ausgeführt. Anschlieÿend muss nur noch das neue Attribut zinssatz initialisiert werden.
Die Methode verzinsen() ändert den Kontostand der aufrufenden Instanz. Obwohl das Attribut saldo nicht in der Klasse Sparkonto deklariert worden ist, kann der Name verwendet
werden.
//Datei: Konto6/Sparkonto.java
//Beispiel : Vererbung
// −−> Vererbung
public class Sparkonto extends Konto{
private double zinssatz; // zusaetzliches Attribut
// Konstuktor der Klasse Sparkonto
public Sparkonto(int nummer, double zins){
super(nummer); // Konstruktor der Superklasse Konto wird ausgerufen
}
zinssatz = zins;
// neue Methoden fuer Zugri auf Attribut zinssatz
public double getZinssatz(){
return zinssatz;
}
public void setZinssatz(double zins){
}
zinssatz = zins;
// neue Methode
public void verzinsen(){
}
}
saldo = saldo + (zinssatz *saldo)/100; // verwendet geerbte Attribute
Eine Instanz der Klasse Sparkonto wird durch den Konstruktor Sparkonto(nummer,zins) erzeugt und initialisiert. Jedes Objekt von Typ Sparkonto besitzt die 3 Attribute kontonummer,
21
saldo und zinssatz, die mit Hilfe der Methoden getKontonummer(), getSaldo() und getZinssatz() ausgelesen werden können. Das Datenfeld zinssatz kann durch die Methode setZinssatz(zins) beschrieben werden.
Alle Methoden, die für eine Instanz der Klasse Konto deniert sind, können auch auf ein Objekt vom Typ Sparkonto angewendet werden. Dazu gehören die Methoden einzahlen(betrag),
auszahlen(betrag) und auszug(). Zusätzlich verfügt jedes Sparkonto -Objekt über die Methode
verzinsen().
//Datei: Konto6/KontoTest.java
//Beispiel : Vererbung
public class KontoTest{
public static void main(String[] args){
Sparkonto meinSparkonto = new Sparkonto(5613990,3.4);
// jede Instanz der Klasse Sparkonto hat 3 Attribute
System.out.print("Kontonummer: "+meinSparkonto.getKontonummer()); // geerbtes Attribut
System.out.print(" Saldo: "+meinSparkonto.getSaldo()+" Euro"); //geerbtes Attribut
System.out.println(" Zinssatz: "+meinSparkonto.getZinssatz()); // neu deklariertes Attribut
// Methoden einzahlen(betrag), auszahlen(betrag) und auszug() koennen verwendet werden
meinSparkonto.einzahlen(250.00); // geerbte Methoden
meinSparkonto.auszug();
meinSparkonto.auszahlen(10.00);
meinSparkonto.auszug();
}
}
// ausserdem gibt es fuer Instanzen der Klasse Sparkonto die Methode verzinsen()
meinSparkonto.verzinsen(); // neu implementierte Methode
meinSparkonto.auszug();
Oberon-2
In Oberon-2 erbt die Subklasse nur die Komponenten, die vom Modul der Superklasse exportiert werden. Soll die Klasse Sparkonto die Attribute kontonummer und saldo enthalten,
so müssen diese in der Klasse Konto mit einem Stern markiert sein. Damit sind sie auch
in anderen Modulen sichtbar und nicht mehr gekapselt. Jeder Programmier muss für seine
Anwendung selbst entscheiden, ob Datenkapselung oder Vererbung wichtiger ist. Beide Konzepte können nur dann parallel verwendet werden, wenn Sub- und Superklasse in einem Modul
implementiert sind. Dies geht aber zu Lasten der Programmlesbarkeit und das Prinzip der
Aggregierung wird gelockert.
(* Datei: Konto6/Konto.mod *)
(* Beispiel : Vererbung *)
MODULE Konto;
IMPORT Out;
TYPE
Konto* = RECORD
kontonummer* : LONGINT; (* Attribute sollen vererbt werden *)
saldo * : REAL
END;
KontoRef* = POINTER TO Konto;
22
VAR zaehler* : INTEGER;
(* Implementierung des Konstruktors und der Methoden wurde hier ausgelassen *)
PROCEDURE (k : KontoRef) Konto*(nummer : LONGINT);
PROCEDURE (k : KontoRef) getKontonummer*() : LONGINT;
PROCEDURE (k : KontoRef) getSaldo*() : REAL;
PROCEDURE (k : KontoRef) einzahlen*(betrag : REAL);
PROCEDURE (k : KontoRef) auszahlen*(betrag : REAL);
PROCEDURE (k : KontoRef) auszug*();
BEGIN
zaehler := 0;
END Konto.
Indem man den Recordtyp einer Klasse in Klammern hinter das Schlüsselwort
RECORD in
die Typdeklaration einer neuen Klasse schreibt, drückt man aus, dass diese von der anderen
abgeleitet werden soll. Bendet sich die Implementierung der Superklasse in einem anderen
Modul, so muss dieses importierte Modul und der Modulname bei der Typableitung mit angegeben werden.
Die Klasse Sparkonto übernimmt somit alle Attribute und Methoden der Klasse Konto, inklusive der Konstruktormethode. Diese wird im neuen Konstruktor Sparkonto(nummer, zins)
verwendet, um die geerbten Attribute kontonummer und saldo zu initialisieren. Das neue
Attribut zinssatz muss explizit mit einem Startwert belegt werden.
Die zusätzlichen Methoden der Klasse Sparkonto werden wie gewohnt als typgebundene Prozeduren implementiert.
(* Datei: Konto6/Sparkonto.mod *)
(* Beispiel : Vererbung *)
MODULE Sparkonto;
IMPORT Konto, Out;
TYPE
Sparkonto* = RECORD (Konto.Konto) (* −−> Vererbung *)
zinssatz * : REAL; (* zusaetzliches Attribut *)
END;
SparkontoRef* = POINTER TO Sparkonto;
(* Konstuktor der Klasse Sparkonto *)
PROCEDURE (sk : SparkontoRef) Sparkonto*(nummer : LONGINT; zins : REAL);
BEGIN
sk.Konto(nummer); (* Konstruktor der Superklasse Konto wird aufgerufen *)
sk. zinssatz := zins ;
END Sparkonto;
(* neue Methoden fuer Zugri auf Attribut zinssatz *)
PROCEDURE (sk : SparkontoRef) getZinssatz*() : REAL;
BEGIN
RETURN sk.zinssatz;
END getZinssatz;
PROCEDURE (sk : SparkontoRef) setZinssatz*(zins : REAL);
BEGIN
sk. zinssatz := zins ;
END setZinssatz;
(* neue Methode *)
PROCEDURE (sk : SparkontoRef) verzinsen*();
BEGIN
sk.saldo := sk.saldo + (sk. zinssatz *sk.saldo)/100; (* verwendet geerbte Attribute *)
23
END verzinsen;
BEGIN
END Sparkonto.
Der
NEW -Operator erzeugt ein Objekt vom Typ Sparkonto und speichert einen Adressver-
weis auf dieses Objekt in meinSparkonto. Die Attribute werden von durch den Aufruf der
Methode Sparkonto(nummer, zins) mit Werten belegt, so dass sich die in Abb. 6 dargestellte
Situation ergibt.
Für die Instanz meinSparkonto können alle Methoden aufgerufen werden, die in den Klassen
Sparkonto und Konto implementiert wurden.
Abbildung 6: Instanz der Klasse Sparkonto
(* Datei: Konto6/KontoTest.mod *)
(* Beispiel : Verbung*)
MODULE KontoTest;
IMPORT Sparkonto, Out;
PROCEDURE ProgMain*();
VAR meinSparkonto : Sparkonto.SparkontoRef;
BEGIN
NEW(meinSparkonto);
meinSparkonto.Sparkonto(5613990,3.4);
(* jede Instanz der Klasse Sparkonto hat 3 Attribute *)
Out.String("Kontonummer: ");
Out.Int(meinSparkonto.getKontonummer(),10); (* geerbtes Attribut *)
Out.String(" Saldo: ");
Out.Real(meinSparkonto.getSaldo(),10); (* geerbtes Attriubut *)
Out.String(" Euro Zinssatz: ");
Out.Real(meinSparkonto.getZinssatz(),10); (* neu deklariertes Attribut *)
Out.Ln;
(* Methoden einzahlen(betrag), auszahlen(betrag) und auszug() koennen verwendet werden *)
meinSparkonto.einzahlen(250.00); (* geerbte Methoden *)
meinSparkonto.auszug();
meinSparkonto.auszahlen(10.00);
meinSparkonto.auszug();
(* ausserdem gibt es fuer Instanzen der Klasse Sparkonto die Methode verzinsen() *)
meinSparkonto.verzinsen(); (* neu implementierte Methode *)
meinSparkonto.auszug();
END ProgMain;
BEGIN
Out.Open
END KontoTest.
24
2.7 Reimplementierung
In vielen Fällen reicht es nicht aus, Methoden unverändert von der Superklasse zu erben.
Für ein Girokonto muss bspw. beim Auszahlen überprüft werden, ob der Überziehungskredit
9
überschritten wird. Ist dies der Fall, darf kein Geld ausgezahlt werden.
Die Klasse Girokonto
kann trotzdem von Konto abgeleitet werden, die geerbte Methode muss lediglich an die neue
Situation angepasst werden.
Wird in einer Subklasse eine Methode der Superklasse mit einer anderen Funktionalität implementiert, so bezeichnet man diesen Vorgang als Überschreiben oder Reimplementieren der
Methode. Das ist dann der Fall, wenn man die Methode in der Subklasse mit gleichem Rückgabewert, Namen und Parametern deniert und die Anweisungen im Methodenrumpf ändert.
Die Objekte der Subklasse haben dann zwei Methoden mit gleichem Namen und gleicher
Signatur, jedoch wird die ursprüngliche Methode der Superklasse von der überschreibenden
Methode verdeckt.
Java
Solange eine Methode in der Superklasse nicht mit
nal
gekennzeichnet ist, kann man sie in
einer Subklasse überschreiben. Dazu wird der Methodenkopf aus der übergeordneten Klasse übernommen und die Anweisungen im Methodenrumpf werden der Aufgabe entsprechend
angepasst.
Die Klasse Girokonto wird von Konto abgeleitet und reimplementiert die Methode auszah-
len(betrag). Der gewünschte Betrag wird nicht mehr in allen Fällen vom Konto abgebucht,
sondern nur wenn das Limit nicht überschritten ist. Diese Voraussetzung überprüft die eingebaute if-Anweisung. Ist die Bedingung erfüllt, ruft der Befehl
super.auszahlen(betrag) die
verdeckte Methode der Superklasse Konto auf. In alle anderen Fällen wird eine Fehlermeldung ausgegeben.
Alle anderen Methoden werden unverändert von Konto übernommen.
//Datei: Konto7/Girokonto.java
//Beispiel : Reimplementierung
public class Girokonto extends Konto{
private double limit;
public Girokonto(int nummer, double limit){
super(nummer);
if ( limit < 0)
limit = −limit;
this . limit = limit ; // Parameter this zeigt auf das Objekt fuer das die Methode aufgerufen wird
}
public double getLimit(){
return limit ;
}
// Methode fuer schreibenden Zugri ueberprueft ob limit postiv ist
public void setLimit(double limit){
if ( limit < 0)
9 Eine Überprüfung, ob das Konto überzogen wird ist natürlich auch für Konten ohne Kreditrahmen sinnvoll.
Ich habe jedoch darauf verzichtet.
25
limit = −limit;
}
this . limit = limit ;
// −−> Reimplementierung
public void auszahlen(double betrag){
if (betrag <= saldo + limit)
super.auszahlen(betrag);
else
}
}
System.out.println("Limit ueberschritten!");
Genau wie die Klasse Sparkonto erbt Girokonto alle Attribute und Methoden von Konto.
Insbesondere die Methode einzahlen(betrag) kann auf Instanzen der Klasse Girokonto angewendet werden.
Ist auf dem Konto noch genügend Geld vorhanden, so verhält sich die Methode auszah-
len(betrag) - wenn sie für ein Girokonto -Objekt aufgerufen wird - genauso, als wäre das Objekt eine Instanz der Klasse Konto. Der Unterschied zwischen beiden Implementierungen zeigt
sich erst wenn man das Konto überzieht. Vom normalen Konto wird der Betrag abgebucht,
während das Girokonto die Auszahlung verweigert.
Welche Implementierung verwendet wird, hängt davon ab, von welchem Typ das aufrufende
Objekt ist.
//Datei: Konto7/KontoTest.java
//Beispiel : Reimplenetierung
public class KontoTest{
public static void main(String[] args){
Konto meinKonto = new Konto(4531088);
meinKonto.einzahlen(400.00);
meinKonto.auszug();
System.out.println("Versuche 100 Euro abzuheben.");
meinKonto.auszahlen(100.00); // Methode auszahlen(betrag) der Klasse Konto
meinKonto.auszug();
System.out.println("Versuche 500 Euro abzuheben.");
meinKonto.auszahlen(500.00); // Methode auszahlen(betrag) der Klasse Konto
meinKonto.auszug();
System.out.println ();
}
}
Girokonto meinGirokonto = new Girokonto(1733065,100);
meinGirokonto.einzahlen(400.00);
meinGirokonto.auszug();
System.out.println("Versuche 100 Euro abzuheben.");
meinGirokonto.auszahlen(100.00); // Methode auszahlen(betrag) der Klasse Girokonto
meinGirokonto.auszug();
System.out.println("Versuche 500 Euro abzuheben.");
meinGirokonto.auszahlen(500.00); // Methode auszahlen(betrag) der Klasse Girokonto
meinGirokonto.auszug();
Oberon-2
Oberon-2 ermöglicht ebenfalls das Überschreiben von Methoden. Hierzu deklariert man die
Prozedur mit der gleichen Schnittstelle, bindet sie aber durch den Empfängerparameter an
die Subklasse. Die verdeckte Methode der Superklasse wird aufgerufen, wenn an den Methodennamen ein Pfeil (^) angehängt wird.
26
An der Oberon-Syntax wird nochmal deutlich, dass die Klasse Girokonto tatsächlich über
zwei Varianten der Methode auszahlen(betrag) verfügt, die geerbte Variante aber nur dann
auswählt wird, wenn man dies explizit fordert.
Die Methode setLimit(limit) zeigt, warum Attribute nur durch Methoden verändert werden
sollten, auch wenn sie gekapselt sind. Sie stellt sicher, dass der Überziehungskredit immer als
positiver Wert gespeichert ist. Dies ist ein Detail der Implementierung, das in der Methode
auszahlen(betrag) zum Tragen kommt. Wären negative Werte möglich, so würde in bestimmen Fällen die Auszahlung verweigert werden, obwohl noch genügend Geld auf dem Konto
ist. Dank der im Konstruktor und in setLimit(limit) realisierten Betragsbildung muss sich der
Anwender jedoch nicht um dieses Problem kümmern.
(* Datei: Konto7/Girokonto.mod *)
(* Beispiel : Reimplementierung *)
MODULE Girokonto;
IMPORT Konto, Out;
TYPE
Girokonto* = RECORD (Konto.Konto)
limit * : REAL;
END;
GirokontoRef* = POINTER TO Girokonto;
PROCEDURE (gk : GirokontoRef) Girokonto*(nummer : LONGINT; limit : REAL);
BEGIN
gk.Konto(nummer);
IF limit < 0 THEN
limit := −limit
END;
gk. limit := limit ;
END Girokonto;
PROCEDURE (gk : GirokontoRef) getLimit*() : REAL;
BEGIN
RETURN gk.limit;
END getLimit;
(* Methode fuer schreibenden Zugri ueberprueft ob limit positiv ist *)
PROCEDURE (gk : GirokontoRef) setLimit*(limit : REAL);
BEGIN
IF limit < 0 THEN
limit := −limit
END;
gk. limit := limit ;
END setLimit;
(* −−> Reimplementierung *)
PROCEDURE (gk : GirokontoRef) auszahlen*(betrag : REAL);
BEGIN
IF betrag <= gk.saldo + gk.limit THEN
gk.auszahlen^(betrag);
ELSE
Out.String("Limit ueberschritten!"); Out.Ln;
END;
END auszahlen;
BEGIN
END Girokonto.
27
Von den Klassen Konto und Girokonto wird jeweils eine Instanz erzeugt. Für das Girokonto
wird ein Kreditrahmen von 100
Kontostand von 300
¿
¿ festgelegt. Obwohl beide nach der ersten Auszahlung einen
haben, können nur vom normalen Konto 500
¿
abgehoben werden.
(* Datei: Konto7/KontoTest.mod *)
(* Beispiel : Reimplementierung*)
MODULE KontoTest;
IMPORT Konto, Girokonto, Out;
PROCEDURE ProgMain*();
VAR meinGirokonto : Girokonto.GirokontoRef;
meinKonto : Konto.KontoRef;
BEGIN
NEW(meinKonto);
meinKonto.Konto(4531088);
meinKonto.einzahlen(400.00);
meinKonto.auszug();
Out.String("Versuche 100 Euro abzuheben."); Out.Ln;
meinKonto.auszahlen(100.00); (* Methode auszahlen(betrag der Klasse Konto *)
meinKonto.auszug();
Out.String("Versuche 100 Euro abzuheben."); Out.Ln;
meinKonto.auszahlen(500.00); (* Methode auszahlen(betrag der Klasse Konto *)
meinKonto.auszug();
Out.Ln;
NEW(meinGirokonto);
meinGirokonto.Girokonto(1733065,100);
meinGirokonto.einzahlen(400.00);
meinGirokonto.auszug();
Out.String("Versuche 100 Euro abzuheben."); Out.Ln;
meinGirokonto.auszahlen(100.00); (* Methode auszahlen(betrag) der Klasse Girokonto *)
meinGirokonto.auszug();
Out.String("Versuche 500 Euro abzuheben."); Out.Ln;
meinGirokonto.auszahlen(500.00); (* Methode auszahlen(betrag) der Klasse Girokonto *)
meinGirokonto.auszug();
END ProgMain;
BEGIN
Out.Open
END KontoTest.
2.8 Polymorphismus
Durch Vererbung ist es möglich, dass Variablen zur Laufzeit auf Objekte von verschiedenem
Typ verweisen, d.h. ein Objekt vom Typ einer Subklasse kann einer Variablen der Superklasse
zugewiesen werden. Diese Eigenschaft wird Polymorphismus
10
genannt.
Für jede Variable muss deshalb zwischen ihrem statischen Typ und dem dynamischen Typ
unterschieden werden. Der statische Typ ist der Typ, mit dem sie deklariert wurde. Er bestimmt, auf welche Attribute und Methoden man zugreifen kann, d.h. nur die Komponenten,
die in der Superklasse deklariert wurden, sind ansprechbar. Der dynamische Typ ist der Typ
10 Hier
bezieht sich der Begri Polymorphismus auf polymorphe Variablen. In der Literatur wird häu-
g auch von polymorphen Operationen gesprochen, die durch Überschreiben und Überladen von Methoden
erzeugt werden.
28
des Objektes auf das die Variable zur Laufzeit zeigt. Er bestimmt, welche Methoden aufgerufen werden.
Der dynamische Typ ist zur Übersetzungszeit häug nicht bekannt. Der Compiler kann bei
der Typüberprüfung nur den statischen Typ berücksichtigen. Deshalb muss die entsprechende Methode dynamisch gebunden werden, d.h. es wird erst zur Laufzeit entschieden, welche
Implementierung beim Aufruf einer Methode zum Einsatz kommt.
Der Vorteil dieser Zuweisungskompatibilität besteht darin, dass Programme, die bisher nur
Objekte der Superklasse verwenden, in der Lage sind, automatisch auch mit Objekten der
Subklasse zu arbeiten. Programmänderungen sind nicht nötig. Die Programme können später
sogar mit Klassen arbeiten, die bei der Erstellung noch nicht existierten.
Die Variable meinKonto wird als Variable vom Typ Konto deklariert. Im Verlauf des Programmes verweist sie jedoch auf Objekte verschiedener Typen.
Der Befehl meinKonto.auszahlen(500.00) bspw. löst zwei unterschiedliche Reaktionen aus.
Beim ersten Aufruf wird das Geld wie gewünscht ausgezahlt, da meinKonto auf ein Objekt
von Typ Konto zeigt. Zum Zeitpunkt des zweiten Aufrufs verweist die Variable jedoch auf
eine Instanz der Klasse Girokonto. In diesem Fall wird die Auszahlung verweigert. Dies liegt
daran, dass stets die Version der Methode verwendet wird, die zum dynamischen Typ der
Variable gehört. Die Methoden beider Klassen heiÿen zwar gleich, sind jedoch unterschiedlich
implementiert.
Obwohl meinKonto am Ende des Programmes auf ein Sparkonto -Objekt zeigt, kann die Methode verzinsen() der Klasse Sparkonto nicht auf meinKonto angewendet werden, da ver-
zinsen() in der Klasse Konto nicht existiert. Um auf die Methode verzinsen() zugreifen zu
können, ist eine explizite Typumwandlung erforderlich.
Java
//Datei: Konto8/KontoTest.java
//Beispiel : Polymorphismus
public class KontoTest{
public static void main(String[] args){
Konto meinKonto ; // Variable vom Typ Konto
meinKonto = new Konto(4531088); // meinKonto zeigt auf ein Objekt vom Typ Konto
meinKonto.einzahlen(300.00);
meinKonto.auszug();
meinKonto.auszahlen(500.00); // Methode auszahlen(betrag) der Klasse Konto wird aufgerufen
meinKonto.auszug();
System.out.println ();
meinKonto = new Girokonto(5613990,100); // meinKonto zeigt jetzt auf ein Objekt vom Typ Girokonto
meinKonto.einzahlen(300.00);
meinKonto.auszug();
meinKonto.auszahlen(500.00); // Methode auszahlen(betrag) der Klasse Girokonto wird aufgerufen
// −−> Polymorphismus & dynamische Bindung
meinKonto.auszug();
System.out.println ();
meinKonto = new Sparkonto(1733065,1.25); // meinKonto zeigt jetzt auf ein Objekt von Typ Sparkonto
meinKonto.einzahlen(400.00);
meinKonto.auszug();
29
meinKonto.auszahlen(100.00); // Methode auszahlen(betrag) der Klasse Sparkonto wird aufgerufen
meinKonto.auszug();
/* meinKonto.verzinsen(); // Fehler: die Klasse Konto kennt die Methode verzinsen() nicht */
((Sparkonto)meinKonto).verzinsen();
meinKonto.auszug();
}
}
Oberon-2
(* Datei: Konto8/KontoTest.mod *)
(* Beispiel : Polymorphismus*)
MODULE KontoTest;
IMPORT Konto, Sparkonto, Girokonto, Out;
PROCEDURE ProgMain*();
VAR meinKonto : Konto.KontoRef; (* Variable vom Typ Konto. *)
meinSparkonto : Sparkonto.SparkontoRef;
meinGirokonto : Girokonto.GirokontoRef;
BEGIN
NEW(meinKonto); (* meinKonto zeigt auf ein Objekt vom Typ Konto *)
meinKonto.Konto(4531088);
meinKonto.einzahlen(300.00);
meinKonto.auszug();
meinKonto.auszahlen(500.00); (* Methode auszahlen(betrag) der Klasse Konto wird aufgerufen. *)
meinKonto.auszug();
Out.Ln;
NEW(meinGirokonto);
meinGirokonto.Girokonto(5613990,100);
(* −−> Polymorphismus *)
meinKonto := meinGirokonto; (* meinKonto zeigt jetzt auf ein Objekt vom Typ Girokonto.*)
meinKonto.einzahlen(300.00);
meinKonto.auszug();
meinKonto.auszahlen(500.00); (* Methode auszahlen(betrag) der Klasse Girokonto wird aufgerufen.
−−> dynamische Bindung *)
meinKonto.auszug();
Out.Ln;
NEW(meinSparkonto);
meinSparkonto.Sparkonto(1733065,1.25);
meinKonto := meinSparkonto; (* meinKonto zeigt jetzt auf ein Objekt von Typ Sparkonto *)
meinKonto.einzahlen(400.00);
meinKonto.auszug();
meinKonto.auszahlen(100.00); (* Methode auszahlen(betrag) der Klasse Sparkonto wird aufgerufen *)
meinKonto.auszug();
(*meinKonto.verzinsen(); // Fehler: die Klasse Konto kennt die Methode verzinsen() nicht *)
meinKonto(Sparkonto.SparkontoRef).verzinsen();
meinKonto.auszug();
END ProgMain;
BEGIN
Out.Open
END KontoTest.
2.9 Überladen
Überladen bezeichnet die Tatsache, dass mehrere Methoden mit demselben Namen, jedoch
unterschiedlicher Signatur deklariert werden dürfen. D.h. die Parameterlisten der Methoden
müssen sich in mindestens einem Parameter unterscheiden. Entweder ist die Anzahl der Pa-
30
rameter verschieden oder sie haben unterschiedliche Typen.
Es ist durchaus üblich, dass eine Klasse mehrere Versionen einer Methode anbietet, diese
sollten allerdings eine vergleichbare Funktionalität haben.
Beim Aufruf einer solche Methode entscheidet der Compiler anhand der übergebenen Parameter, welche Implementierung auszuführen ist.
Für ein Konto sollte es bspw. möglich sein, bei der Erönung zu entscheiden ob gleich ein
gewisser Geldbetrag eingezahlt wird oder nicht. Dies bedeutet, dass zwei Konstruktoren notwendig sind. Gleiches gilt für Sparkonten und Girokonten.
Java
Die Programmiersprache Java erlaubt das Überschreiben von Methoden und Konstruktoren.
Der bisherigen Klassenbeschreibungen von Konto wird einfach ein zweiter Konstruktor hinzugefügt, der mit zwei Parametern deklariert ist. Für die Klassen Sparkonto und Girokonto
wird ein weiterer Konstruktor mit je drei Argumenten eingeführt.
//Datei: Konto9/Konto.java
//Beispiel : Ueberladen
class Konto{
static int zaehler = 0;
protected int kontonummer;
protected double saldo;
Konto(int nummer){ // bisheriger Konstruktor mit 1 Parameter
}
zaehler = zaehler + 1;
kontonummer = nummer;
saldo = 0.00;
Konto(int nummer, double betrag){ // weiterer Konstruktor mit 2 Parametern
zaehler = zaehler + 1;
kontonummer = nummer;
saldo = betrag;
}
}
// die bereits bekannten Methoden wurde hier weggelassen
//Datei: Konto9/Sparkonto.java
//Beispiel : Ueberladen
class Sparkonto extends Konto{
private double zinssatz;
Sparkonto(int nummer, double zins){ // bisheriger Konstruktor mit 2 Parameter
super(nummer); // ruft 1−stelligen Konstruktor der Superklasse auf
}
zinssatz = zins;
Sparkonto(int nummer, double betrag, double zins){ // weiterer Konstruktor mit 3 Parametern
super(nummer, betrag); // ruft 2−stelligen Konstruktor der Superklasse auf
zinssatz = zins;
}
}
// die bereits bekannten Methoden wurde hier weggelassen
31
//Datei: Konto9/Girokonto.java
//Beispiel : Ueberladen
class Girokonto extends Konto{
private double limit;
Girokonto(int nummer, double limit){ // bisheriger Konstruktor mit 2 Parameter
super(nummer); // ruft 1−stelligen Konstruktor der Superklasse auf
if ( limit < 0)
limit = −limit;
this . limit = limit ;
}
Girokonto(int nummer, double betrag, double limit){ // weiterer Konstruktor mit 2 Parametern
super(nummer, betrag); // ruft 2−stelligen Konstruktor der Superklasse auf
if ( limit < 0)
limit = −limit;
this . limit = limit ;
}
}
// die bereits bekannten Methoden wurde hier weggelassen
Instanzen der einzelnen Klassen können jetzt wahlweise mit dem bisher bekannten Konstruktor erzeugt werden, der den Kontostand zu Beginn auf 0.00 setzt, oder man verwendet die
neuen Konstruktoren, die bereits Geld auf das Konto einzahlen. Dabei ist besonders bei Objekten der Klassen Sparkonto und Girokonto auf die Reihenfolge der Argumente zu achten. Zu
erst werden Kontonummer und gewünschter Kontostand angegeben, erst danach der Zinssatz
bzw. das Limit.
//Datei: Konto9/KontoTest.java
//Beispiel : Ueberladen
public class KontoTest{
public static void main(String[] args){
Konto meinKonto = new Konto(4531088);
meinKonto.einzahlen(300.00);
meinKonto.auszug();
Konto deinKonto = new Konto(1733065, 200.00);
deinKonto.auszug();
Sparkonto meinSparkonto = new Sparkonto(5613990, 1.25);
meinSparkonto.einzahlen(600.00);
meinSparkonto.auszug();
Sparkonto deinSparkonto = new Sparkonto(7835406, 500.00, 1.3);
deinSparkonto.auszug();
Girokonto meinGirokonto = new Girokonto(2571183, 400.00);
meinGirokonto.einzahlen(250.00);
meinGirokonto.auszug();
}
}
Girokonto deinGirokonto = new Girokonto(6464951, 600.00, 400.00);
deinGirokonto.auszug();
Oberon-2
In Oberon-2 dürfen Namen nicht mehrfach deklariert werden, so dass ein Überladen von
Methoden nicht möglich ist.
32
3 Beispiel: Mediendatenbank
11
In diesem Beispiel
soll eine Anwendung programmiert werden, die verschiedene Arten von
Medien verwalten kann. Eine solche Datenbank speichert Informationen über Bücher, CDs
und Videos, und gibt diese auf Anfrage aus.
Zunächst ist zu überlegen, welche Daten gespeichert werden. Für Bücher wollen wir Autor,
Titel, Verlag und das Erscheinungsjahr festhalten. Eine CD wird mit Titel, Name des Interpreten, Anzahl der Musiktitel und Gesamtspielzeit erfasst. Für Videos werden Regisseur,
Titel und die Länge des Films angegeben. Soll die Anwendung später als Bestandskatalog
einer Buchhandlung oder Bibliothek eingesetzt werden, so ist es sinnvoll, neben den bereits
genannten Daten, den Preis und einen Kommentar zu speichern. Auÿerdem sollte jedes Medium durch eine Artikelnummer eindeutig gekennzeichnet sein.
Es erscheint sinnvoll, die Daten eines Mediums als Einheit zu speichern. Hierzu werden in
der objektorientierten Programmierung Klassen genutzt. Es ist also nahe liegend, für jede
Medienart eine eigene Klasse zu entwickeln, in der alle relevanten Eigenschaften gesammelt
sind.
Damit die Daten nicht unberechtigt geändert werden, sind sie in der Klasse gekapselt. D.h. die
einzelnen Attribute sind nicht nach auÿen hin sichtbar und können nur über entsprechende
Methoden gelesen und geändert werden. Damit andere Programme in der Lage sind die Datenfelder auszulesen, wird für jedes Attribut eine get-Methode angelegt. Da sich die meisten
Attribute in unserem Beispiel später nicht mehr ändern, gibt es für sie keine Methoden, die
es ermöglichen die Attributwerte zu manipulieren. Lediglich der Preis und die Kommentare
können durch sogenannte set-Methoden abgeändert werden. Dies bedeutet aber auch, dass
alle Attributwerte im Konstruktor gesetzt werden müssen. Zusätzlich ist eine Methode druck-
eInfo() deniert, die die Details des jeweiligen Mediums ausgibt.
Obwohl die Klassen Buch, Cd und Video unterschiedliche Arten von Medien beschreiben, sind
sie sich sehr ähnlich. Alle drei Klasse besitzen die Attribute code, titel, preis und kommen-
tar. Auÿerdem verfügen alle über die zugehörigen get- und set-Methoden, die sogar identisch
zu implementieren sind. Diese oensichtliche Code-Duplizierung erhöht den Programmieraufwand unnötig und erschwert die Programmwartung, da mögliche Änderungen an mind. drei
Stellen vorgenommen werden müssen. Zudem ist es wünschenswert, dass andere Programme
nicht zwischen Büchern, CDs und Videos unterscheiden brauchen, sondern diese allgemein
als Medium betrachten können. Insbesondere dann, wenn das Angebot später um weitere
Medientypen erweitert werden soll.
Dieses Problem kann man durch Vererbung lösen. Anstatt die Klassen völlig unabhängig voneinander zu denieren, wird zuerst eine Klasse Medium beschrieben, die die Gemeinsamkeiten
der drei Klassen zusammenfasst. Die Klassen Buch, Cd und Video werden von dieser Klasse
abgleleitet und implementieren nur die typspezischen Eigenschaften von Büchern, CDs bzw.
Videos. Somit ergibt sich die in Abb. 7 dargestellte Klassenhierarchie. Der grundlegende Vorteil dieser Vorgehensweise ist, dass man gemeinsame Eigenschaften nur einmal beschreiben
11 Hierbei handelte es sich um eine veränderte Variante des Projektes DoME von D.J. Barnes und M. Kölling
[BK03, S.263 ].
33
muss.
Sobald die Klassen Medium, Buch, Cd und Video deniert sind, können Instanzen der Klassen
erzeugt werden. Abgesehen von diesen Klassen wird aber noch eine weitere Klasse benötigt,
die die Objekte verwaltet. Die Klasse Datenbank verfügt über ein Attribut medien, das die
einzelnen Medien-Objekte sammelt. Auÿerdem implementiert sie eine Methode zum Erfassen
neuer Objekte, die sowohl mit Objekten vom Typ Medium, als auch mit Instanzen aller Unterklassen von Medium arbeiten kann. Die Methode auisten(), gibt eine Liste aller Medien
auf der Konsole aus.
Abbildung 7: Klassendiagramm Mediendatenbank
Java
In der Klasse Medium werden alle Attribute deklariert, die verschiedene Medien gemein haben. Sie sind mit dem Schlüsselwort
protected
gekennzeichnet, um sie an die Subklassen zu
vererben. Zusätzlich zu den bereits genannten Attributen wird eine Klassenvariable zaehler
deniert, die zählt, wie viele Objekte erzeugt wurden.
Der zweistellige Konstruktor Medium(titel, preis) initialisiert die einzelnen Attribute. Er verwendet die Klassenvariable zaehler, um automatisch eine Artikelnummer zu erzeugen. Die
Werte von titel und preis werden, durch die Argumente des Konstruktors festgelegt und
der Kommentar wird zunächst mit einer leeren Zeichenkette initialisiert. Auÿerdem muss im
Konstruktor der Wert von zaehler erhöht werden. Auf diese Weise wird ein Instanzenzähler
implementiert, da Medeium(titel, preis) immer dann aufgerufen wird, wenn man ein neues
Objekt erzeugt.
34
Die get- und set-Methoden der Attribute sind mit dem Modizierer
public
gekennzeichnet,
so dass andere Probleme die Möglichkeit haben, sie zu verwenden.
Die Methode druckeInfo() gibt alle Datenfelder, die verschiedenartige Medien gemeinsam
haben auf die Konsole aus.
// Datei: Medien/Medium.java
public class Medium {
protected static int zaehler = 0;
protected int code ;
protected String titel;
protected double preis;
protected String kommentar;
public Medium(String titel, double preis){
this .zaehler += 1;
this .code = 100000 + this.zaehler;
this . titel = titel ;
this . preis = preis ;
this .kommentar = "";
}
public int getCode(){
return this.code;
}
public String getTitel(){
return this.titel ;
}
public double getPreis(){
return this.preis;
}
public void setPreis(double preis){
this . preis = preis ;
}
public String getKommentar(){
return this.kommentar;
}
public void setKommentar(String kommentar){
this .kommentar = kommentar;
}
public void druckeInfo(){
}
}
System.out.println("Code: "+this.code);
System.out.println("Titel: "+this. titel );
System.out.println("Preis: "+this.preis+" Euro");
System.out.println("Kommentar: "+this.kommentar);
System.out.println ();
Da die Klasse Buch von Medium abgeleitet wird, ist es nicht nötig, die die Attribute code,
titel, preis und kommentar erneut zu deklarieren. Genau wie die zugehörigen get- und setMethoden erbt Buch diese Attribute von der Superklasse. Es müssen somit nur die Attribute
autor, verlag und erscheinungsjahr, einschlieÿlich der jeweiligen get-Methoden, deniert werden.
Weiterhin implementiert Buch einen eigenen Konstruktor Buch(titel, autor, verlag, jahr,
preis). Dieser ruft zu nächst den Konstruktor der Superklasse Medium auf und übergibt
35
ihm die Argumente titel und autor. Durch diesen Aufruf werden die geerbten Attribute mit
Werten belegt und die Klassenvariable zaehler erhöht.
Von der Superklasse Medium erbt Buch auÿerdem die Methode druckeInfo(). Diese kann jedoch nicht alle Daten eines Buches ausgeben, da bspw. der Autor in der Superklasse nicht
bekannt ist. Die Methode druckeInfo() muss also in Buch mit einer anderen Funktionalität
implementiert werden. Die neue Implementierung überschreibt dann, die Variante der Klasse
Medium.
// Datei: Medien/Buch.java
public class Buch extends Medium{
protected String autor;
protected String verlag;
protected int erscheinungsjahr;
public Buch(String titel , String autor, String verlag , int jahr , double preis){
super(titel , preis );
this .autor = autor;
this .verlag = verlag;
this .erscheinungsjahr = jahr;
}
public String getAutor(){
return this.autor;
}
public String getVerlag(){
return this.verlag;
}
public int getErscheinungsjahr(){
return this.erscheinungsjahr;
}
public void druckeInfo(){
}
}
System.out.println("Code: "+this.code);
System.out.println("Titel: "+this. titel );
System.out.println("Autor: "+this.autor);
System.out.println("Verlag: "+this.verlag+", "+this.erscheinungsjahr);
System.out.println("Preis: "+this.preis+" Euro");
System.out.println("Kommentar: "+this.kommentar);
System.out.println ();
Für die Klassen Cd und Video gilt das Gleiche, wie für Buch. Sie werden von Medium abgeleitet und denieren lediglich weitere Attribute und Methoden, um die spezischen Eigenschaften von CDs bzw. Videos zu implementieren.
Die Konstruktoren Cd(titel, interpret, titelanz, zeit, preis) und Video(titel, reg, zeit, preis)
enthalten als ersten Befehl den Aufruf des Konstruktors der Klasse Medium. Dies bedeutet
insbesondere, dass die Klassenvariable zaehler alle Medienobjekte zählt unabhängig davon,
ob es sich um Bücher, CDs oder Videos handelt.
Genau wie in der Klasse Buch muss die Methode druckeInfo() in Cd und Video erneut implementiert werden. Da sich die einzelnen Implementierungen voneinander unterscheiden, kann
diese Aufgabe nicht komplett in die Superklasse verschoben werden.
36
// Datei: Medien/Cd.java
public class Cd extends Medium{
protected String interpret;
protected int titelanzahl;
protected int spieldauer;
public Cd(String titel , String interpret , int
super(titel , preis );
this . interpret = interpret ;
this . titelanzahl = titelanz ;
this .spieldauer = zeit ;
titelanz ,
int
zeit ,
double preis){
}
public String getInterpret(){
return this.interpret;
}
public int getTitelanzahl(){
return this.titelanzahl;
}
public int getSpieldauer(){
return this.spieldauer;
}
public void druckeInfo(){
}
}
System.out.println("Code: "+this.code);
System.out.println("Titel: "+this. titel );
System.out.println("Interpret: "+this.interpret );
System.out.println("Spieldauer: "+this.spieldauer+" min, "+this.titelanzahl+" Titel");
System.out.println("Preis: "+this.preis+" Euro");
System.out.println("Kommentar: "+this.kommentar);
System.out.println ();
// Datei: Medien/Video.java
public class Video extends Medium{
protected String regisseur;
protected int spieldauer;
public Video(String titel , String reg , int
super(titel , preis );
this . regisseur = reg;
this .spieldauer = zeit ;
zeit ,
double preis){
}
public String getRegisseur(){
return this.regisseur;
}
public int getSpieldauer(){
return this.spieldauer;
}
public void druckeInfo(){
}
}
System.out.println("Code: "+this.code);
System.out.println("Titel: "+this. titel );
System.out.println("Regisseur: "+this.regisseur);
System.out.println("Spieldauer: "+this.spieldauer+" min");
System.out.println("Preis: "+this.preis+" Euro");
System.out.println("Kommentar: "+this.kommentar);
System.out.println ();
37
Die Klasse Datenbank verwendet die Klasse Vector aus dem Paket java.util. Dieses Paket
muss zunächst durch eine
import -Anweisung
importiert werden. Vector implementiert ein
dynamisches Array, auf dessen Komponenten über einen ganzzahligen Index zugegrien werden kann. Bei der Deklaration der Variable medien wird in spitzen Klammern angegeben, von
welchem Typ die Komponenten des Vektors sind.
12
Der Konstruktor Datenbank() erzeugt ein Vector -Objekt, indem der Konstruktor Vector() der
Klasse Vector aufgerufen wird. Dabei muss erneut angegeben werden, dass die Komponenten
vom Typ Medium sein sollen. Der erzeugte Vektor ist zunächst leer.
Die Methode erfasseMedium(med) fügt neue Objekte in den Vektor medien ein. Dazu wird
die Methode add(object) aus der Klasse Vector auf medien angewendet. add(med) hängt das
Objekt med ans Ende des Vektors an. Auf diese Art und Weise werden Instanzen der Klasse
Medium gesammelt.
Die Methode auisten() durchläuft die Sammlung und ruft für jede Komponente die Methode
druckeInfo() auf. Zuvor muss das Element jedoch mittels get(i) geholt werden. Die Methoden
get(i) und size() sind in der Klasse Vector implementiert.
// Datei: Medien/Datenbank.java
import java.util.*;
public class Datenbank{
private Vector<Medium> medien;
public Datenbank(){
this .medien = new Vector<Medium>();
}
public void erfasseMedium(Medium med){
this .medien.add(med);
}
public void auisten(){
Medium med;
for ( int i=0; i < medien.size ();
med =this.medien.get(i);
}
}
}
i++){
med.druckeInfo();
In der Klasse Datenbank wird nur beschrieben, welche Eigenschaften und welches Verhalten
eine Datenbank haben kann. Die Datenbank selbst existiert also noch nicht. Deshalb wird im
Testprogramm zunächst eine Instanz der Klasse Datenbank erzeugt. Es ist möglich verschiedene Datenbanken anzulegen, dazu müssen lediglich weitere Instanzen der Klasse gebildet
werden.
In die Datenbank werden durch die Methode erfasseMedium(med) nacheinander verschiedene Medien eingefügt. Dabei fällt auf, dass die Methode auch Objekte vom Typ Buch, Cd
12 Die
Klasse
Vector steht in JDK 1.5 als generische Klasse zur Verfügung, d.h. sie kann mit verschiedenen
Arten von Objekten arbeiten. Dies vereinfacht die Programmierung, da wir den statischen Typ der Rückgabewerte der
Vector -Methoden festgelegen können. Bisher wurden Objekte als Instanzen der Klasse Object
übergeben und mussten explizit umgewandelt werden. Siehe auch [Mös98, S.91], [Cla98, S.26] oder vergleiche
[API1.4] und [API1.5]
38
oder Video verarbeiten kann. Dies ist möglich, da es sich um Subklassen der Klasse Medium
handelt. In der Deniton von erfasseMedium(med) ist med als Variable vom Typ Medium
deklariert. Durch diese Festlegung wird lediglich der statische Typ bestimmt. Zur Ausführungszeit kann med auch auf Objekte eines anderen Typs verweisen. Die Polymorphie von
13
Variablen reduziert den Programmieraufwand erheblich, da man sonst mehrere Versionen
der Methode erfasseMedium(med) implementieren müsste. Zudem könnten Bücher, CDs und
Videos nicht innerhalb eines Vektors verwaltet werden.
Schlieÿlich wird der Inhalt der Datenbank ausgegeben. Die Methode auisten() ruft für jedes Medium in der Datenbank die Methode druckInfo() auf. Welche Implementierung dieser
Methode ausgeführt wird, legt der Übersetzer - in Abhängigkeit vom dynamischen Typ der
Variable - erst zur Laufzeit fest.
// Datei: Medien/TestMedienDB.java
public class TestMedienDB{
public static void main(String[] args){
Datenbank db = new Datenbank();
db.erfasseMedium(new Medium("Herr der Ringe", 11.00));
db.erfasseMedium(new Buch("Sprechen Sie Java","Hanspeter Moessenboeck", "dpunkt.verlag", 2001, 28.00));
db.erfasseMedium(new Cd("No Angel","Dido",13,90,15.00));
db.erfasseMedium(new Video("X−Men","Bryan Singer",100,21.00));
}
}
db. auisten ();
Oberon-2
Die Anwendung Mediendatenbank lässt sich mit einigen Anpassungen auch in Oberon-2 umsetzen.
Für jede Klasse wird ein eigenes Modul geschrieben, in dem die Datenfelder in einem
CORD
RE-
deklariert werden. Die instanzspezischen Methoden werden als typgebundene Pro-
zeduren implementiert.
Da Oberon-2 nicht über einen eigenen Datentyp für Zeichenketten verfügt, werden die Attribute titel, kommentar, autor usw. als
ARRAY 100 OF CHAR deklariert. Dies bedeutet
jedoch, dass die Einträge maximal 100 Zeichen lang sein dürfen. Für die Wertzuweisung wird
die vordenierte Prozedur
COPY(s,
t) genutzt. Diese Prozedur kopiert den Wert des Zei-
chenfeldes s in das Feld t.
Im Modul Medium wird als erste Prozedur eine Klassenmethode NEWMedium(titel, preis)
implementiert. Diese Methode ersetzt den in anderen objektorientierten Programmiersprachen üblichen Konstruktor. Anders als im Beispiel Konten ist der
NEW -Operator diesmal
in der Konstruktormethode enthalten, d.h. die Methode erzeugt und initialisiert die Objekte.
Anschlieÿend werden die Objekte als Rückgabewert an die aufrufende Stelle zurückgeben. Das
bedeutet, dass eine Variable des passenden Referenztyps das Objekt entgegennehmen muss.
Die Klassen Buch, Cd und Video besitzen mit NEWBuch(titel, autor, verlag, jahr, preis),
NEWCd(titel, interpret, titelanz, zeit, preis) bzw. NEWVideo(Titel, reg, zeit, preis) ebenfalls
eine Klassenmethode, die den Konstruktor ersetzt.
13 erfasseBuch(buch), erfasseCd(cd)
und
erfasseVideo(video)
39
Anders als in Java sind die Methoden getTitel(titel), getKommentar(kommentar) u.a. nicht
als Funktionen implementiert. Da Arrays nicht Rückgabewert einer Funktion sein können,
wird das Ergebnis an einen Referenzparameter übergeben.
(* Datei: Medien/Medium.mod *)
MODULE Medium;
IMPORT Out;
TYPE
Medium* = RECORD
code * : LONGINT;
titel * : ARRAY 100 OF CHAR;
preis * : REAL;
kommentar* : ARRAY 100 OF CHAR;
END;
MediumRef* = POINTER TO Medium;
VAR zaehler* : INTEGER;
PROCEDURE NEWMedium*(titel : ARRAY 100 OF CHAR; preis : REAL) : MediumRef;
VAR tmp : MediumRef;
BEGIN
NEW(tmp);
INC(zaehler);
tmp.code := 100000 + zaehler;
COPY(titel, tmp.titel);
tmp.preis := preis ;
tmp.kommentar := "";
RETURN tmp;
END NEWMedium;
PROCEDURE (m : MediumRef) getCode*() : LONGINT;
BEGIN
RETURN m.code;
END getCode;
PROCEDURE (m : MediumRef) getTitel*(VAR titel : ARRAY 100 OF CHAR) ;
BEGIN
COPY(m.titel, titel);
END getTitel;
PROCEDURE (m : MediumRef) getPreis*() : REAL;
BEGIN
RETURN m.preis;
END getPreis;
PROCEDURE (m : MediumRef) setPreis*(preis : REAL);
BEGIN
m.preis := preis ;
END setPreis;
PROCEDURE (m : MediumRef) getKommentar*(VAR kommentar : ARRAY 100 OF CHAR);
BEGIN
COPY(m.kommentar, kommentar);
END getKommentar;
PROCEDURE (m : MediumRef) setKommentar*(kommentar : ARRAY 100 OF CHAR);
BEGIN
COPY(kommentar, m.kommentar);
END setKommentar;
PROCEDURE (m : MediumRef) druckeInfo*();
BEGIN
Out.String("Code: "); Out.Int(m.code, 7); Out.Ln;
Out.String("Titel: " ); Out.String(m.titel ); Out.Ln;
Out.String("Preis: " ); Out.Real(m.preis , 5); Out.String(" Euro"); Out.Ln;
40
Out.String("Kommentar: "); Out.String(m.kommentar); Out.Ln;
Out.Ln;
END druckeInfo;
BEGIN
zaehler := 0;
END Medium.
(* Datei: Medien/Buch.mod *)
MODULE Buch;
IMPORT Out, Medium;
TYPE
Buch* = RECORD (Medium.Medium)
autor : ARRAY 100 OF CHAR;
verlag : ARRAY 100 OF CHAR;
erscheinungsjahr : INTEGER;
END;
BuchRef* = POINTER TO Buch;
PROCEDURE NEWBuch*(titel, autor, verlag : ARRAY 100 OF CHAR; jahr : INTEGER; preis : REAL) : BuchRef;
VAR tmp : BuchRef;
BEGIN
NEW(tmp);
INC(Medium.zaehler);
tmp.code := 100000 + Medium.zaehler;
COPY(titel, tmp.titel);
COPY(autor, tmp.autor);
COPY(verlag, tmp.verlag);
tmp.erscheinungsjahr := jahr;
tmp.preis := preis ;
tmp.kommentar := "";
RETURN tmp;
END NEWBuch;
PROCEDURE (b : BuchRef) getAutor*(VAR autor : ARRAY 100 OF CHAR);
BEGIN
COPY(b.autor, autor);
END getAutor;
PROCEDURE (b : BuchRef) getVerlag*(VAR verlag : ARRAY 100 OF CHAR);
BEGIN
COPY(b.verlag, verlag);
END getVerlag;
PROCEDURE (b : BuchRef) getErscheinungsjahr*() : INTEGER;
BEGIN
RETURN b.erscheinungsjahr;
END getErscheinungsjahr;
PROCEDURE (b : BuchRef) druckeInfo*();
BEGIN
Out.String("Code: "); Out.Int(b.code , 7); Out.Ln;
Out.String("Titel: " ); Out.String(b. titel ); Out.Ln;
Out.String("Autor: "); Out.String(b.autor); Out.Ln;
Out.String("Verlag: "); Out.String(b.verlag ); Out.String(", " ); Out.Int(b.erscheinungsjahr ,4); Out.Ln;
Out.String("Preis: " ); Out.Real(b.preis , 5); Out.String(" Euro"); Out.Ln;
Out.String("Kommentar: "); Out.String(b.kommentar); Out.Ln;
Out.Ln;
END druckeInfo;
BEGIN
END Buch.
41
(* Datei: Medien/Cd.mod *)
MODULE Cd;
IMPORT Out, Medium;
TYPE
Cd* = RECORD (Medium.Medium)
interpret : ARRAY 100 OF CHAR;
titelanzahl : INTEGER;
spieldauer : INTEGER;
END;
CdRef* = POINTER TO Cd;
PROCEDURE NEWCd*(titel, interpret : ARRAY 100 OF CHAR; titelanz, zeit : INTEGER; preis : REAL) : CdRef;
VAR tmp : CdRef;
BEGIN
NEW(tmp);
INC(Medium.zaehler);
tmp.code := 100000 + Medium.zaehler;
COPY(titel, tmp.titel);
COPY(interpret, tmp.interpret);
tmp.titelanzahl := titelanz ;
tmp.spieldauer := zeit ;
tmp.preis := preis ;
tmp.kommentar := "";
RETURN tmp;
END NEWCd;
PROCEDURE (c : CdRef) getInterpret*(VAR interpret : ARRAY 100 OF CHAR);
BEGIN
COPY(c.interpret, interpret);
END getInterpret;
PROCEDURE (c : CdRef) getTitelanzahl*() : INTEGER;
BEGIN
RETURN c.titelanzahl;
END getTitelanzahl;
PROCEDURE (c : CdRef) getSpieldauer*() : INTEGER;
BEGIN
RETURN c.spieldauer;
END getSpieldauer;
PROCEDURE (c : CdRef) druckeInfo*();
BEGIN
Out.String("Code: "); Out.Int(c.code , 7); Out.Ln;
Out.String("Titel: " ); Out.String(c. titel ); Out.Ln;
Out.String("Interpret: " ); Out.String(c. interpret ); Out.Ln;
Out.String("Spieldauer: "); Out.Int(c.spieldauer , 3); Out.String(" min, ");
Out.Int(c. titelanzahl ,3); Out.String(" Titel"); Out.Ln;
Out.String("Preis: " ); Out.Real(c.preis , 5); Out.String(" Euro"); Out.Ln;
Out.String("Kommentar: "); Out.String(c.kommentar); Out.Ln;
Out.Ln;
END druckeInfo;
BEGIN
END Cd.
(* Datei: Medien/Video.mod *)
MODULE Video;
IMPORT Out, Medium;
TYPE
Video* = RECORD (Medium.Medium)
regisseur : ARRAY 100 OF CHAR;
spieldauer : INTEGER;
END;
42
VideoRef* = POINTER TO Video;
PROCEDURE NEWVideo*(titel, regisseur : ARRAY 100 OF CHAR; zeit : INTEGER; preis : REAL) : VideoRef;
VAR tmp : VideoRef;
BEGIN
NEW(tmp);
INC(Medium.zaehler);
tmp.code := 100000 + Medium.zaehler;
COPY(titel, tmp.titel);
COPY(regisseur, tmp.regisseur);
tmp.spieldauer := zeit ;
tmp.preis := preis ;
tmp.kommentar := "";
RETURN tmp;
END NEWVideo;
PROCEDURE (v : VideoRef) getRegisseur*(VAR reg : ARRAY 100 OF CHAR);
BEGIN
COPY(v.regisseur, reg);
END getRegisseur;
PROCEDURE (v : VideoRef) getSpieldauer*() : INTEGER;
BEGIN
RETURN v.spieldauer;
END getSpieldauer;
PROCEDURE (v : VideoRef) druckeInfo*();
BEGIN
Out.String("Code: "); Out.Int(v.code , 7); Out.Ln;
Out.String("Titel: " ); Out.String(v. titel ); Out.Ln;
Out.String("Regisseur: "); Out.String(v. regisseur ); Out.Ln;
Out.String("Spieldauer: "); Out.Int(v.spieldauer ,3); Out.String(" min"); Out.Ln;
Out.String("Preis: " ); Out.Real(v.preis , 5); Out.String(" Euro"); Out.Ln;
Out.String("Kommentar: "); Out.String(v.kommentar); Out.Ln;
Out.Ln;
END druckeInfo;
BEGIN
END Video.
Oberon-2 verfügt zwar über sogenannte oene Arrays, diese dürfen aber nicht innerhalb einer
RECORD -Deklaration
verwendet werden. Deshalb musste im Modul Datenbank auf ein
statisches Array des Gröÿe 100 zurückgegrien werden, dass auf Objekte vom Typ Medium
verweist. Somit kann jede Datenbank maximal 100 Einträge speichern. Damit die Methode
auisten() nur den Teil des Arrays durchläuft, der tatsächlich verwendet wird, speichert das
Attribut length die Länge des belegten Abschnitts. Nur das Modul Datenbank benutzt dieses
Attribut, weshalb es nicht exportiert wird.
Die Methode NEWDatenbank() erzeugt zunächst eine Instanz der Klasse Datenbank und
speichert einen Adressverweis auf dieses Objekt in einer temporären Variable. Die Felder des
Arrays werden auf den Wert NIL gesetzt, d.h. sie verweisen auf kein Objekt. Der Wert des
Attributs length wird mit 0 initialisiert. Anschlieÿend wird das erzeugte Objekt an die Stelle
des Funktionsaufrufes zurückgegeben.
Durch die Methode erfasseMedium(med) wird das Objekt, dass med referenziert dem nächsten
freien Feld von medien zugewiesen und length um 1 erhöht. Ist kein freies Feld verfügbar, gibt
die Methode eine Fehlermeldung aus.
43
(* Datei: Medien/Datenbank.mod *)
MODULE Datenbank;
IMPORT Out, Medium;
TYPE
Datenbank* = RECORD
medien* : ARRAY 100 OF Medium.MediumRef;
length : INTEGER;
END;
DatenbankRef* = POINTER TO Datenbank;
PROCEDURE NEWDatenbank*() : DatenbankRef;
VAR tmp : DatenbankRef;
i : INTEGER;
BEGIN
NEW(tmp);
FOR i:= 0 TO 99 DO
tmp.medien[i] := NIL;
END;
tmp.length := 0;
RETURN tmp;
END NEWDatenbank;
PROCEDURE (db : DatenbankRef) erfasseMedium*(med : Medium.MediumRef);
BEGIN
IF db.length < 100 THEN
db.medien[db.length] := med;
INC(db.length);
ELSE
Out.String("Datenbank ist voll, Medium öknnte nicht eingefuegt werden.");
END;
END erfasseMedium;
PROCEDURE (db : DatenbankRef) auisten*();
VAR i : INTEGER;
BEGIN
FOR i:= 0 TO db.length−1 DO
db.medien[i ]. druckeInfo();
END;
END auisten;
BEGIN
END Datenbank.
Das Testprogramm importiert alle Module, die es verwendet, da sonst die darin denierten
Namen nicht bekannt sind. Um die Komponenten eines Moduls zu nutzen, muss der Name dieses Moduls immer mit angegeben sein. Dies bedeutet insbesondere, dass die Klassenmethoden
NEWDatenbank(), NEWMedium(titel, preis) usw. nur über den Modulnamen angesprochen
werden können. Typgebundene Prozeduren, wie z.B. erfasseMedium(med), sind an ein Objekt der Klasse gebunden. Deshalb kann der Aufruf über den zugehörigen Variablennamen
erfolgen.
(* Datei: Medien/TestMedienDB.mod *)
MODULE TestMedienDB;
IMPORT Out, Datenbank, Medium, Buch, Cd, Video;
PROCEDURE ProgMain*();
VAR db : Datenbank.DatenbankRef;
BEGIN
44
db := Datenbank.NEWDatenbank();
db.erfasseMedium(Medium.NEWMedium("Herr der Ringe", 11.00));
db.erfasseMedium(Buch.NEWBuch("OPP mit Oberon−2","Hanspeter Moessenboeck","dpunkt.verlag", 2001, 28.00));
db.erfasseMedium(Cd.NEWCd("No Angel", "Dido", 13, 90, 15.00));
db.erfasseMedium(Video.NEWVideo("X−Men", "Bryan Singer", 100, 21.00));
db. auisten ();
END ProgMain;
BEGIN
Out.Open;
END TestMedienDB.
45
4 Beispiel: geometrische Figuren
Das letzte Beispiel stellt verschiedene Methoden zur Manipulation von geometrischen Figuren
zur Verfügung. Für jede Art von Figur kann man die Fläche berechnen, die Figur verschieben oder strecken und sie zeichnen. Die Aufgaben sind für alle Figuren gleich, jedoch ist die
korrekte Implementierung einiger Methoden von der jeweiligen Figur abhängig. Jede Figur
benötigt somit eine eigene Klasse, in der charakteristische Eigenschaften und das zugehörige
Verhalten beschrieben sind. Damit Programme, die Objekte dieser Klassen benutzten, nicht
zwischen den unterschiedlichen Varianten unterscheiden müssen wird eine allgemeine Klasse Figur eingeführt. Die konkreten Figuren Rechteck, Quadrat und Kreis werden aus dieser
Klasse abgeleitet.
Bleibt die Frage, welche Komponenten in der Superklasse zu denieren sind. Ein ebenes Rechteck wird durch einen Punkt, sowie Höhe und Breite eindeutig bestimmt. Für Quadrat und
Kreis genügen ein Punkt und die Höhe bzw. der Radius als Angaben. Da jeder Figurtyp einen
Referenzpunkt zur Bestimmung der Lage benötigt, scheint es sinnvoll die x- und y-Koordinate
dieses Punktes als Attribute in Figur einzuführen. Die notwendigen Maÿangaben sind bei den
einzelnen Figuren unterschiedlich und gehören deshalb in die zugehörigen Subklassen.
Damit die Methoden getFlaeche(), verschieben(dx, dy) , strecken(faktor) und zeichnen() für
eine Variable vom Typ Figur aufgerufen werden können, müssen sie in der Klasse Figur deklariert sein. Dies liegt daran, dass zur Übersetzungszeit der statische Typ der Variable
14
festlegt,
welche Methoden ausführbar sind. Die Methode verschieben(dx, dy) kann leicht implementiert
werden. Alle anderen bereiten jedoch Probleme, da sie auf Längenangaben angewiesen sind,
die in Figur nicht zur Verfügung stehen.
Zur Lösung dieses Problems gibt es in objektorientierten Sprachen die Möglichkeit, abstrakte
Methoden ohne Anweisungsblock zu denieren. Eine Klasse, die abstrakte Methoden enthält
ist damit automatisch eine abstrakte Klasse. Von solchen Klassen dürfen keine Instanzen erzeugt werden. Sie dienen ausschlieÿlich als Superklasse für andere Klassen.
Die Klasse Figur wird somit als abstrakte Klasse implementiert. Die abstrakten Methoden
getFlaeche(), strecken(faktor) und zeichnen() werden in den Klassen Rechteck, Quadrat und
Kreis überschrieben und erhalten so ihre Funktionalität. Insgesamt ergibt sich die in Abb. 8
dargestellte Klassenhierarchie.
Java
Abstrakte Klassen und Methoden sind in Java mit dem Schlüsselwort
abstract
gekennzeich-
net. Sobald eine Klasse eine abstrakte Methode enthält, muss auch die Klasse als abstrakt
deniert werden.
Um zu verhindern, dass andere Programme versuchen eine Instanz der Klasse Figur zu erzeugen, trägt der Konstruktor den Modizierer
protected. Er ist somit nur in den Subklassen
von Figur sichtbar.
Neben den bereits genannten Methoden zur Manipulation, wurden weitere Methoden er-
14 Siehe
auch Abschnitt 2.8 über Polymorphismus
46
Abbildung 8: Klassendiagramm geometische Figuren
gänzt, die Informationen über das Objekt liefern. Anhand dieser Methoden wird das Prinzip
des Information Hiding noch einmal deutlich. Die Attribute können nicht direkt angesprochen
werden. Um die Informationen trotzdem verfügbar zu machen, sind spezielle get-Methoden
implementiert. Diese Methoden geben die Attributwerte jedoch nicht als Integer oder Double
zurück, sondern als String. Änderung des Datentyps der Attribute wirken sich deshalb nicht
auf den Anwender aus.
//Datei: Figuren/Figur.java
import java.awt.*;
public abstract class Figur{
protected int xpos, ypos;
protected Figur(int x, int y){
this .xpos = x;
this .ypos = y;
}
public abstract String getTyp();
public String getPosition(){
return "("+this.xpos+";"+this.ypos+") ";
}
public abstract String getLaengen();
public abstract double getFlaeche();
public void verscheiben(int dx, int dy){
this .xpos += dx;
this .ypos += dy;
}
47
}
public abstract void strecken(double faktor);
public abstract void zeichnen(Graphics g);
Die Klassen Rechteck, Quadrat und Kreis deklarieren die spezischen Attribute der Figurtypen und implementieren die abstrakten Methoden der Klasse Figur. Die Methoden getPosi-
ton() und verschieben(dx, dy) können unverändert von der Superklasse geerbt werden.
Zum Zeichnen der Figuren wurden Methoden der Klasse Graphics aus den Paket java.awt
genutzt.
15
//Datei: Figuren/Rechteck.java
import java.awt.*;
public class Rechteck extends Figur{
protected int breite, hoehe;
public Rechteck(int x, int y, int breite , int hoehe){
super(x,y);
this . breite = breite ;
this .hoehe = hoehe;
}
public String getTyp(){
return "Rechteck";
}
public String getLaengen(){
return "Breite = "+this.breite+" Hoehe = "+this.hoehe;
}
public double getFlaeche(){
return this.breite*this.hoehe;
}
public void strecken(double faktor){
this . breite *= faktor;
this .hoehe *= faktor;
}
public void zeichnen(Graphics g){
g.drawRect(this.xpos, this.ypos, (int)this . breite , ( int)this .hoehe);
}
}
//Datei: Figuren/Quadrat.java
import java.awt.*;
public class Quadrat extends Figur{
protected double hoehe;
public Quadrat(int x, int y, double hoehe){
super(x,y);
this .hoehe = hoehe;
}
public String getTyp(){
return "Quadrat";
}
15 Siehe
[Abt02, S. 205 ] oder [API1.5]
48
public String getLaengen(){
return "Hoehe = "+this.hoehe;
}
public double getFlaeche(){
return this.hoehe*this.hoehe;
}
public void strecken(double faktor){
this .hoehe *= faktor;
}
public void zeichnen(Graphics g){
g.drawRect(this.xpos, this.ypos, (int)this .hoehe, ( int)this .hoehe);
}
}
//Datei: Figuren/Kreis.java
import java.awt.*;
public class Kreis extends Figur{
protected double radius;
public Kreis(int x, int y, double radius){
super(x,y);
this .radius = radius;
}
public String getTyp(){
return "Kreis";
}
public String getLaengen(){
return "Radius = "+this.radius;
}
public double getFlaeche(){
return 3.14*this.radius*this.radius;
}
public void strecken(double faktor){
this .radius *= faktor;
}
public void zeichnen(Graphics g){
g.drawOval(this.xpos−(int)this.radius, this.ypos−(int)this.radius, (int)(2*this.radius ), ( int)(2*this .radius ));
}
}
Im ersten Testprogramm werden Instanzen der Klassen Quadrat und Kreis erzeugt und in Variablen des jeweiligen Typs gespeichert. Auf diese Objekte können die deklarierten Methoden
wie gewohnt angewendet werden.
//Datei: Figuren/TestFigur1.java
import java.awt.*;
import java.awt.event.*;
public class TestFigur1 extends Frame{
// die Methode paint wird automatisch üausgefhrt, wenn eine grasche Komponente gezeichnet wird
public void paint (Graphics g){
Quadrat testQ = new Quadrat(100,100,10);
Kreis testK = new Kreis(500,200,80);
49
for ( int i = 0; i < 18; i++) {
}
}
testQ.zeichnen(g);
testQ.strecken (1.2);
testK.zeichnen(g);
testK.verscheiben (10,0);
// folgende Programmteile werden zum Zeichnen benoetigt
// Konstruktor initialisiert das Fenster , in dem gezeichnet wird
public TestFigur1(){
super("Figuren");
setVisible (true);
setSize (1000,600);
addWindowListener(new WindowAdapter() {
public void windowClosing ( WindowEvent e) {
System.exit(0);
}
});
}
// im Hauptprogramm wird ein Fenster erzeugt
public static void main(String args[]){
TestFigur1 frame = new TestFigur1();
}
}
Die Testprogramme TestFigur2 und TestFigur3 nutzen aus, dass Variablen vom Typ der Superklasse auf Objekte der Subklassen verweisen können. Beide Programme benötigen deshalb
nur eine einzige Variable, die verschiedene Figur-Objekte in einem Array speichert. Für die
einzelnen Komponenten des Arrays können die in Figur denierten Methoden unabhängig
von der Art des Objektes aufgerufen werden. Welche Implementierung tatsächlich ausgeführt
wird, entscheidet sich erst zur Laufzeit.
//Datei: Figuren/TestFigur2.java
public class TestFigur2{
public static void main(String args[]){
Figur [] test = new Figur[3];
test [0] = new Rechteck(100,100,40,60);
test [1] = new Quadrat(200,200,40);
test [2] = new Kreis(300,300,50);
for ( int i=0; i<test.length ; i++){
System.out.print(test [ i ]. getTyp()+" −−> ");
System.out.print(test [ i ]. getPosition ());
System.out.print(test [ i ]. getLaengen());
System.out.println(" Flaeche: "+test[i ]. getFlaeche());
test [ i ]. strecken (1.5);
test [ i ]. verscheiben(10,−5);
}
}
}
System.out.print(test [ i ]. getTyp()+" −−> ");
System.out.print(test [ i ]. getPosition ());
System.out.print(test [ i ]. getLaengen());
System.out.println(" Flaeche: "+test[i ]. getFlaeche());
System.out.println ();
50
//Datei: Figuren/TestFigur3.java
import java.awt.*;
import java.awt.event.*;
public class TestFigur3 extends Frame{
public void paint (Graphics g){
Figur [] test = new Figur[6];
test [0] = new Rechteck(200,100,110,200);
test [1] = new Quadrat(225,125,60);
test [2] = new Rechteck(310,200,200,100);
test [3] = new Rechteck(450,150,30,50);
test [4] = new Kreis(280,300,50);
test [5] = new Kreis(430,300,50);
for ( int i=0; i<test.length ; i++){
}
}
test [ i ]. zeichnen(g);
public TestFigur3(){
super("Figuren"); // Titel des Fensters
setVisible (true); // Sichtbarkeit des Fensters
}
setSize (800,500); // Groesse des Fensters
// aktiviert Button zum Schliessen des Fenstern
addWindowListener(new WindowAdapter() {
public void windowClosing ( WindowEvent e) {
System.exit(0);
}
});
public static void main(String args[]){
TestFigur3 frame = new TestFigur3();
}
}
Oberon-2
In Oberon-2 existiert kein Schlüsselwort, um abstrakte Methoden zu kennzeichnen. Sie werden stattdessen mit einem leeren Methodenrumpf deklariert. Wenn eine solche Methode zur
Ausführung gelangt, geschieht einfach nichts. Deshalb ist es besonders wichtig, dass alle abstrakten Methoden in Subklassen überschrieben werden. Dies erfordert besondere Aufmerksamkeit vom Programmierer, da der Compiler abstrakte Methoden, die nicht überschrieben
wurden, nicht als Fehler meldet.
Für Figur ist kein Konstruktor deniert, um zu verhindern, dass ein Anwender eine Instanz
der Klasse anlegt. Mittels
NEW
ist es zwar weiterhin möglich, sollte jedoch vermieden wer-
den, da die Klasse kleine vollständige Funktionalität zur Verfügung stellt.
(* Datei: Figuren/Figur.mod *)
MODULE Figur;
IMPORT Out;
TYPE
Figur* = RECORD
xpos*, ypos * : INTEGER;
END;
FigurRef* = POINTER TO Figur;
51
PROCEDURE (f : FigurRef) printTyp*();
END printTyp;
PROCEDURE (f : FigurRef) printPosition*();
BEGIN
Out.String(" −−> (");
Out.Int(f .xpos,4);
Out.String(";");
Out.Int(f .ypos,4);
Out.String(") ");
END printPosition;
PROCEDURE (f: FigurRef) printLaengen*();
END printLaengen;
PROCEDURE (f : FigurRef) printFlaeche*();
END printFlaeche;
PROCEDURE (f : FigurRef) verschieben*(dx, dy : INTEGER);
BEGIN
INC(f.xpos, dx);
INC(f.ypos, dy);
END verschieben;
PROCEDURE (f : FigurRef) strecken*(faktor : REAL);
END strecken;
PROCEDURE (f : FigurRef) zeichnen*();
END zeichnen;
BEGIN
END Figur.
Die get-Methoden der Klassen Figur, Rechteck, Quadrat und Kreis sind in Oberon-2 als printMethoden implementiert, d.h. die Daten werden von der Methode direkt auf die Konsole
ausgegeben. Für unsere Zwecke genügte diese Variante, und man umgeht die umständliche
Verwendung von Zeichenfeldern.
Zum Zeichnen der Figuren verwendet zeichnen() Befehle einer Turtle-Grak, die im Modul
16
MKTurtleLight
deniert sind.
(* Datei: Figuren/Rechteck.mod *)
MODULE Rechteck;
IMPORT Out, Figur, MKTurtleLight;
TYPE
Rechteck* = RECORD (Figur.Figur)
breite * , hoehe * : REAL;
END;
RechteckRef* = POINTER TO Rechteck;
PROCEDURE NEWRechteck*(x, y, breite, hoehe : INTEGER) : RechteckRef ;
VAR tmp : RechteckRef;
BEGIN
NEW(tmp);
tmp.xpos := x;
tmp.ypos := y;
tmp.breite := breite ;
tmp.hoehe := hoehe;
RETURN tmp;
END NEWRechteck;
16 Das
Modul MKTurtleLight stammt von M. Kühn und ist unter www.???.de kostenfrei erhältlich.
52
PROCEDURE (r : RechteckRef) printTyp*();
BEGIN
Out.String("Rechteck");
END printTyp;
PROCEDURE (r : RechteckRef) printLaengen*();
BEGIN
Out.String(" Breite = ");
Out.Real(r.breite ,5);
Out.String(" Hoehe = ");
Out.Real(r.hoehe,5);
END printLaengen;
PROCEDURE (r : RechteckRef) printFlaeche*();
BEGIN
Out.String(" Flaeche = ");
Out.Real(r.breite *r.hoehe,10);
END printFlaeche;
PROCEDURE (r : RechteckRef) strecken*(faktor : REAL);
BEGIN
r . breite := r . breite *faktor;
r .hoehe := r.hoehe*faktor;
END strecken;
PROCEDURE (r : RechteckRef) zeichnen*();
VAR i : INTEGER;
BEGIN
(* Gehe zum Punkt (r.xpos; r.ypos) *)
MKTurtleLight.StiftHoch;
MKTurtleLight.Gehe(r.xpos);
MKTurtleLight.Drehe(90);
MKTurtleLight.Gehe(r.ypos);
MKTurtleLight.Drehe(−90);
MKTurtleLight.StiftRunter;
(* Zeichne Rechteck *)
MKTurtleLight.Gehe(r.breite);
MKTurtleLight.Drehe(−90);
MKTurtleLight.Gehe(r.hoehe);
MKTurtleLight.Drehe(−90);
MKTurtleLight.Gehe(r.breite);
MKTurtleLight.Drehe(−90);
MKTurtleLight.Gehe(r.hoehe);
MKTurtleLight.Drehe(−90);
(* Gehe zum Koordinatenursprung *)
MKTurtleLight.StiftHoch;
MKTurtleLight.Drehe(−90);
MKTurtleLight.Gehe(r.ypos);
MKTurtleLight.Drehe(−90);
MKTurtleLight.Gehe(r.xpos);
MKTurtleLight.Drehe(180);
MKTurtleLight.StiftRunter;
END zeichnen;
BEGIN
END Rechteck.
53
(* Datei: Figuren/Quadrat.mod *)
MODULE Quadrat;
IMPORT Out, Figur, MKTurtleLight;
TYPE
Quadrat* = RECORD (Figur.Figur)
hoehe * : REAL;
END;
QuadratRef* = POINTER TO Quadrat;
PROCEDURE NEWQuadrat*(x, y, hoehe : INTEGER) : QuadratRef;
VAR tmp : QuadratRef;
BEGIN
NEW(tmp);
tmp.xpos := x;
tmp.ypos := y;
tmp.hoehe := hoehe;
RETURN tmp;
END NEWQuadrat;
PROCEDURE (q : QuadratRef) printTyp*();
BEGIN
Out.String("Quadrat");
END printTyp;
PROCEDURE (q : QuadratRef) printLaengen*();
BEGIN
Out.String(" Hoehe = ");
Out.Real(q.hoehe,5);
END printLaengen;
PROCEDURE (q : QuadratRef) printFlaeche*();
BEGIN
Out.String(" Flaeche = ");
Out.Real(q.hoehe*q.hoehe, 10);
END printFlaeche;
PROCEDURE (q : QuadratRef) strecken*(faktor : REAL);
BEGIN
q.hoehe := q.hoehe*faktor;
END strecken;
PROCEDURE (q : QuadratRef) zeichnen*();
VAR i : INTEGER;
BEGIN
(* Gehe zum Punkt (q.xpos; q.ypos) *)
MKTurtleLight.StiftHoch;
MKTurtleLight.Gehe(q.xpos);
MKTurtleLight.Drehe(90);
MKTurtleLight.Gehe(q.ypos);
MKTurtleLight.Drehe(−90);
MKTurtleLight.StiftRunter;
(* Zeichne Quadrat *)
FOR i:= 1 TO 4 DO
MKTurtleLight.Gehe(q.hoehe);
MKTurtleLight.Drehe(−90);
END;
(* Gehe zum Koordinatenursprung *)
MKTurtleLight.StiftHoch;
MKTurtleLight.Drehe(−90);
MKTurtleLight.Gehe(q.ypos);
MKTurtleLight.Drehe(−90);
MKTurtleLight.Gehe(q.xpos);
MKTurtleLight.Drehe(180);
MKTurtleLight.StiftRunter;
END zeichnen;
BEGIN
END Quadrat.
54
(* Datei: Figuren/Kreis.mod *)
MODULE Kreis;
IMPORT Out, Figur, MKTurtleLight;
TYPE
Kreis* = RECORD (Figur.Figur)
radius * : REAL;
END;
KreisRef* = POINTER TO Kreis;
PROCEDURE NEWKreis*(x, y, radius : INTEGER) : KreisRef ;
VAR tmp : KreisRef;
BEGIN
NEW(tmp);
tmp.xpos := x;
tmp.ypos := y;
tmp.radius := radius;
RETURN tmp;
END NEWKreis;
PROCEDURE (k : KreisRef) printTyp*();
BEGIN
Out.String("Kreis");
END printTyp;
PROCEDURE (k : KreisRef) printLaengen*();
BEGIN
Out.String(" Radius = ");
Out.Real(k.radius,5);
END printLaengen;
PROCEDURE (k : KreisRef) printFlaeche*();
BEGIN
Out.String(" Flaeche = ");
Out.Real(3.14*k.radius*k.radius , 10);
END printFlaeche;
PROCEDURE (k : KreisRef) strecken*(faktor : REAL);
BEGIN
k.radius := k.radius*faktor;
END strecken;
PROCEDURE (k : KreisRef) zeichnen*();
VAR i : INTEGER;
schritt : REAL;
BEGIN
(* Gehe zum Punkt (k.xpos; k.ypos) *)
MKTurtleLight.StiftHoch;
MKTurtleLight.Gehe(k.xpos+k.radius);
MKTurtleLight.Drehe(90);
MKTurtleLight.Gehe(k.ypos);
MKTurtleLight.StiftRunter;
(* Zeichne Kreis *)
schritt := (3.14* k.radius)/180;
FOR i:= 1 TO 360 DO
MKTurtleLight.Gehe(schritt);
MKTurtleLight.Drehe(1);
END;
(* Gehe zum Koordinatenursprung *)
MKTurtleLight.StiftHoch;
MKTurtleLight.Drehe(90);
MKTurtleLight.Gehe(k.radius+k.xpos);
MKTurtleLight.Drehe(90);
MKTurtleLight.Gehe(k.ypos);
MKTurtleLight.Drehe(90);
MKTurtleLight.StiftRunter;
END zeichnen;
BEGIN
END Kreis.
55
Damit die Turtle-Grak richtig arbeitet, muss sie zunächst aktiviert werden. Anschlieÿend
kann die Methode zeichnen() für beliebige Figur -Objekte aufgerufen werden. Nach Abarbeitung der Programmbefehle sollte die Turtle wieder deaktiviert werden.
Von den Klassen Rechteck, Quadrat und Kreis können beliebig viele Instanzen angelegt werden. Man kann sie sowohl mit einer Variablen jedes eigenen Typs, als auch in Variable vom
Typ Figur referenzieren. Die Referenztypen der Klassen können als Komponententyp für Arrays genutzt werden. Ein
ARRAY n OF Figur.FigurRef
kann Objekte der einzelnen Klassen
sammeln. Die einzelnen Komponenten werden über einen Index angesprochen.
(* Datei: Figuren/TestFigur1.mod *)
MODULE TestFigur1;
IMPORT Out, Figur, Kreis, Quadrat, Rechteck, MKTurtleLight;
PROCEDURE ProgMain*();
VAR testQ : Quadrat.QuadratRef;
testK : Kreis.KreisRef;
i : INTEGER;
BEGIN
MKTurtleLight.TurtleEin;
testQ := Quadrat.NEWQuadrat(−350,200,10);
testK := Kreis.NEWKreis(50,50,80);
FOR i:= 0 TO 18 DO
testQ.zeichnen();
testQ.strecken (1.2);
testK.zeichnen();
testK.verschieben (10,0);
END;
MKTurtleLight.TurtleAus;
END ProgMain;
BEGIN
Out.Open
END TestFigur1.
(* Datei: Figuren/TestFigur2.mod *)
MODULE TestFigur2;
IMPORT Out, Figur, Kreis, Quadrat, Rechteck;
PROCEDURE ProgMain*();
VAR test : ARRAY 3 OF Figur.FigurRef;
i : INTEGER;
BEGIN
test [0] := Rechteck.NEWRechteck(100,100,40,60);
test [1] := Quadrat.NEWQuadrat(200,200,40);
test [2] := Kreis.NEWKreis(300,300,50);
FOR i:=0 TO LEN(test)−1 DO
test [ i ]. printTyp();
test [ i ]. printPosition ();
test [ i ]. printLaengen();
test [ i ]. printFlaeche ();
56
Out.Ln;
test [ i ]. strecken (1.5);
test [ i ]. verschieben(10,−5);
test [ i ]. printTyp();
test [ i ]. printPosition ();
test [ i ]. printLaengen();
test [ i ]. printFlaeche ();
Out.Ln; Out.Ln;
END;
END ProgMain;
BEGIN
Out.Open
END TestFigur2.
(* Datei: Figuren/TestFigur3.mod *)
MODULE TestFigur3;
IMPORT Out, Figur, Kreis, Quadrat, Rechteck, MKTurtleLight;
PROCEDURE ProgMain*();
VAR test : ARRAY 6 OF Figur.FigurRef;
i : INTEGER;
BEGIN
MKTurtleLight.TurtleEin;
test [0] :=
test [1] :=
test [2] :=
test [3] :=
test [4] :=
test [5] :=
Rechteck.NEWRechteck(−110,100,110,200);
Quadrat.NEWQuadrat(−85,75,60);
Rechteck.NEWRechteck(0,0,200,100);
Rechteck.NEWRechteck(140,50,30,50);
Kreis.NEWKreis(−30,−100,50);
Kreis.NEWKreis(120,−100,50);
FOR i:=0 TO LEN(test)−1 DO
test [ i ]. zeichnen();
END;
MKTurtleLight.TurtleAus;
END ProgMain;
BEGIN
Out.Open
END TestFigur3.
57
Anhang
A Python-Quellcode
A.1 Beispiel: Konto
Aggregierung, Kapselung und Konstruktor
Python kennt keine begin-end -Klammern. Anweisungsblöcke werden durch dieselbe Einzugstiefe gekennzeichnet.
Die Kopfzeile class Klassenname: ist der Beginn einer Klassendenition. Der Klassenkörper besteht
aus einer oder mehreren eingerückten Anweisungen.
Die __init__-Methode gehört zu einer Menge von Spezialmethoden in Python. Sie wird bei der
Instanzbildung automatisch als Konstruktormethode ausgeführt. Variablen die innerhalb dieser Methode initialisiert werden, bilden die Attribute der Klasse.
Methoden werden im Klassenkörper als Funktionen mit dem Schlüsselwort def deklariert. An den
ersten Parameter self wird beim Aufruf automatisch das aufrufende Objekt übergeben.
#Datei: Konto3/Konto.pyw
#Beispiel: Aggregierung, Kopplung, Kapselung und Konstruktor
# −−> Aggregierung
class Konto:
# −−> Konstruktor
def __init__(self, nummer):
# Attriubte
self .kontonummer = nummer
self .saldo = 0.0
# Methoden
def getKontonummer(self):
return self.kontonummer
def getSaldo(self ):
return self.saldo
def einzahlen( self , betrag):
self .saldo = self .saldo + betrag # −−> Kopplung
def auszahlen(self , betrag):
self .saldo = self .saldo − betrag # −−> Kopplung
def auszug(self ):
print "Kontonummer: ", self.kontonummer, " Saldo: ", self.saldo, " Euro"
Damit man die denierte Klasse in anderen Programmen verwenden kann, muss das zugehörige Modul
mittels from Klassenname import * importiert werden.
Zur Instanzbildung dient der Klassenname als Funktionsname, um die Konstuktormethode aufzurufen.
Die angegebenen Parameter werden als Initialwerte genutzt.
Python-Module exportieren alle Namen. Es gibt keine Möglichkeit, zu spezizieren, welche Namen
auÿerhalb des Moduls nicht sichtbar sein sollen. Datenkapselung ist somit nur eine Konvention. Der
Programmierer kann das Geheimnisprinzip gewährleisten, indem er auf Datenattribute grundsätzlich
nur über Methoden zugreift.
#Datei: Konto3/KontoTest.pyw
#Beispiel: Aggregierung, Kopplung, Kapselung und Konstruktor
from Konto import *
meinKonto = Konto(4531088) # Objekt erzeugen und initialisieren
meinKonto.saldo = 200.00 # Zugri auf Attribute
print "Kontonummer: ", meinKonto.kontonummer, " Saldo: ", meinKonto.saldo
meinKonto.einzahlen(300.00) # Methodenaufruf
meinKonto.auszug()
deinKonto = Konto(1733065) # neue Instanz
deinKonto.einzahlen(1000.00) # Datenkapselung ist Konvention
print "Kontonummer: ", meinKonto.getKontonummer(), " Saldo: ", meinKonto.getSaldo()
deinKonto.auszug()
deinKonto.einzahlen(300.00)
deinKonto.auszug()
58
Klassenvariablen
In Python werden Variablen nicht deklariert, sondern durch Zuweisungen deniert.
Instanzvariablen können nur im Konstruktor erzeugt werden. Alle anderen Variablen, die innerhalb
einer Klasse deniert werden, sind Klassenvariablen. Der Zugri auf Klassenvariablen erfolgt über den
Namen der Klasse.
#Datei: Konto4/Konto.pyw
#Beispiel: Klassenvariablen
class Konto:
zaehler = 0 # −−> Klassenvariable
def __init__(self, nummer):
Konto.zaehler = Konto.zaehler + 1
self .kontonummer = nummer # −−> Instanzvariablen
self .saldo = 0.0
def getKontonummer(self):
return self.kontonummer
def getSaldo(self ):
return self.saldo
def einzahlen( self , betrag):
self .saldo = self .saldo + betrag
def auszahlen(self , betrag):
self .saldo = self .saldo − betrag
def auszug(self ):
print "Kontonummer: ", self.kontonummer, " Saldo: ", self.saldo, "Euro"
#Datei: Konto4/KontoTest.pyw
#Beispiel: Klassenvariablen
from Konto import *
# Zugri auf Klassenvariablen ueber Klassennamen
print"Anzahl Konten: ", Konto.zaehler
meinKonto = Konto(4531088)
print "Anzahl Konten: ", Konto.zaehler
deinKonto = Konto(1733065)
print "Anzahl Konten: ", Konto.zaehler
Manipulierbarkeit
#Datei: Konto5/KontoTest.pyw
#Beispiel: Manipulierbarkeit
from Konto import *
def ueberweisen(betrag, kontoA, kontoZ):
kontoA.auszug()
kontoZ.auszug()
kontoA.auszahlen(betrag)
kontoZ.einzahlen(betrag)
print "Von Konto ",kontoA.getKontonummer(),
print " wurden ",betrag," Euro ",
print "auf Konto ",kontoZ.getKontonummer()," ueberwiesen."
kontoA.auszug()
kontoZ.auszug()
meinKonto = Konto(4531088)
meinKonto.einzahlen(300.00)
deinKonto = Konto(1733065)
deinKonto.einzahlen(100.00)
# Zuweisung
unserKonto = deinKonto
# Vergleich
if meinKonto == deinKonto:
print "Mein Konto und dein Konto sind identisch."
else :
print "Mein Konto und dein Konto sind verschieden."
if unserKonto == deinKonto:
print "Unser Konto und dein Konto sind identisch."
else :
print "Unser Konto und dein Konto sind verschieden."
# Parameter
ueberweisen(30.00, meinKonto, deinKonto)
59
Vererbung
In der Kopfzeile der Klassendenition kann hinter dem Namen der Klasse in Klammern abgegeben
werden, von welchen Klassen sie abgeleitet wird. Die abgeleitete Klasse erbt dann alle Attribute und
Methoden der Superklasse.
Der Konstruktor wird in der Subklasse neu geschrieben, um weitere Attribute zu ergänzen. Die Konstrkutormethode der Superklasse kann man durch den Befehl Superklassenname.__init__(self, ...)
aufrufen.
In einem Modul können mehrere Klassen deniert werden.
# −*− coding: cp1252 −*−
#Datei: Konto6/Konto.pyw
#Beispiel: Vererbung
class Konto:
zaehler = 0
def __init__(self, nummer):
Konto.zaehler = Konto.zaehler + 1
self .kontonummer = nummer
self .saldo = 0.0
def getKontonummer(self):
return self.kontonummer
def getSaldo(self ):
return self.saldo
def einzahlen( self , betrag):
self .saldo = self .saldo + betrag
def auszahlen(self , betrag):
self .saldo = self .saldo − betrag
def auszug(self ):
print "Kontonummer: ", self.kontonummer, " Saldo: ", self.saldo, "Euro"
# −−> Vererbung
class Sparkonto(Konto):
# Konstruktor der Klasse Sparkonto
def __init__(self, nummer, zins):
Konto.__init__(self, nummer) # Konstruktor der Superklasse Konto wird aufgerufen
self . zinssatz = zins # zusaetzliches Attribut
# neue Methoden fuer Zugri auf Attribut zinssatz
def getZinssatz( self ):
return self . zinssatz
def setZinssatz( self , zins ):
self . zinssatz = zins
# neue Methode
def verzinsen( self ):
self .saldo = self .saldo + ( self . zinssatz * self .saldo)/100
Um Objekte der Subklasse zu erzeugen, wird deren Name als Funktionsaufruf verwendet. Für Instanzen der Subklasse können alle Methoden aufgerufen werden, die in der Superklasse deniert sind.
#Datei: Konto6/KontoTest.pyw
#Beispiel: Vererbung
from Konto import *
meinSparkonto = Sparkonto(5613990,3.4)
# jede Instanz der Klasse Sparkonto hat 3 Attribute
print "Kontonummer: ",meinSparkonto.getKontonummer(), # geerbtes Attribut
print " Saldo: ", meinSparkonto.getSaldo(), " Euro", # geerbtes Attribut
print " Zinssatz: ", meinSparkonto.getZinssatz() # neu deklariertes Attriubut
# Methoden einzahlen(betrag), auszahlen(betrag) und auszug() koennen verwendet werden
meinSparkonto.einzahlen(250.00) # geerbte Methoden
meinSparkonto.auszug()
meinSparkonto.auszahlen(10.00)
meinSparkonto.auszug()
# ausserdem gibt es fuer Instanzen der Klasse Sparkonto die Methode verzinsen()
meinSparkonto.verzinsen() # neu implementierte Methode
meinSparkonto.auszug()
60
Reimplementierung
Wird innerhalb der Subklasse eine Methode der Superklasse mit gleichem Namen und gleicher Signatur
erneut deniert, dann überschreibt sie die geerbte Methode.
#Datei: Konto7/Konto.pyw
#Beispiel: Reimplementierung
class Konto:
zaehler = 0
def __init__(self, nummer):
Konto.zaehler = Konto.zaehler + 1
self .kontonummer = nummer
self .saldo = 0.0
def getKontonummer(self):
return self.kontonummer
def getSaldo(self ):
return self.saldo
def einzahlen( self , betrag):
self .saldo = self .saldo + betrag
def auszahlen(self , betrag):
self .saldo = self .saldo − betrag
def auszug(self ):
print "Kontonummer: ", self.kontonummer, " Saldo: ", self.saldo, "Euro"
class Girokonto(Konto):
def __init__(self, nummer, limit):
Konto.__init__(self, nummer)
if limit < 0 :
limit = −limit
self . kreditlimit = limit
# Methode fuer schreibenden Zugri ueberprueft ob limit positiv ist
def getLimit(self ):
if limit < 0 :
limit = −limit
return self. kreditlimit
def setLimit( self , limit ):
self . kreditlimit = limit
# −−> Reimplementierung
def auszahlen(self , betrag):
if betrag <= self.saldo + self . kreditlimit :
self .saldo = self .saldo − betrag
else :
print "Limit ueberschritten!"
#Datei: Konto7/KontoTest.pyw
#Beispiel: Reimplementierung
from Konto import *
meinKonto = Konto(4531088)
meinKonto.einzahlen(400.00);
meinKonto.auszug();
print "Versuche 100 Euro abzuheben."
meinKonto.auszahlen(100.00); # Methode auszahlen(betrag) der Klasse Konto
meinKonto.auszug();
print "Versuche 500 Euro abzuheben."
meinKonto.auszahlen(500.00); # Methode auszahlen(betrag) der Klasse Konto
meinKonto.auszug();
print
meinGirokonto = Girokonto(1733065,100)
meinGirokonto.einzahlen(400.00);
meinGirokonto.auszug();
print "Versuche 100 Euro abzuheben."
meinGirokonto.auszahlen(100.00); # Methode auszahlen(betrag) der Klasse Girokonto
meinGirokonto.auszug();
print "Versuche 500 Euro abzuheben."
meinGirokonto.auszahlen(500.00); # Methode auszahlen(betrag) der Klasse Girokonto
meinGirokonto.auszug();
61
Polymorphismus
Da Variablen in Python nicht mit einem bestimmten Datentyp deklariert werden, sind alle Variablen
polymorph. Dies bedeutet, dass eine Variable auf Objekte beliebigen Typs verweisen kann. Python
merkt sich während der Ausführungszeit, welche Objetarten ein Programm benutzt.
#Datei: Konto8/KontoTest.pyw
#Beispiel: Polymorphismus
from Konto import *
meinKonto = Konto(4531088) # meinKonto zeigt auf ein Objekt vom Typ Konto
meinKonto.einzahlen(300.00)
meinKonto.auszug()
meinKonto.auszahlen(500.00) # Methode auszahlen(betrag) der Klasse Konto wird aufgerufen
meinKonto.auszug()
print
meinKonto = Girokonto(5613990,100) # meinKonto zeigt jetzt auf ein Objekt vom Typ Girokonto
meinKonto.einzahlen(300.00)
meinKonto.auszug()
meinKonto.auszahlen(500.00) # Methode auszahlen(betrag) der Klasse Girokonto wird aufgerufen
# −−> dynamische Bindung
meinKonto.auszug()
print
meinKonto = Sparkonto(1733065,1.25) # meinKonto zeigt jetzt auf ein Objekt vom Typ Sparkonto
meinKonto.einzahlen(400.00)
meinKonto.auszug()
meinKonto.auszahlen(500.00) # Methode auszahlen(betrag) der Klasse Sparkonto wird aufgerufen,
# die sie von der Klasse Konto geerbt hat
# meinKonto.verzinsen() Fehler: die Klasse Konto kennt die Methode verzinsen() nicht
Überladen
Das Überladen von Methoden ist in Python nicht möglich.
62
A.2 Beispiel: Mediendatenbank
# Datei: Medien/Medium.pyw
class Medium:
zaehler = 0
def __init__(self, titel , preis ):
Medium.zaehler += 1
self .code = 100000 + Medium.zaehler
self . titel = titel
self . preis = preis
self .kommentar = ''
def getCode(self):
return self.code
def getTitel( self ):
return self. titel
def getPreis( self ):
return self. preis
def setPreis( self , preis ):
self . preis = preis
def getKommentar(self):
return self.kommentar
def setKommentar(self, kommentar):
self .kommentar = kommentar
def druckeInfo():
print "Code: ", self .code
print "Titel: " , self . titel
print "Preis: ", self . preis , " Euro"
print "Kommentar: ", self.kommentar
print
class Buch(Medium):
def __init__(self, titel , autor, verlag , jahr , preis ):
Medium.__init__(self, titel, preis)
self .autor = autor
self .verlag = verlag
self .erscheinungsjahr = jahr
def getAutor(self ):
return self.autor
def getVerlag( self ):
return self.verlag
def getErscheinungsjahr(self ):
return self.erscheinungsjahr
def druckeInfo( self ):
print "Code: ", self .code
print "Titel: " , self . titel
print "Autor: ", self .autor
print "Verlag: ", self .verlag , ", " , self .erscheinungsjahr
print "Preis: ", self . preis , " Euro"
print "Kommentar: ", self.kommentar
print
class Cd(Medium):
def __init__(self, titel , interpret , titelanz , zeit , preis ):
Medium.__init__(self, titel, preis)
self . interpret = interpret
self . titelanzahl = titelanz
self .spieldauer = zeit
def getInterpret( self ):
return self. interpret
def getTitelanzahl( self ):
return self. titelanzahl
def getSpieldauer( self ):
return self.spieldauer
def druckeInfo( self ):
print "Code: ", self .code
print "Titel: " , self . titel
print "Interpret: " , self . interpret
print "Spieldauer: ", self .spieldauer , " min, ", self . titelanzahl , " Titel"
print "Preis: ", self . preis , " Euro"
print "Kommentar: ", self.kommentar
print
63
class Video(Medium):
def __init__(self, titel , reg , zeit , preis ):
Medium.__init__(self, titel, preis)
self . regisseur = reg
self .spieldauer = zeit
def getRegisseur( self ):
return self. regisseur
def getSpieldauer( self ):
return self.spieldauer
def druckeInfo( self ):
print "Code: ", self .code
print "Titel: " , self . titel
print "Regisseur: ", self . regisseur
print "Spieldauer: ", self .spieldauer , " min"
print "Preis: ", self . preis , " Euro"
print "Kommentar: ", self.kommentar
print
# Datei: Medien/Datenbank.pyw
from Medium import *
class Datenbank:
def __init__(self):
self .medien = []
def erfasseMedium(self, med):
self .medien.append(med)
def auisten ( self ):
for i in range(0, len( self .medien), 1):
self .medien[i ]. druckeInfo()
# Datei: Medien/TestMedienDB.pyw
from Datenbank import *
from Medium import *
db = Datenbank()
db.erfasseMedium(Buch("Sprechen Sie Java","Hanspeter Moessenboeck", "dpunkt.verlag",2001,28.00))
db.erfasseMedium(Cd("No Angel", "Dido", 13, 90, 15.00))
db.erfasseMedium(Video("X−Men", "Bryan Singer", 100, 21.00))
db. auisten ()
64
A.3 Beispiel: geometrische Figuren
Abstrakte Methoden werden mit Hilfe der pass -Anweisung implementiert. Da der Methodenrumpf
nicht leer bleiben kann, nutzt man diese leere Platzhalteranweisung, die keine Aktion bewirkt.
Obwohl die Klasse zu einer abstrakten Klasse wird, wenn sie abstrakte Methoden enthält, können
Instanzen der Klasse erzeugt werden.
# Datei: Figuren/Figur.pyw
from turtle import *
class Figur:
def __init__(self,x,y):
self .xpos = x
self .ypos = y
def getTyp(self):
pass
def getPosition( self ):
return "(%d;%d)" %(self.xpos, self.ypos)
def getLaengen(self):
pass
def getFlaeche(self ):
pass
def verschieben( self , dx, dy):
self .xpos += dx
self .ypos += dy
def strecken( self , faktor ):
pass
def zeichnen( self ):
pass
class Rechteck(Figur):
def __init__(self,x,y,breite,hoehe):
Figur.__init__(self,x,y)
self . breite = breite
self .hoehe = hoehe
def getTyp(self):
return "Rechteck"
def getLaengen(self):
return "Breite = %d Hoehe = %d" %(self.breite, self.hoehe)
def getFlaeche(self ):
return self. breite * self .hoehe
def strecken( self , faktor ):
self . breite *= faktor
self .hoehe *= faktor
def zeichnen( self ):
up()
goto([ self .xpos, self .ypos])
down()
forward(self . breite)
right(90)
forward(self .hoehe)
right(90)
forward(self . breite)
right(90)
forward(self .hoehe)
right(90)
class Quadrat(Figur):
def __init__(self,x,y,hoehe):
Figur.__init__(self,x,y)
self .hoehe = hoehe
def getTyp(self):
return "Quadrat"
def getLaengen(self):
return "Hoehe = %d" %(self.hoehe)
def getFlaeche(self ):
return self.hoehe**2
def strecken( self , faktor ):
self .hoehe *= faktor
65
def zeichnen( self ):
up()
goto([ self .xpos, self .ypos])
down()
for i in range (0,4,1):
forward(self .hoehe)
right(90)
class Kreis(Figur):
PI = 3.14
def __init__(self,x,y,radius):
Figur.__init__(self,x,y)
self .radius = radius
def getTyp(self):
return "Kreis"
def getLaengen(self):
return "Radius = %d" %(self.radius)
def getFlaeche(self ):
return Kreis.PI*self.radius**2
def strecken( self , faktor ):
self .radius *= faktor
def zeichnen( self ):
schritt = Kreis.PI* self .radius/180
up()
goto([ self .xpos+self.radius, self .ypos])
down()
left (90)
for i in range (0,360,1):
forward(schritt)
left (1)
right(90)
# Datei: Figuren/TestFigur1.pyw
from Figur import *
testQ = Quadrat(−150,100,20)
testK = Kreis(200,0,80)
for i in range (0,15,1):
testQ.zeichnen()
testQ.strecken(1.2)
for i in range (0,10,1):
testK.zeichnen()
testK.verschieben(10,0)
# Datei: Figuren/TestFigur2.pyw
from Figur import *
test = [Rechteck(100,100,40,40), Quadrat(200,200,40), Kreis(300,300,50)]
for i in range (0,3,1):
print test[ i ]. getTyp()," −−> ",
print test[ i ]. getPosition (),
print test[ i ]. getLaengen(),
print "Flaeche = ", test[i ]. getFlaeche()
test [ i ]. strecken(1.5)
test [ i ]. verschieben(10,−5)
print test[ i ]. getTyp()," −−> ",
print test[ i ]. getPosition (),
print test[ i ]. getLaengen(),
print "Flaeche = ", test[i ]. getFlaeche()
# Datei: Figuren/TestFigur3.pyw
from Figur import *
test
test
test
test
test
test
test
= []
+= [Rechteck(−110,100,110,200)]
+= [Quadrat(−85,75,60)]
+= [Rechteck(0,0,200,100)]
+= [Rechteck(140,50,30,50)]
+= [Kreis(−30,−100,50)]
+= [Kreis(120,−100,50)]
for i in range (0,6,1):
test [ i ]. zeichnen()
66
B Delphi-Quellcode
B.1 Beispiel: Konto
Aggregierung und Kopplung
Klassen werden in einer Unit beschrieben. Der Name der Unit muss sich dabei vom Klassennamen
unterscheiden.
Die Denition der Klasse erfolgt als Typdeklaration; eingeleitet durch das Schlüsselwort type. Auÿerdem wird mittels Klassenname = class angegeben, dass es sich um eine Klassendenition handelt.
In der Klassenbeschreibung müssen die zugehörigen Attribute und Methoden deklariert werden.
Die Implementierung der Methoden erfolgt gesondert im Implementationsteil der Unit. Für jede Methode, die deklariert worden ist, muss hier der Methodenrumpf geschrieben werden. Dabei wird explizit
angegeben, zu welcher Klasse die Methode gehört.
{Datei: Konto1/KontoU.pas}
{Beispiel : Aggregierung und Kopplung}
unit KontoU;
interface
{ −−> Aggregierung}
type
Konto = class
public
{Attribute}
kontonummerF : Integer;
saldoF : Double;
{Methoden}
procedure einzahlen(betrag : Double);
procedure auszahlen(betrag : Double);
procedure auszug;
end;
implementation
{Implementierung der Methoden}
procedure Konto.einzahlen(betrag : Double);
begin
saldoF := saldoF + betrag
{−−> Kopplung}
end;
procedure Konto.auszahlen(betrag : Double);
begin
saldoF := saldoF − betrag
{−−> Kopplung}
end;
procedure Konto.auszug;
begin
Writeln('Kontonummer: ',kontonummerF,' Saldo: ',saldoF:8:2,' Euro')
end;
end.
Das Testprogramm ist eine einfache Konsolenanwendung, die alle notwendigen Units unter uses importiert.
Variablen werden auÿerhalb des Hauptprogrammes deklariert und müssen später initialisiert werden.
Die Funktion create erzeugt ein Objekt der angegebenen Klasse und liefert dessen Speicheradresse als
Rückgabewert. Sie wird genutzt, um die deklarierten Variablen mit Werten zu belegen.
Verweist eine Variable auf eine Instanz einer Klasse, dann können deren Attribute und Methoden
über den Namen der Variable angesprochen werden.
{Datei: Konto1/KntoTest.dpr}
{Beispiel : Aggregierung und Kopplung}
program KontoTest;
{$APPTYPE CONSOLE}
uses
SysUtils,
KontoU in 'KontoU.pas';
var meinKonto, deinKonto : Konto; {Variablen vom Typ Konto}
67
begin
meinKonto := Konto.create; {Objekt vom Typ Konto wird erzeugt}
meinKonto.kontonummerF := 4531088; {Zugri auf Attribute}
meinKonto.saldoF := 200.00;
Writeln('Konotnummer: ', meinKonto.kontonummerF,' Saldo: ', meinKonto.saldoF:8:2);
meinKonto.einzahlen(300.00); {Methodenaufruf}
meinKonto.auszug;
Writeln;
deinKonto := Konto.create; {deinKonto zeigt auf neue Intanz der Klasse Konto}
deinKonto.kontonummerF := 1733065;
deinKonto.saldoF := 1000.00;
deinKonto.auszug;
deinKonto.auszahlen(100.00);
deinKonto.auszug;
Readln
end.
Kapselung
Die Klassendenition wird in mehrere Bereiche unterteilt. Der erste Teil - gekennzeichnet durch das
Schlüsselwort private - enthält alle Attribute und Methoden, auf die man von anderen Units aus nicht
zugreifen kann. Sie sind somit gekapselt. Im public -Teil sind diejenigen Komponenten deklariert, die
uneingeschränkt sichtbar sein sollen.
Anstelle der für gekapselte Attribute notwendigen get- und set-Methoden werden in Delphi Eigenschaften - sogenannte properties - verwendet. Jede Eigenschaft hat einen read - und einen write -Zweig.
Dort können Datenfelder oder Methoden eingetragen werden, die angeben was getan wird wenn der
Wert der Eigenschaft gesetzt oder gelesen werden soll. Der write -Zweig ist optional.
{Datei: Konto2/KontoU.pas}
{Beispiel : Kapselung}
unit KontoU;
interface
type
Konto = class
{ −−> Datenkapselung }
private
kontonummerF : Integer;
saldoF : Double;
public
{ Property fuer sicheren Zugri auf Attibut kontonummerF }
property kontonummer : Integer
read kontonummerF write kontonummerF;
{ Property fuer sicheren Zugri auf Attribut saldoF }
property saldo : Double
read saldoF write saldoF;
{ urspruengliche Methoden }
procedure einzahlen(betrag : Double);
procedure auszahlen(betrag : Double);
procedure auszug;
end;
implementation
procedure Konto.einzahlen(betrag : Double);
begin
saldoF := saldoF + betrag
end;
procedure Konto.auszahlen(betrag : Double);
begin
saldoF := saldoF − betrag
end;
procedure Konto.auszug;
begin
Writeln('Kontonummer: ',kontonummerF,' Saldo: ',saldoF:8:2,' Euro')
end;
end.
Anstelle der Attribute nutzt das Testprogramm die denierten
initialisieren bzw. zu lesen.
68
Properties
um die Datenfelder zu
{Datei: Konto2/KontoTest.dpr}
{Beispiel : Kapselung}
program KontoTest;
{$APPTYPE CONSOLE}
uses
SysUtils,
KontoU in 'KontoU.pas';
var meinKonto : Konto;
begin
meinKonto := Konto.create;
{ meinKonto.kontonummerF := 4531088; !!Fehler
Writeln(meinKonto.kontonummerF); !!Fehler }
meinKonto.kontonummer := 4531088; { schreibender Zugri auf Attribute ueber Properties }
meinKonto.saldo := 0.00;
Writeln('Kontonummer: ', meinKonto.kontonummer,' Saldo: ', meinKonto.saldo:8:2);
Readln
end.
Konstruktor
Zur Denition eines Konstruktors wird das Schlüsselwort constructor vor die Methodendeklaration gesetzt. Der Konstruktor heiÿt üblicherweise create. Wie alle anderen Methoden, muss er im
Implementierungsteil beschrieben werden.
{Datei: Konto3/KontoU.pas}
{Beispiel : Konstruktor}
unit KontoU;
interface
type
Konto = class
private
kontonummerF : Integer;
saldoF : Double;
public
{−−> Konstruktor}
constructor create(nummer : Integer);
property kontonummer : Integer
read kontonummerF ; {write entfernt}
property saldo : Double
read saldoF ; {write entfernt}
procedure einzahlen(betrag : Double);
procedure auszahlen(betrag : Double);
procedure auszug;
end;
implementation
{Implementierung des Konstruktors}
constructor Konto.create(nummer : Integer);
begin
kontonummerF := nummer;
saldoF := 0.0;
end;
procedure Konto.einzahlen(betrag : Double);
begin
saldoF := saldoF + betrag
end;
procedure Konto.auszahlen(betrag : Double);
begin
saldoF := saldoF − betrag
end;
procedure Konto.auszug;
begin
Writeln('Kontonummer: ',kontonummerF,' Saldo: ',saldoF:8:2,' Euro')
end;
end.
69
{Datei: Konto3/KontoTest.dpr}
{Beispiel : Konstruktor}
program KontoTest;
{$APPTYPE CONSOLE}
uses
SysUtils,
KontoU in 'KontoU.pas';
var meinKonto : Konto;
begin
{ bisher:
meinKonto := Konto.create; // Objekt erzeugen
meinKonto.kontonummer := 4531088; // Kontonummer initialisieren
meinKonto.saldo := 0.00; //Kontostand initialisieren }
{ jetzt :
}
meinKonto := Konto.create(4531088); {Konstruktor erzeugt Objet und initialisiert Attribute}
meinKonto.auszug;
Readln
end.
Klassenvariablen
Variablen, die auÿerhalb der Klassendenition deklariert sind, können als Klassenvariablen verwendet
werden. Sie gehören jedoch zur Unit und nicht zur Klasse, deshalb werden sie auÿerhalb der Unit über
den Unitnamen angesprochen.
{Datei: Konto4/KontoU.pas}
{Beispiel : Klassenvariablen}
unit KontoU;
interface
type
Konto = class
private
kontonummerF : Integer; { −−> Instanzvariablen }
saldoF : Double;
public
constructor create(nummer : Integer);
property kontonummer : Integer
read kontonummerF ;
property saldo : Double
read saldoF ;
procedure einzahlen(betrag : Double);
procedure auszahlen(betrag : Double);
procedure auszug;
end;
{−−> Variable der Unit, ersetzt Klassenvariable }
var zaehler : Cardinal = 0;
implementation
constructor Konto.create(nummer : Integer);
begin
zaehler := zaehler + 1;
kontonummerF := nummer;
saldoF := 0.00;
end;
procedure Konto.einzahlen(betrag : Double);
begin
saldoF := saldoF + betrag
end;
procedure Konto.auszahlen(betrag : Double);
begin
saldoF := saldoF − betrag
end;
procedure Konto.auszug;
begin
Writeln('Kontonummer: ',kontonummerF,' Saldo: ',saldoF:8:2,' Euro')
end;
end.
70
{Datei: Konto4/KontoTest.dpr}
{Beispiel : Klassenvariablen}
program KontoTest;
{$APPTYPE CONSOLE}
uses
SysUtils,
KontoU in 'KontoU.pas';
var meinKonto, deinKonto : Konto;
begin
{ Zugri auf Klassenvariable ueber Namen der Unit }
Writeln('Anzahl Konten: ', KontoU.zaehler);
meinKonto := Konto.create(4531088);
Writeln('Anzahl Konten: ', KontoU.zaehler);
deinKonto := Konto.create(1733065);
Writeln('Anzahl Konten: ', KontoU.zaehler);
Readln
end.
Manipulierbarkeit
{Datei: Konto5/KontoTest.dpr}
{Beispiel : Manipulierbarkeit}
program KontoTest;
{$APPTYPE CONSOLE}
uses
SysUtils,
KontoU in 'KontoU.pas';
var meinKonto, deinKonto, unserKonto : Konto;
procedure ueberweisen(betrag : Double; kontoA, kontoZ : Konto);
begin
kontoA.auszug;
kontoZ.auszug;
kontoA.auszahlen(betrag);
kontoZ.einzahlen(betrag);
Write('Von Konto ',kontoA.kontonummer);
Write(' wurden ',betrag:8:2,' Euro ');
Writeln('auf Konto ',kontoZ.kontonummer,' ueberwiesen.');
kontoA.auszug;
kontoZ.auszug
end;
begin
meinKonto := Konto.create(4531088);
meinKonto.einzahlen(300.00);
deinKonto := Konto.create(1733065);
meinKonto.einzahlen(100.00);
{ Zuweisung }
unserKonto := deinKonto;
{ Vergleich }
if meinKonto = deinKonto
then Writeln('Mein Konto und dein Konto sind identisch.')
else Writeln('Mein Konto und dein Konto sind verschieden.');
if unserKonto = deinKonto
then Writeln('Unser Konto und dein Konto sind identisch.')
else Writeln('Unser Konto und dein Konto sind verschieden.');
{ Parameter }
ueberweisen(30.00,meinKonto,deinKonto);
Readln
end.
71
Vererbung
Damit die Superklasse ihre Attribute, Methoden und Eigenschaften an die Subklasse vererbt, müssen
sie public oder protected deklariert werden. Dabei bedeutet protected, dass der Benutzer nicht auf
diese Komponenten zugreifen kann. Wird jedoch von der Klasse eine neue Klasse abgeleitet, dann
sind diese Komponenten zugänglich.
{Datei: Konto6/KontoU.pas}
{Beispiel : Vererbung}
unit KontoU;
interface
type
Konto = class
protected
kontonummerF : Integer; { Attribute sollen vererbt werden }
saldoF : Double;
public
constructor create(nummer : Integer);
property kontonummer : Integer
read kontonummerF ;
property saldo : Double
read saldoF ;
procedure einzahlen(betrag : Double);
procedure auszahlen(betrag : Double);
procedure auszug;
end;
var zaehler : Cardinal = 0;
implementation
constructor Konto.create(nummer : Integer);
begin
zaehler := zaehler + 1;
kontonummerF := nummer;
saldoF := 0.00;
end;
procedure Konto.einzahlen(betrag : Double);
begin
saldoF := saldoF + betrag
end;
procedure Konto.auszahlen(betrag : Double);
begin
saldoF := saldoF − betrag
end;
procedure Konto.auszug;
begin
Writeln('Kontonummer: ',kontonummerF,' Saldo: ',saldoF:8:2,' Euro')
end;
end.
Nach dem Schlüsselwort class kann man in Klammern angeben, von welcher Klasse die neue Klasse
abgeleitet wird. Die Subklasse kann in der selben Unit deniert werden, wie die Superklasse. Steht
die Superklasse in einer anderen Unit, muss diese importiert werden.
Der Konstuktor der Superklasse wird vom Konstruktor der Subklasse verdeckt. Soll er trotzdem
aufgerufen werden, muss das Schlüsselwort inherited vorm Methodenaufruf stehen.
{Datei: Konto6/SparkontoU.pas}
{Beispiel : Vererbung}
unit SparkontoU;
interface
uses
SysUtils , KontoU ;
type
Sparkonto = class(Konto)
private
zinssatzF : Double; { zusaetzliches Attribut }
public
constructor create(nummer : Integer; zins : Double);
{ neue Property fuer den Zugri auf Attribut zinssatzF }
property zinssatz : Double
read zinssatzF write zinssatzF;
{ neue Methode }
procedure verzinsen;
end;
72
implementation
constructor Sparkonto.create(nummer : Integer; zins : Double);
begin
inherited create(nummer); { Konstruktor der Superklasse Konto wird aufgerufen }
zinssatzF := zins ;
end;
procedure Sparkonto.verzinsen;
begin
saldoF := saldoF + (zinssatzF+saldo)/100;
end;
end.
{Datei: Konto6/KontoTest.dpr}
{Beispiel : Vererung}
program KontoTest;
{$APPTYPE CONSOLE}
uses
SysUtils,
KontoU in 'KontoU.pas',
SparkontoU in 'SparkontoU.pas';
var meinSparkonto : Sparkonto;
begin
meinSparkonto := Sparkonto.create(5613990 , 3.4);
{ Jede Instanz der Klasse Sparkonto hat 3 Attribute }
Write('Kontonummer: ',meinSparkonto.kontonummer); { geerbtes Attribut }
Write(' Saldo: ',meinSparkonto.saldo:8:2,' Euro'); { geerbtes Attribut }
Writeln(' Zinssatz: ', meinSparkonto.zinssatz:4:2); { neu deklariertes Attribut }
{ Methoden einzahlen(betrag), auszahlen(betrag) und auszug koennen verwendet werden }
meinSparkonto.einzahlen(250.00); {geerbte Methoden }
meinSparkonto.auszug;
meinSparkonto.auszahlen(10.00);
meinSparkonto.auszug;
{ ausserdem gibt es fuer Instanzen der Klasse Sparkonto die Methode verzinsen }
meinSparkonto.verzinsen; { neu implementierte Methode }
meinSparkonto.auszug;
Readln
end.
Reimplementierung
Wird innerhalb der Subklasse eine Methode der Superklasse mit gleichem Namen und gleicher Signatur erneut deniert, dann überschreibt sie die geerbte Methode.
Methoden werden in Delphi üblicherweise statisch gebunden, d.h. der Compiler legt zur Übersetzungszeit fest, welche Implementierung ausgeführt wird. Sollen Methoden dynamisch gebunden werden, wie
in anderen objektorientierten Programmiersprachen, so muss dies durch das Schlüsselwort dynamic
expilizit gefordert werden.
{Datei: Konto7/KontoU.pas}
{Beispiel : Reimplementierung}
unit KontoU;
interface
type
Konto = class
protected
kontonummerF : Integer;
saldoF : Double;
public
constructor create(nummer : Integer);
property kontonummer : Integer
read kontonummerF ;
property saldo : Double
read saldoF ;
procedure einzahlen(betrag : Double);
procedure auszahlen(betrag : Double); dynamic;
procedure auszug;
end;
73
var zaehler : Cardinal = 0;
implementation
constructor Konto.create(nummer : Integer);
begin
zaehler := zaehler + 1;
kontonummerF := nummer;
saldoF := 0.00;
end;
procedure Konto.einzahlen(betrag : Double);
begin
saldoF := saldoF + betrag
end;
procedure Konto.auszahlen(betrag : Double);
begin
saldoF := saldoF − betrag
end;
procedure Konto.auszug;
begin
Writeln('Kontonummer: ',kontonummerF,' Saldo: ',saldoF:8:2,' Euro')
end;
end.
Das Schlüsselwort override weist daraufhin, dass diese Methode eine andere Implementierung überschreibt.
{Datei: Konto7/GirokontoU.pas}
{Beispiel : Reimplementierung}
unit GirokontoU;
interface
uses
KontoU;
type
Girokonto = class(Konto)
private
limitF : Double;
procedure setLimit(limit : Double);
public
constructor create(nummer : Integer; limit : Double);
property limit : Double
read limitF write setLimit;
procedure auszahlen(betrag : Double); override;
end;
implementation
constructor Girokonto.create(nummer : Integer; limit : Double);
begin
inherited create(nummer);
if limit < 0
then limit := −limit;
limitF := limit ;
end;
{ Methode fuer schreibenden Zugri ueberprueft ob limit positiv ist }
procedure Girokonto.setLimit(limit : Double);
begin
if limit < 0
then limit := −limit;
limitF := limit
end;
{ −−> Reimplementierung }
procedure Girokonto.auszahlen(betrag : Double);
begin
if betrag <= saldoF + limitF
then saldoF := saldoF − betrag
else Writeln('Limit ueberschritten!')
end;
end.
{Datei: Konto7/KontoTest.dpr}
{Beispiel : Reimplementierung}
program KontoTest;
{$APPTYPE CONSOLE}
uses
SysUtils,
KontoU in 'KontoU.pas',
GirokontoU in 'GirokontoU.pas';
var meinKonto : Konto;
74
meinGirokonto : Girokonto;
begin
meinKonto := Konto.create(4531088);
meinKonto.einzahlen(400.00);
meinKonto.auszug;
Writeln('Versuche 100 Euro abzuheben.');
meinKonto.auszahlen(100.00); { Methode auszahlen(betrag) der Klasse Konto}
meinKonto.auszug;
Writeln('Versuche 500 Euro abzuheben.');
meinKonto.auszahlen(500.00); { Methode auszahlen(betrag) der Klasse Konto}
meinKonto.auszug;
Writeln;
meinGirokonto := Girokonto.create(1733065,100);
meinGirokonto.einzahlen(400.00);
meinGirokonto.auszug;
Writeln('Versuche 100 Euro abzuheben.');
meinGirokonto.auszahlen(100.00); { Methode auszahlen(betrag) der Klasse Girokonto }
meinGirokonto.auszug;
Writeln('Versuche 500 Euro abzuheben.');
meinGirokonto.auszahlen(500.00); { Methode auszahlen(betrag) der Klasse Girokonto }
meinGirokonto.auszug;
Readln
end.
Polymorphismus
Die Deklaration der Variablen gibt ihren statischen Typ an.Eine Variable kann während der Ausführungszeit auch auf Instanzen der Subklassen verweisen.
Damit zur Laufzeit die richtigen Aktionen ausgeführt werden, müssen Methoden, die mehrfach implementiert sind, dynamisch gebunden werden.
{Datei: Konto8/KontoTest.dpr}
{Beispiel : Polymorphismus}
program KontoTest;
{$APPTYPE CONSOLE}
uses
SysUtils,
KontoU in 'KontoU.pas',
SparkontoU in 'SparkontoU.pas',
GirokontoU in 'GirokontoU.pas';
var meinKonto : Konto; { Variable vom Typ Konto }
begin
meinKonto := Konto.create(4531088); { meinKonto zeigt auf ein Objekt vom Typ Konto}
meinKonto.einzahlen(300.00);
meinKonto.auszug;
meinKonto.auszahlen(500.00); { Methode auszahlen(betrag) der Klasse Konto wird aufgerufen }
meinKonto.auszug;
Writeln;
meinKonto := Girokonto.create(1733065,100); { meinKonto zeigt jetzt auf ein Objekt vom Typ Girokonto}
meinKonto.einzahlen(300.00);
meinKonto.auszug;
meinKonto.auszahlen(500.00); { Methode auszahlen(betrag) der Klasse Girokonto wird aufgerufen }
{ −−> dynamische Bindung }
meinKonto.auszug;
Writeln;
meinKonto := sparkonto.create(1733065,1.25); { meinKonto zeigt jetzt auf ein Objekt vom Typ Sparkonto }
meinKonto.einzahlen(400.00);
meinKonto.auszug;
meinKonto.auszahlen(500.00); { Methode auszahlen(betrag) der Klasse Sparkonto wird aufgerufen, }
{ die sie von der Klasse Konto geerbt hat }
meinKonto.auszug;
{ meinKonto.verzinsen; // Fehler: die Klasse Konto kennt die Methode verzinsen() nicht }
Readln
end.
75
Überladen
Methoden können mit gleichen Namen, aber unterschiedlichen Signaturen mehrfach innerhalb einer
Klasse deklariert werden. Solche Methoden werden mit den Zusatz overload versehen.
{Datei: Konto9/KontoU.pas}
{Beispiel : Ueberladen}
unit KontoU;
interface
type
Konto = class
protected
kontonummerF : Integer;
saldoF : Double;
public
{ bisheriger Konstruktor mit 1 Parameter }
constructor create(nummer : Integer); overload;
{ weiterer Konstruktor mit 2 Parameter }
constructor create(nummer : Integer; betrag : Double); overload ;
property kontonummer : Integer
read kontonummerF ;
property saldo : Double
read saldoF ;
procedure einzahlen(betrag : Double);
procedure auszahlen(betrag : Double); dynamic;
procedure auszug;
end;
var zaehler : Cardinal = 0;
implementation
constructor Konto.create(nummer : Integer);
begin
zaehler := zaehler + 1;
kontonummerF := nummer;
saldoF := 0.00;
end;
constructor Konto.create(nummer : Integer ; betrag : Double);
begin
zaehler := zaehler + 1;
kontonummerF := nummer;
saldoF := betrag;
end;
procedure Konto.einzahlen(betrag : Double);
begin
saldoF := saldoF + betrag
end;
procedure Konto.auszahlen(betrag : Double);
begin
saldoF := saldoF − betrag
end;
procedure Konto.auszug;
begin
Writeln('Kontonummer: ',kontonummerF,' Saldo: ',saldoF:8:2,' Euro')
end;
end.
{Datei: Konto9/SparkontoU.pas}
{Beispiel : Ueberladen}
unit SparkontoU;
interface
uses
SysUtils , KontoU ;
type
Sparkonto = class(Konto)
private
zinssatzF : Double;
public
{ bisheriger Konstruktor mit 2 Parameter }
constructor create(nummer : Integer; zins : Double); overload;
{ neuer Konstruktor mit 3 Parameter }
constructor create(nummer : Integer; betrag, zins : Double); overload;
76
property zinssatz : Double
read zinssatzF write zinssatzF;
procedure verzinsen;
end;
implementation
constructor Sparkonto.create(nummer : Integer; zins : Double);
begin
inherited create(nummer); { ruft 1−stelligen Konstruktor der Superklasse auf }
zinssatzF := zins ;
end;
constructor Sparkonto.create(nummer : Integer; betrag : Double; zins : Double);
begin
inherited create(nummer , betrag); { ruft 2− stelligen Konstruktor der Superklasse auf }
zinssatzF := zins ;
end;
procedure Sparkonto.verzinsen;
begin
saldoF := saldoF + (zinssatzF+saldo)/100;
end;
end.
{Datei: Konto9/GirokontoU.pas}
{Beispiel : Ueberladen}
unit GirokontoU;
interface
uses
KontoU;
type
Girokonto = class(Konto)
private
limitF : Double;
procedure setLimit(limit : Double);
public
{ bisheriger Konstruktor mit 2 Parameter }
constructor create(nummer : Integer; limit : Double); overload;
{ neuer Konstruktor mit 3 Parameter }
constructor create(nummer : Integer; betrag, limit : Double); overload;
property limit : Double
read limitF write setLimit;
procedure auszahlen(betrag : Double); override;
end;
implementation
constructor Girokonto.create(nummer : Integer; limit : Double);
begin
inherited create(nummer); { ruft 1− stelligen Konstruktor der Superklasse auf }
if limit < 0
then limit := −limit;
limitF := limit ;
end;
constructor Girokonto.create(nummer : Integer; betrag : Double; limit : Double);
begin
inherited create(nummer,betrag); { ruft 2− stelligen Konstruktor der Superklasse auf }
if limit < 0
then limit := −limit;
limitF := limit ;
end;
procedure Girokonto.setLimit(limit : Double);
begin
if limit < 0
then limit := −limit;
limitF := limit
end;
procedure Girokonto.auszahlen(betrag : Double);
begin
if betrag <= saldoF + limitF
then saldoF := saldoF − betrag
else Writeln('Limit ueberschritten!')
end;
end.
77
{Datei: Konto9/KontoTest.dpr}
{Beispiel : Ueberladen}
program KontoTest;
{$APPTYPE CONSOLE}
uses
SysUtils,
KontoU in 'KontoU.pas',
SparkontoU in 'SparkontoU.pas',
GirokontoU in 'GirokontoU.pas';
var meinKonto, deinKonto : Konto;
meinSparkonto, deinSparkonto : Sparkonto;
meinGirokonto, deinGirokonto : Girokonto;
begin
meinKonto := Konto.create(4531088);
meinKonto.auszug;
meinKonto.einzahlen(300.00);
meinKonto.auszug;
Writeln;
deinKonto := Konto.create(1733065, 200.00);
deinKonto.auszug;
deinKonto.einzahlen(300.00);
deinKonto.auszug;
Writeln;
meinSparkonto := Sparkonto.create(5613990,1.25);
meinSparkonto.auszug;
meinSparkonto.einzahlen(600.00);
meinSparkonto.auszug;
Writeln;
deinSparkonto := Sparkonto.create(7835406,500.0,1.3);
deinSparkonto.auszug;
deinSparkonto.einzahlen(600.00);
deinSparkonto.auszug;
Writeln;
meinGirokonto := Girokonto.create(2571183,400.00);
meinGirokonto.auszug;
meinGirokonto.einzahlen(250.00);
meinGirokonto.auszug;
Writeln;
deinGirokonto := Girokonto.create(6464951,600.00,400.00);
deinGirokonto.auszug;
deinGirokonto.einzahlen(250.00);
deinGirokonto.auszug;
Writeln;
Readln
end.
78
B.2 Beispiel: Mediendatenbank
unit MediumU;
interface
type
Medium = class
protected
codeF : Integer ;
titelF : String;
preisF : Double;
kommentarF : String;
public
constructor create(titel : String; preis : Double);
property code : Integer read codeF;
property titel : String read titelF;
property preis : Double read preisF write preisF;
property kommentar : String read kommentarF write kommentarF;
procedure druckeInfo; dynamic;
end;
var zaehler : Integer =0;
implementation
constructor Medium.create(titel : String; preis : Double);
begin
zaehler := zaehler +1;
self .codeF := 100000 + zaehler;
self . titelF := titel ;
self .preisF := preis ;
self .kommentarF := '';
end;
procedure Medium.druckeInfo;
begin
Writeln('Code: ', self.codeF);
Writeln('Titel: ' , self . titelF );
Writeln('Preis: ', self .preisF , ' Euro');
Writeln('Kommentar: ', self.Kommentar);
Writeln;
end;
end.
unit BuchU;
interface
uses MediumU;
type
Buch = class(Medium)
protected
autorF : String;
verlagF : String;
erscheinungsjahrF : Integer;
public
constructor create(titel, autor, verlag : String; jahr : Integer ; preis : Double);
property autor : String read autorF;
property verlag : String read verlagF;
property erscheinungsjahr : Integer read erscheinungsjahrF;
procedure druckeInfo; override;
end;
implementation
constructor Buch.create(titel, autor, verlag : String; jahr : Integer ; preis : Double);
begin
inherited create( titel , preis );
self .autorF := autor;
self .verlagF := verlag;
self .erscheinungsjahrF := jahr;
end;
procedure Buch.druckeInfo;
begin
Writeln('Code: ', self.codeF);
Writeln('Titel: ' , self . titelF );
Writeln('Autor: ', self .autorF);
Writeln('Verlag: ', self .verlagF, ' , ' , self .erscheinungsjahrF);
Writeln('Preis: ', self .preisF :6:2, ' Euro');
Writeln('Kommentar: ', self.Kommentar);
Writeln;
end;
end.
79
unit CdU;
interface
uses MediumU;
type
Cd = class(Medium)
protected
interpretF : String;
titelanzahlF : Integer ;
spieldauerF : Integer;
public
constructor create(titel, interpret : String; titelanz , zeit : Integer ; preis : Double);
property interpret : String read interpretF;
property titelanzahl : Integer read titelanzahlF;
property spieldauer : Integer read spieldauerF;
procedure druckeInfo; override;
end;
implementation
constructor Cd.create(titel, interpret : String; titelanz , zeit : Integer ; preis : Double);
begin
inherited create( titel , preis );
self .interpretF := interpret ;
self . titelanzahlF := titelanz ;
self .spieldauerF := zeit ;
end;
procedure Cd.druckeInfo;
begin
Writeln('Code: ', self.codeF);
Writeln('Titel: ' , self . titelF );
Writeln('Interpret: ' , self .interpretF);
Writeln('Spieldauer: ', self .spieldauerF, ' min, ', self . titelanzahlF , ' Titel' );
Writeln('Preis: ', self .preisF :6:2, ' Euro');
Writeln('Kommentar: ', self.Kommentar);
Writeln;
end;
end.
unit VideoU;
interface
uses MediumU;
type
Video = class(Medium)
protected
regisseurF : String;
spieldauerF : Integer;
public
constructor create(titel, reg : String; zeit : Integer ; preis : Double);
property regisseur : String read regisseurF;
property spieldauer : Integer read spieldauerF;
procedure druckeInfo; override;
end;
implementation
constructor Video.create(titel, reg : String; zeit : Integer ; preis : Double);
begin
inherited create( titel , preis );
self .regisseurF := reg;
self .spieldauerF := zeit ;
end;
procedure Video.druckeInfo;
begin
Writeln('Code: ', self.codeF);
Writeln('Titel: ' , self . titelF );
Writeln('Regisseur: ', self .regisseurF );
Writeln('Spieldauer: ', self .spieldauerF, ' min');
Writeln('Preis: ', self .preisF :6:2, ' Euro');
Writeln('Kommentar: ', self.Kommentar);
Writeln;
end;
end.
unit DatenbankU;
interface
uses MediumU;
type
Datenbank = class
private
medien : array of Medium;
public
constructor create;
80
procedure erfasseMedium(med : Medium);
procedure auisten;
end;
implementation
constructor Datenbank.create();
begin
SetLength(self.medien,0);
end;
procedure Datenbank.erfasseMedium(med : Medium);
begin
SetLength(self.medien, Length(self.medien)+1);
self .medien[Length(self.medien)−1] := med;
end;
procedure Datenbank.auisten;
var i : Integer ;
begin
for i:=0 to Length(self.medien)−1 do
self .medien[i ]. druckeInfo;
end;
end.
program TestMedienDB;
{$APPTYPE CONSOLE}
uses
SysUtils,
MediumU in 'MediumU.pas',
BuchU in 'BuchU.pas',
DatenbankU in 'DatenbankU.pas',
CdU in 'CdU.pas',
VideoU in 'VideoU.pas';
var db : Datenbank;
begin
db := Datenbank.create;
db.erfasseMedium(Buch.create('Sprechen Sie Java', 'Hanspeter Moessenboeck', 'dpunkt.verlag', 2001, 28.00));
db.erfasseMedium(Cd.create('No Angel', 'Dido', 13, 90, 15.00));
db.erfasseMedium(Video.create('X−Men', 'Bryan Singer', 100, 21.00));
db. auisten ;
Readln
end.
81
B.3 Beispiel: geometrische Figuren
Abstrakte Methoden werden durch das Schüsselwort abstract als solche gekennzeichnet und im Implementationsteil ausgelassen. Für abstrakte Methoden wird durch virtual erzwungen, dass sie dynamisch gebunden werden.
{Datei: Figuren/Figur.pas}
unit FigurU;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls;
type
Figur = class
protected
xpos, ypos : Integer;
constructor Create(x,y : Integer);
public
function getTyp : String; virtual; abstract;
function getPosition : String;
function getLaengen : String; virtual; abstract;
function getFlaeche : Double; virtual; abstract;
procedure verschieben(dx, dy : Integer);
procedure strecken(faktor : Double); virtual; abstract;
procedure zeichnen(can : TCanvas); virtual; abstract;
end;
implementation
constructor Figur.create(x,y : Integer);
begin
self .xpos := x;
self .ypos := y;
end;
function Figur.getPosition : String;
begin
Result := ' ('+IntToStr(self.xpos)+';'+IntToStr(self.ypos)+')';
end;
procedure Figur.verschieben(dx, dy : Integer);
begin
self .xpos := self .xpos + dx;
self .ypos := self .ypos + dy;
end;
end.
{Datei: Figuren/RechteckU.pas}
unit RechteckU;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, FigurU;
type
Rechteck = class(Figur)
public
breite , hoehe : Double;
constructor create(x,y : Integer ; b,h : Double);
function getTyp : string; override;
function getLaengen : String; override;
function getFlaeche : Double; override;
procedure strecken(faktor : Double); override;
procedure zeichnen(canvas : TCanvas); override;
end;
implementation
constructor Rechteck.create(x,y: Integer; b,h : Double);
begin
inherited create(x,y);
self . breite := b;
self .hoehe := h;
end;
function Rechteck.getTyp : String;
begin
Result := 'Rechteck';
end;
function Rechteck.getLaengen : String;
begin
Result := ' Breite = '+FloatToStr(self.breite)+' Hoehe = '+FloatToStr(self.hoehe);
end;
82
function Rechteck.getFlaeche : Double;
begin
Result := self . breite * self .hoehe;
end;
procedure Rechteck.strecken(faktor : Double);
begin
self . breite := self . breite * faktor;
self .hoehe := self .hoehe * faktor;
end;
procedure Rechteck.zeichnen(canvas : TCanvas);
begin
canvas.Rectangle(self .xpos, self .ypos, self .xpos+Round(self.breite), self.ypos+Round(self.hoehe));
end;
end.
{Datei: Figuren/QuadratU.pas}
unit QuadratU;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, FigurU;
type
Quadrat = class(Figur)
protected
hoehe : Double;
public
constructor create(x, y : Integer ; h : Double);
function getTyp : string; override;
function getLaengen : String; override;
function getFlaeche : Double; override;
procedure strecken(faktor : Double); override;
procedure zeichnen(canvas : TCanvas); override;
end;
implementation
constructor Quadrat.create(x,y : Integer; h : Double);
begin
inherited create(x,y);
self .hoehe := h;
end;
function Quadrat.getTyp : String;
begin
Result := 'Quadrat';
end;
function Quadrat.getLaengen : String;
begin
Result := ' Hoehe = '+FloatToStr(self.hoehe);
end;
function Quadrat.getFlaeche : Double;
begin
Result := self .hoehe * self .hoehe;
end;
procedure Quadrat.strecken(faktor : Double);
begin
self .hoehe := self .hoehe * faktor;
end;
procedure Quadrat.zeichnen(canvas : TCanvas);
var hoehe : Integer;
begin
hoehe := Round(self.hoehe);
canvas.Rectangle(self .xpos, self .ypos, self .xpos+hoehe, self.ypos+hoehe);
end;
end.
83
{Datei: Figuren/KreisU.pas}
unit KreisU;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, FigurU;
type
Kreis = class(Figur)
protected
radius : Double;
public
constructor create(x,y : Integer ; r : Double);
function getTyp : string; override;
function getLaengen : String; override;
function getFlaeche : Double; override;
procedure strecken(faktor : Double); override;
procedure zeichnen(canvas : TCanvas); override;
end;
implementation
constructor Kreis.create(x,y : Integer ; r : Double);
begin
inherited create(x,y);
self .radius := r;
end;
function Kreis.getTyp : String;
begin
Result := 'Kreis' ;
end;
function Kreis.getLaengen : String;
begin
Result := ' Radius = '+FloatToStr(self.radius);
end;
function Kreis.getFlaeche : Double;
begin
Result := 3.14 * self .radius * self .radius;
end;
procedure Kreis.strecken(faktor : Double);
begin
self .radius := self .radius * faktor;
end;
procedure Kreis.zeichnen(canvas : TCanvas);
var radius : Integer;
begin
radius := Round(self.radius);
canvas.Ellipse ( self .xpos−radius, self .ypos−radius, self .xpos+radius, self .ypos+radius);
end;
end.
{Datei: Figuren/TestFigur1U.pas }
unit TestFigur1U;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, FigurU, RechteckU, QuadratU, KreisU, ExtCtrls;
type
TForm1 = class(TForm)
PaintBox1: TPaintBox;
procedure PaintBox1Paint(Sender: TObject);
private
{ Private−Deklarationen }
public
{ Public−Deklarationen }
end;
var
Form1: TForm1;
testQ : Quadrat;
testK : Kreis;
implementation
{$R *.dfm}
procedure TForm1.PaintBox1Paint(Sender: TObject);
var i : Integer;
begin
testQ := Quadrat.create(100,100,10);
testK := Kreis.create (500,200,80);
84
Canvas.Brush.Style := bsClear;
for i:= 0 to 18 do
begin
testQ.zeichnen(Canvas);
TestQ.strecken(1.2);
TestK.zeichnen(Canvas);
TestK.verschieben(10,0);
end;
end;
end.
{Datei: Figuren/TestFigur2.dpr}
program TestFigur2;
{$APPTYPE CONSOLE}
uses
SysUtils,
FigurU in 'FigurU.pas',
RechteckU in 'RechteckU.pas',
QuadratU in 'QuadratU.pas',
KreisU in 'KreisU.pas';
var test : array [0..2] of Figur;
i : Integer;
begin
test [0] := Rechteck.create(100,100,40,60);
test [1] := Quadrat.create(200,200,40);
test [2] := Kreis.create (300,300,50);
for i:=0 to 2 do
begin
Write(test[i ]. getTyp, '−−>');
Write(test[i ]. getPosition );
Write(test[i ]. getLaengen);
Writeln(' Flaeche = ', test[i ]. getFlaeche :8:2);
test [ i ]. strecken (1.5);
test [ i ]. verschieben(10,−5);
Write(test[i ]. getTyp, '−−>');
Write(test[i ]. getPosition );
Write(test[i ]. getLaengen);
Writeln(' Flaeche = ', test[i ]. getFlaeche :8:2);
Writeln;
end;
Readln;
end.
{ Datei: Figuren/TestFigur3U.pas}
unit TestFigur3U;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, FigurU, RechteckU, QuadratU, KreisU;
type
TForm1 = class(TForm)
PaintBox1: TPaintBox;
procedure PaintBox1Paint(Sender: TObject);
private
{ Private−Deklarationen }
public
{ Public−Deklarationen }
end;
var
Form1: TForm1;
test : array [0..5] of Figur;
i : Integer;
implementation
{$R *.dfm}
procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
test [0] := Rechteck.create(200,100,110,200);
test [1] := Quadrat.create(225,125,60);
test [2] := Rechteck.create(310,200,200,100);
test [3] := Rechteck.create(450,150,30,50);
test [4] := Kreis.create (280,300,50);
test [5] := Kreis.create (430,300,50);
for i:=0 to 5 do test[ i ]. zeichnen(Canvas);
end;
end.
85
Literatur
[Abt02] D. Abts: Grundkurs Java. Von den Grundlagen bis zu Datenbank- und Netzanwendungen, 3.
Auage, Vieweg, Braunschweig/Weisbaden, 2002.
[Bal99] H. Balzert: Lehrbuch der Objektmodellierung. Analyse und
Spektrum Akademischer Verlag, Heidelberg/Berlin, 1999.
Entwurf,
[BK03] D. J. Barnes/M. Kölling: Objektorientierte Programmierung mit Java.
Eine praxisnahe Einführung mit BlueJ, Pearson Studium, München, 2003.
[BP03] D. Bell/M. Parr: Java für Studenten. Grundlagen der Programmierung,
3. überarbeitete Auage, Pearson Studium, München, 2003.
[C+05] V. Chandrasekhara/A. Eberhart/H. Hellbrück/S. Kraus/U. Walther:
Java 5.0. Konzepte, Grundlagen und Erweiterungen in 5.0, Hanser Verlag, Wien, 2005.
[Cla98] U. Claussen: Objektorientiertes Programmieren. Mit
Auage, Springer Verlag, Berlin/Heidelberg, 1998.
Beispielen und Übungen in C++,
2.
[CH95] C. K. M. Crutzen/H.-W. Hein: Objektorientiertes Denken als didaktische Basis der Informatik, In: [Sch05, S. 149-158].
[Ebn00] M. Ebner: Delphi
5. nachschlagen und verstehen, Addison-Wesley, München, 2000.
[ER98] G. Ehmayer/S. Reich: Java in der Anwendungsentwicklung. Objektorientierung, Verteilung,
Datenbanken, dpunkt.verlag, Heidelberg, 1998.
[HM99] T. Himstedt/K. Mätzel: Mit Python programmieren. Einführung und Anwendung skriptorientierter Programmierung, dpunkt.verlag, Heidelberg, 1999.
[Lan00] I. van Laningham: Jetzt lerne ich Python. Der schnelle Einstieg in die wunderbare Welt der
Programmierung, Markt+Technik Verlag, München, 2000.
[Lis00] R. Lischner: Delphi in a Nutshell. A Desktop Quick Reference, O'Reilly, Sebastopol, 2000.
[LS96] A. Lüscher/A. Straubinger: Objektorientierte Technologien. Eine Einführung, vdf Hochschulverlag, Zürich, 1996.
[LA00] M. Lutz/D. Ascher: Einführung
[Mat04]
[Mös98]
in Python, O'Reilly, Köln, 2000.
W.-G. Matthäus: Grundkrus Programmieren mit Delphi, Vieweg, Wiesbaden, 2004.
H. Mössenböck: Objektorientierte Programmierung in Oberon-2, 3. Auage, Springer, Berlin/Heidelberg/New York, 1998.
[Mös01] H. Mössenböck: Sprechen sie Java?
dpunkt.verlag, Heidelberg, 2001.
Eine Einführung in das systematische Programmieren,
[M+97] J. R. Mühlbacher/B. Leisch/B. Kirk/U. Kreuzeder: Oberon-2.
Springer Verlag, Berlin/Heidelberg/New York, 1997.
Programming with Windows,
[Sch95] S. Schubert (Hrsg.): Innovative
[Zep04]
Konzepte für die Ausbildung, Springer Verlag, Berlin, 1995.
K. Zeppenfeld: Objektorientierte Programmiersprachen. Einführung und Vergleich von Java,
C++, C#, Ruby, Spektrum Akademischer Verlag, München, 2004.
[API1.4] API Spezikation für Java 2 Plattform Standard Edition v1.4.1.
[API1.5] API Spezikation für Java 2 Plattform Standard Edition 5.0.
86
Herunterladen