XProfan - Hilfe gemacht. Vielleicht ist es nützlich.
Code: Alles auswählen
1: Klassen - Objekte - Eigenschaften - Methoden
Wie ein Objekt mit Eigenschaften und Methoden beschrieben wird
Was ist ein Objekt? Nun, eigentlich alles: Ein Haus, ein Fernseher, ein Mensch, ein Hund, ein Fenster, eine Spielfigur, ein Programm, ein Dialogelement, eine Textzeile, ein Datum, ...
In der Programmierung wird ein solches Objekt als eine Sammlung aus Eigenschaften, auch Attribute genannt, und Methoden dargestellt. Ein Hund hat z.B. die Eigenschaften Geburtsdatum, Rasse, Farbe und Größe. Zu den Methoden gehören z.B. laufen, bellen, beißen und schwanzwedeln.
Was ist eine Klasse?
Es gibt natürlich schrecklich viele Hunde, die alle die genannten Eigenschaften und Methoden haben. Diese Beschreibung trifft auf alle Hunde zu. In der Programmierung nennt man diese Beschreibung eine Klasse. Die Klasse "Hund" beschreibt also welche Eigenschaften und Methoden ein Hund im Allgemeinen hat. Die Klasse kann daher als die Vorlage für die einzelnen Objekte betrachtet werden.
Klassen und Objekte in XProfan
In XProfan wird eine Klasse mit dem Befehl Class beschrieben:
Class Hund = Geburtsdatum$(10), \
Rasse$(32), \
Groesse%, \
bellen@
In diesem Beispiel habe ich die Eigenschaften und Methoden auf ein Minimum reduziert, um Tipparbeit zu sparen und es übersichtlicher zu machen. Zur Klassenbeschrebung gehört aber auch noch hinzu, wie die Methode "bellen" aussieht. Eine Methode ist nichts anderes als eine Funktion oder Prozedur, deren Name aus Klassenname und Methodenname mit einem Punkt verbunden besteht:
Proc Hund.bellen
Parameters text$
Print text$
EndProc
Hinweis: Bei Methodennamen wird das @ bei der Funktion nicht mitgeschrieben. Es dient bei der Definition der Klasse nur zur Kennzeichnung!
Wer schon länger mit Profan arbeitet, erkennt, dass eine Klassenbeschreibung einer Strukturbeschreibung ähnelt, mit dem Unterschied, dass Methoden hinzukommen.
Um jetzt von der Beschreibung zum Objekt zu kommen, benötigen wir zunächst eine Variable (Bezeichner), die für das neue Objekt steht. Wie bei Strukturen dient hierzu eine Bereichsvariable:
Declare Waldi#
Noch kann diese Variable sehr vieles beschreiben, sei es nun ein Bereich, eine Struktur oder ein Objekt. Wie bei Bereichen und Strukturen kommt hier der DIM-Befehl zum Einsatz:
Dim Waldi#, Hund
Und schon ist Waldi ein Hund. Technisch gesprochen: Das Objekt Waldi# ist eine Instanz der Klasse Hund. Waldi# hat die Eigenschaften und Methoden eines Hundes. Wir könnten nun beliebig viele weitere Hunde erzeugen. Nehmen wir also noch Bello# hinzu:
Declare Bello#
Dim Bello#, Hund
Da Bello# und Waldi# beides Hunde sind, haben sie die gleichen Eigenschaften und Methoden. Um Eigenschaften zuzuweisen und Methoden aufzurufen, verbinden wir Objektnamen und Eigenschaft, bzw. Methode mit einem Punkt:
Waldi#.Geburtsdatum$ = "10.01.1997"
Waldi#.Rasse$ = "Dackel"
Waldi#.Groesse% = 35
Mit WITH können wir das etwas vereinfachen:
With Bello#
.Geburtsdatum$ = "23.09.1985"
.Rasse$ = "Bernhardiner"
.Groesse% = 65
EndWith
Ebenso können wir nun auf diese Eigenschaften zugreifen und die Hunde bellen lassen:
With Waldi#
Print .Geburtsdatum$
Print .Rasse$
Print @Str$(.Groesse%) + " cm"
.bellen("WauWau!")
EndWith
With Bello#
Print .Geburtsdatum$
Print .Rasse$
Print @Str$(.Groesse%) + " cm"
.bellen("Knurr Kläff!")
EndWith
Zum Schluß bleibt uns vor dem Programmende nur das Aufräumen: Alle geDIMten Objekte sollten wieder mit Dispose aus dem Speicher entfernt werden:
Dispose Waldi#
Dispose Bello#
Wenn auch beim Programmende der vom Programm verwandte Speicher von Windows wieder freigegeben wird, so kann es doch bei lokalen Objekten in Prozeduren wichtig sein, den Speicher dieser Objekte vor Verlassen der Prozedur wieder freizugeben, damit das Programm nicht während des Betriebs ständig mehr Speicher verbraucht.
2: Kapselung - Klassen organisieren - Vererbung - Polymorphie
Kapselung - private und geschützte Eigenschaften und Methoden
Ein Vorteil objektorientierter Programmierung ist die Kapselung von zusammengehörenden Daten (Eigenschaften) und Funktionen (Methoden) in einer Klasse. Der Nutzer einer Klasse muss nicht wissen, was in einer Klasse vor sich geht oder wie sie aufgebaut ist. Ja, es ist sogar denkbar, dass der Hersteller einer Klasse diese an neuere technische Gegebenheiten oder ein anderes Betriebssysten anpasst, ohne dass ein Nutzer derselben auch nur eine Programmzeile ändern müßte. Wenn eine graphische Klasse z.B. die Methode "zeichne" hat, muss der Nutzer nur wissen, dass mit dieser Methode das von dieser Klasse erzeugte Objekt ausgegeben wird. Wie das nun technisch geschieht, ist für ihn uninteressant. Dafür sorgen Funktionen innerhalb der Klasse. Eine Klasse hat neben den Eigenschaften und Funktionen, die von außerhalb genutzt werden oft auch Eigenschaften und Funktonen, die nur für die Klasse selbst von Bedeutung sind. Diese werden als "private" gekennzeichnet. Von UML (Unified Modelling Language) hat XProfan die Kennzeichnung einer solchen Methode/Eigenschaft mit einem vorangestelltem "-" übernommen. Eine als "private" gekennzeichnete Eigenschaft oder Methode kann aber auch nicht vererbt werden. Besonders bei Eigenschaften möchte man oft, dass sie zwar vererbt werden können, aber trotzdem kein direkter Zugriff von "außerhalb" möglich ist. Hier wäre "private" also zu sehr eingeschränkt. Dafür gibt es die Kennzeichnung als "protected" (geschützt) mit dem UML-Kennzeichen "#". Die so gekennzeichneten Eigenschaften und Methode sind in XProfan nur innerhalb der Klasse und ihrer Nachfahren verwendbar.
Öffentliche Methoden und Eigenschaften werden gemäß UML mit einem "+" bezeichnet. Auch das ist in XProfan erlaubt, aber nicht nötig, da Methoden und Eigenschaften ohne Kennzeichen immer als öffentlich betrachtet werden.
Oftmals werden in objektorientierten Programmen Eigenschaften grundsätzlich als "protected" gekennzeichnet und der Zugriff nur über sogenannte Setter- und Getter-Methoden erlaubt. Set = Setzen und Get = Holen einer Eigenschaft. Unsere Klasse Hund sähe dann also so aus:
Class Hund = #Geburtsdatum$(10), \
#Rasse$(32), \
#Groesse%, \
setGeburtsdatum@, \
getGeburtsdatum@, \
setRasse@, \
getRasse@, \
setGroesse@, \
getGroesse@, \
bellen@
Hinzu kommen natürlich noch die entsprechenden Methoden. Als Beispiel hier die Getter- und Settermethoden für das Geburtsdatum:
Proc Hund.setGeburtsdatum
Parameters Datum$
.Geburtsdatum$ = Datum$
EndProc
Proc Hund.getGeburtsdatum
Return .Geburtsdatum$
EndProc
Es ist üblich, den Getter- und Settermethoden einen Namen zu geben, der dem Eigenschaftsnamen mit vorangestelltem set bzw. get entspricht, wobei der erste Buchstabe des Eigenschaftsnamens zur besseren Lesbarkeit groß geschrieben wird.
Der Zugriff auf die Eigenschaften und Methoden des Objektes erfolgt durch den Namen mit vorangestelltem Punkt. (In anderen Programmiersprachen wird stattdessen ein "self" vorangestellt.)
Das komplett angepaßte Programm "okurs2.prf".
Auf den ersten Blick sieht das ja nach gewaltig viel Zusatzarbeit aus. Wo ist da der Vorteil, außer der reinen Lehre von den Objekten Genüge zu tun? Zum einen ist der Mehraufwand nahezu ausschließlich beim Programmierer des Objektes. Im Programm, das das Objekt nutzt fällt es kaum ins Gewicht, wenn nun statt "bello#.groesse%" das unwesentlich längere "bello#.getGroesse()" steht. Und der Hauptvorteil liegt z.B. darin, dass nun nicht direkt die Eigenschaften beschrieben werden, sondern in den Getter- und Settermethoden noch weiterer Code eingebaut werden könnte, wie etwa Plausibilisierungen. So könnte überprüft werden, ob die Größe im passenden Bereich (hier 2-Byte-Integer) ist oder etwa zur Rasse paßt oder ob das Datum im richtigen Format ist, oder ... Die Klasse kann also völlig unabhängig vom Programm, das diese nutzt, verbessert und erweitert werden.
Klassen organisieren
Zur besseren Nutzung ist es empfehlenswert, je Klasse, oder Gruppe von zusammengehörigen Klassen, eine Include-Datei zu schreiben, die lediglich diese Klasse(n) enthält. Enthält die Includedatei eine Klasse, so sollte der Dateiname dem Klassennamen entsprechen. So teilen wir nun das Programm des letzten Abschnittes in die Klassendatei "hund.inc" und das Programm "okurs3.prf". Schließlich wollen wir ja von der Wiederverwertbarkeit der Klassen profitieren! Wenn wir jede Klasse in einer eigenen Datei haben, brauchen wir nur die Klassen einzubinden, die wir auch benötigen.
Wollten wir unsere genialen Klassen später anderen Programmierern zugänglich machen, ohne den Quellcode preiszugeben, so können wir auch aus den Includedateien recht einfach XProfan-Units machen, aber das ist ein anderes Thema ...
Vererbung
Ein wichtiger Vorteil der objektorientierten Programmierung ist die Vererbung. Das heißt, ich muss nicht jedes Mal das Rad neu erfinden, sondern kann auf Bewährtes zurückgreifen.Eine neue Klasse kann also alle Eigenschaften und Methoden einer bisherigen Klasse übernehmen und beliebig weitere Eigenschaften und Methoden hinzufügen.
Man könnte also z.B. eine Klasse "Lebewesen" schreiben, die diejenigen Eigenschaften und Methoden hat, die jedes Lebewesen auszeichnet. Eine neue Klasse "Tier" könnte nun all dieses erben, da ja jedes Tier ein Lebewesen ist. Ein Tier hat aber noich zusätzliche Eigenschaften und Methoden. Diese werden der Klasse "Tier" hinzugefügt. Jetzt könnte man sich eine weitere Klasse "Säugetier" denken, die alles von der Klasse "Tier" erbt und weitere Eigenschaften hinzufügt ...
Der Spieleprogrammierer könnte eine Klasse "Sprite" entwickeln, die alles kann, was Spielfiguren so können, z.B. auf dem Bildschirm angezeigt werden, sich bewegen, etc. Davon könnte er nun einzelne Klassen für die speziellen Objekte seines Spieles ableiten und müßte jeweils nur die neuen Eigenschaften und Methoden hinzufügen.
Zurück zu unserem Beispiel: Wir wollen eine Verwaltung unserer Hunde aufbauen und benötigen dazu neben den Eigenschaften und Methoden der Hunde noch weitere Eigenschaften und Methoden, nämlich den Namen des Besitzers und die entsprechenden Getter- und Setter-Methoden. Um eine Klasse zu erben, übernehmen wir den Namen dieser Klasse als ersten Eintrag in die Klassenbeschreibung:
$I hund.inc
Class hundekarte = hund, \
#besitzer$(60), \
setBesitzer@, \
getBesitzer@
Wichtig: Es kann nur von einer Klasse geerbt werden und diese muss an erster Stelle in der Klassenbeschreibung stehen!
Natürlich müssen wir auch noch noch die neuen Methoden schreiben:
Proc hundekarte.setBesitzer
Parameters name$
.besitzer$ = name$
EndProc
Proc hundekarte.getBesitzer
Return .besitzer$
EndProc
Die Eigenschaften und Methoden der Klasse "hund" sind durch die Vererbung in "hundekarte" bereits vorhanden und müssen nicht neu geschrieben werden. Innerhalb der neuen Methoden können wir auf die nicht privaten Eigenschaften und Methoden von "hund" genauso zugreifen, als stünden sie in "hundekarte".
Jetzt können wir die neue Klasse als "hundekarte.inc" abspeichern. Mit der Includezeile "$I hund.inc" wird die Klasse "hund" eingebunden. Anschließend können wir die neue Klasse in unserem Programm verwenden:
'-Begin-----------------------------------------------------------------
$I hundekarte.inc
Declare Waldi#, Bello#
Dim Waldi#,Hundekarte
Dim Bello#,Hundekarte
With Waldi#
.setGeburtsdatum("10.01.1997")
.setRasse("Dackel")
.setGroesse(35)
.setBesitzer("Klaus")
EndWith
With Bello#
.setGeburtsdatum("23.09.1985")
.setRasse("Bernhardiner")
.setGroesse(65)
.setBesitzer("Hugo")
EndWith
With Waldi#
Print .getGeburtsdatum()
Print .getRasse()
Print @Str$(.getGroesse()) + " cm"
Print .getBesitzer()
EndWith
With Bello#
Print .getGeburtsdatum()
Print .getRasse()
Print @Str$(.getGroesse()) + " cm"
Print .getBesitzer()
EndWith
WaitInput
Dispose Waldi#
Dispose Bello#
'-End-------------------------------------------------------------------
End
Listing: "okurs4.prf"
Polymorphie
Unter Polymorphie wird die Fähigkeit von Objekten verstanden, zur Laufzeit unterschiedliche Ausprägungen bzw. Formen annehmen zu können. D.h. dass der genaue Typ des übergebenen Parameters, an ein Objekt, zum Zeitpunkt der Kompilierung nicht bekannt ist.
Im folgenden Beispiel wird der Methode Out des Objektes Take ein anderes Objekt zur Verarbeitung übergeben.
'-Begin-----------------------------------------------------------------
'-Klasse PrintOut---Gibt einen Text auf dem Drucker wieder------------
Class PrintOut = DirectText@
Proc PrintOut.DirectText
Parameters Text$
StartPrint
DrawText 100, 100, Text$
EndPrint
EndProc
'-Klasse PaintOut---Gibt einen Text auf dem Bildschirm wieder---------
Class PaintOut = DirectText@
Proc PaintOut.DirectText
Parameters Text$
DrawText 100, 100, Text$
EndProc
'-Klasse Take---Polymorphe Klasse-------------------------------------
Class Take = Out@
Proc Take.Out
Parameters Object#, Text$
Object#.DirectText(Text$)
EndProc
'-Objekte aus Klassen ableiten und erzeugen (instanzieren)------------
Declare PrintOut#
Dim PrintOut#, PrintOut
Declare PaintOut#
Dim PaintOut#, PaintOut
Declare Take#
Dim Take#, Take
'-Main----------------------------------------------------------------
Cls
Take#.Out(PrintOut#, "Hello world on paper")
Take#.Out(PaintOut#, "Hello world on screen")
WaitInput
Dispose PrintOut#
Dispose PaintOut#
Dispose Take#
'-End-------------------------------------------------------------------
End
Natürlich können, statt wie in diesem Beispiel gezeigt, auch Zeiger (Pointer) auf andere Variablentypen übergeben werden.
Ein erweitertes Beispiel ist im Listing Polymorphie zu finden.
Polymorphie mit virtuellen Methoden
XProfan bietet keine explizite Kennzeichnung und Unterstützung von virtuellen Methoden an. Hier soll aber kurz beschrieben werden, wie virtuelle Methoden, im eingeschränkten Rahmen, 'simuliert' werden können.
Hinweis: Die Verantwortung für den Umgang mit virtuellen Methoden liegt vollständig beim Programmierer. Besonders beim Umgang mit Units ist darauf zu achten und hinzuweisen.
Virtuelle Methoden müssen in jedem folgenden Objekt der Hierarchie überschrieben werden. Es ist eine Möglichkeit, um einer Funktion einen Namen zu geben, der in der gesamten Objekthierarchie verwendet werden kann, wobei jedes Objekt diese in einer für sie angebrachten Weise implementiert.
Man kann eine virtuelle Methode erzeugen, indem man die Methode in der Klassenbeschreibung definiert und eine Prozedur beschreibt, die lediglich eine Fehlermeldung ausgibt. Das folgende Beispiel zeigt diese Vorgehensweise:
'-Klasse Out----------------------------------------------------------
Class Out = DirectText@
Proc Out.DirectText
@Messagebox("Eine virtuelle Methode kann nicht " + \
"aufgerufen werden!", "Fehler", 0)
EndProc
'-Klasse PrintOut-----------------------------------------------------
Class PrintOut = Out
Proc PrintOut.DirectText
Parameters Text$
StartPrint
DrawText 100, 150, Text$
EndPrint
EndProc
'-Klasse PaintOut-----------------------------------------------------
Class PaintOut = Out
Proc PaintOut.DirectText
Parameters Text$
DrawText 100, 150, Text$
EndProc
Einen kleinen Haken hat die Sache: Es würde erst beim Ausführen des Programmes auffallen, wenn hier bei einem Objekt versäumt wurde, eine virtuelle Methode zu überschreiben. Schöner wäre es natürlich, wenn es schon dem Compiler auffiele
3: Überschreiben - Überladen - Konstruktoren - Destruktoren
Überschreiben
Ok, jetzt haben wir unsere Klasse "hundekarte", aber als Windowsfreak gefällt es uns absolut nicht, dass der Autor der Klasse Hund das Bellen einfach so mittels PRINT ausgibt. Eine Messagebox wäre natürlich angebrachter. Was also tun?
Natürlich könnten wir jetzt in unserer Klasse "hundekarte" eine neue Methode hinzufügen, etwa mBellen, aber dann müßten wir alle Programme unserer Verwaltung umschreiben, die schon die Methode "bellen" benutzen. Das kann es wohl nicht sein. Dazu brauche ich keine Objektorientierung.
Natürlich könnte man auch die Klasse "hund" umschreiben und die Methode "bellen" dort verändern. Aber wie es der Teufel will, gibt es schon haufenweise andere Programme, die diese Klasse verwenden und darauf angewiesen sind, dass die Ausgabe per PRINT im Textmodus erfolgt. Die sich durch eine derartige Änderung ergebenden Seiteneffekte wären unabsehbar. Verspricht nicht gerade die Objektorientierung derartige Seiteneffekte zu vermeiden?
Wir wählen also die dritte Möglichkeit, das sogenannte "Überschreiben": Wenn wir in einer Unterklasse eine Methode der übergeordneten Klasse neuschreiben wollen, geben wir dieser einfach den gleichen Namen, wie in der übergeordneten Klasse. Damit wird die gleichnamige Methode der übergeordneten Klasse überschrieben, so dass alle Instanzen der neuen Klasse die neue Methode verwenden. So sieht unsere Klasse jetzt also aus:
$I hund.inc
CLASS hundekarte = hund, \
#besitzer$(60), \
setBesitzer@, \
getBesitzer@, \
bellen@
PROC hundekarte.setBesitzer
parameters name$
.besitzer$ = name$
ENDPROC
PROC hundekarte.getBesitzer
return .besitzer$
ENDPROC
PROC hundekarte.bellen
parameters text$
messagebox(text$,"Der Hund bellt:",48)
ENDPROC
Listing: "hund.inc"
Ganz nebenbei: Das "Überschreiben" funktioniert auch bei Eigenschaften! (Hier unterscheiden sich Objekte von Strukturen.) Würde ich z.B. etwas ausführlichere Rassenbeschreibungen benötigen, könnte ich z.B. einfach die Eigenschaft rasse$(64) hinzufügen und würde damit die kürzere Variante der übergeordneten Klasse überschreiben.
Als nächstes fällt uns noch ein, dass wir bei der Größenangabe mit "setGroesse" überprüfen wollten, dass diese auch im korrekten Bereich zwischen 20 und 120 cm ist. Kleinere und größere Hunde sind nicht zugelassen. Hier tut sich ein weiteres Problem auf. Wir wissen nicht mehr so genau, in welche Eigenschaft die ursprüngliche Klasse diese Eigenschaft speichert. Der Quellcode ist nicht verfügbar und Dokumentation gibt es auch keine. Was tun? Auch kein Problem: Wir rufen einfach in unserer Methode die gleichnamige Methode der übergeordneten Klasse auf, denn die weiß ja, was zu tun ist!
Die Klassenbeschreibung wird also um "setGroesse@" erweitert, die damit die gleichnamige Methode der übergeordneten Klasse überschreibt:
CLASS hundekarte = hund, \
#besitzer$(60), \
setBesitzer@, \
getBesitzer@, \
bellen@, \
setGroesse@
Und die Methodenprozedur wird geschrieben:
PROC hundekarte.setGroesse
parameters wert%
if wert% < 20
wert% = 20
elseif wert% > 120
wert% = 120
endif
.super.setGroesse(wert%)
ENDPROC
Das ".super." vor dem Methodennamen führt dazu, dass die Methode der übergeordneten Klasse aufgerufen wird.
Hinweis: Das "super" ist an die Syntax von Java angelehnt und macht deutlich, dass hier die Superklasse, de übergeordnete Klasse aufgerufen wird. In Delphi wird hierzu der Befehl "inherited" verwandt.
Überladen
Mit "überladen" meint man, dass es mehrere Methoden desselben Namens in einer Klasse geben kann, die sich nur durch Typ oder Anzahl der Parameter unterscheiden. Das ist nicht in allen Sprachen möglich und in XProfan nur bedingt. Aber es geht:
Nehmen wir also an, wir wollen, um möglichst flexibel zu bleiben, das Bellen in unserer Klasse "hundekarte" so einrichten, dass bei einem Parameter die gewohnte Methode mit PRINT aufgerufen wird und bei zwei Parametern die Messagebox, wober der zweite Parameter dann die Überschrift darstellt. Zwei Methoden eines Namens können nicht definiert werden (bzw. wenn es denn doch geschieht wird immer nur die erste verwandt). Aber in dieser Methode kann ich abhängig von der Parameterzahl unterschiedliche Routinen aufrufen, was ja auf das Gleiche herauskommt. Der Schlüssel dazu ist die Systemvariable %PCount (nicht zu verwechseln mit %ParCount), die die Anzahl der Parameter des Methoden- bzw. Prozeduraufrufes angibt. So könnte es also realisiert werden:
PROC hundekarte.bellen
if %pcount = 1
parameters text$
.super.bellen(text$)
elseif %pcount = 2
parameters text$,ueber$
messagebox(text$,ueber$,48)
endif
ENDPROC
Diese erweiterte Klasse "hundekarte.inc" und das erweiterte Programm "okurs5.prf".
Konstruktoren und Destruktoren
Ein Konstruktor ist eine Methode, die immer aufgerufen wird, wenn von einer Klasse eine Instanz (ein Objekt) erzeugt wird. Hier werden z.B. Eigenschafts-Variablen initialisiert und andere Grundeinstellungen vorgenommen. Sofern es nur um die Initialisierung der Eigenschaften geht, macht dies XProfan beim DIM automatisch. Wenn zur Initialisierung eines Objektes weiteres notwendig ist, muss man in XProfan eine entsprechende Methode schreiben. Damit XProfan eine Methode als Konstruktor erkennt, muß sie den gleichen Namen wie die Klasse haben. Wenn nun das Objekt nicht mit DIM, sondern mit @New erzeugt wird, wird diese Methode automatisch bei der Erstellung des Objektes ausgeführt. Natürlich kann diese Methode auch Parameter haben. Diese folgen dann dem Klassennamen im Aufruf der Funktion @New.
In unserem Beispiel könnte man sich einen Konstruktor vorstellen, der einem neu erzeugten Hund gleich die drei notwendigen Eigenschaften Geburtsdatum, Rasse und Größe zuweist:
Class Hund = Geburtsdatum$(10), \
Rasse$(32), \
Groesse%, \
hund@, \
bellen@
Proc Hund.hund
Parameters GebDat$, Rasse$, Gr%
.Geburtsdatum$ = GebDat$
.Rasse$ = Rasse$
.Groesse% = Gr%
EndProc
<...>
Jetzt kann man das Erzeugen eines neuen Hundes deutlich vereinfachen. Mit einer Programmzeile wird das Objekt erzeugt und die drei Eigenschaften zugewiesen:
Declare Waldi#, Bello#
Waldi# = New(Hund,"10.01.1997","Dackel",35)
Bello# = New(Hund,"23.09.1985","Bernhardiner",65)
Der Destruktor ist die Methode, die aufgerufen wird, wenn das Objekt beendet und sein Speicher freigegeben wird. Ebenso wie Java kennt XProfan keine Destruktoren. Die Speicherfreigabe erfolgt in XProfan mit dem Dispose eines Objektes (oder beim Ende des Programmes). Sind weitere "Aufräumarbeiten" notwendig, könnte man eine Methode "exit" hinzufügen, die man vor dem DISPOSE aufruft:
meinObjekt#.exit()
DISPOSE meinObjekt#
Hinweis: Auch in Java und C++ hat die Konstruktor-Methode immer den gleichen Namen wie die Klasse selbst und wird automatisch beim Anlegen des Objektes mit "new" ausgeführt. Diese Automatik gibt es auch Delphi, wobei hier jedoch bei der Methodendeclaration anstelle des Wortes "procedure" das Wort "constructor" steht. In Delphi gibt es auch Destruktoren, die durch das Wort "destructor" bezeichnet werden. Java kennt keine Destruktoren, da das Aufräumen des Speichers im Zuge einer "Garbage-Collection" automatisch geschieht.
4: Statische Eigenschaften und Methoden - Klasseneigenschaften - Klassenmethoden
Wie wir in den vorherigen Kursteilen gelernt haben, unterscheiden wir zwischen Klassen und Objekten:
Eine Klasse beschreibt alle Eigenschaften und Methoden eines Objektes. Wenn wir ein Objekt aus einer Klasse erstellen, so hat dieses Objekt alle Eigenschaften und Methoden dieser Klasse. Erstellen wir ein weiteres Objekt dieser Klasse, so hat es auch die gleichen Eigenschaften und Methoden. Es ist aber ein anderes, ein eigenständiges Objekt. Um in unserem Beispiel zu bleiben: Jeder Hund hat ein eigenes Alter, ein eigenes Bellen, eine eigene Rasse, etc.
Statische Eigenschaften = Klasseneigenschaften
Was aber, wenn uns jetzt die Anzahl der Hunde interessiert oder im zweiten Beispiel die Anzahl der Hunde-Karteikarten, die wir erzeugt haben? Auch dafür gibt es eine Lösung: eine statische Eigenschaft einer Klasse, oft auch Klassenvariable genannt. Um eine Klassenvariable zu erstellen, deklarieren wir sie genau wie eine globale Variable (was sie technisch gesehen auch ist) und setzen vor ihren Namen den Klassennamen, getrennt durch einen Punkt:
Declare hund.Anzahl&
In JAVA würde eine Klassenvariable durch das Schlüsselwort "static" gekennzeichnet.
Auf diese Variable können wir nun zugreifen wie auf jede andere globale Variable auch:
hund.Anzahl& = hund.Anzahl& + 1
Print hund.Anzahl&
Die Klassenvariable kann natürlich nur mit dem Klassennamen verwandt werden. In den Objekten einer Klasse sind die Klassenvariablen nicht als Eigenschaften bekannt. Ein Zugriff auf waldi#.Anzahl& würde zu einer Fehlermeldung führen.
Hinweis: Klassenvariablen werden nicht vererbt! Eine Klassenvariable hundekarte.Anzahl& gibt es in unserem Beispiel also nicht.
Statische Methoden - Klassenmethoden
Es wäre sicher eleganter, das Hochzählen der Anzahl mit einer Methode zu lösen. Hier gibt es prinzipiell zwei Möglichkeiten. Da die Klassenvariablen technisch gesehen ja globale Variablen sind, kann natürlich in den Methoden der Objekte darauf zugegriffen werden. Es wäre also denkbar in die Klasse hund eine Methode weitererHund aufzunehmen, um diese Variable hochzuzählen:
CLASS Hund = #Geburtsdatum$(10), \
#Rasse$(32), \
#Groesse%, \
setGeburtsdatum@, \
getGeburtsdatum@, \
setRasse@, \
getRasse@, \
setGroesse@, \
getGroesse@, \
bellen@, \
weitererHund@
Die Methode sähe dann so aus:
Proc hund.weitererHund
hund.Anzahl& = hund.Anzahl& + 1
EndProc
Da in dieser Methode aber auf keine Objekteigenschaften zugreift, können wir auf diese Methode auch "statisch" zugreifen, d.h. direkt über den Klassennamen. Folgende beiden Zeilen würden also identisch wirken:
waldi#.weitererHund()
hund.weitererHund()
Was passiert mit dieser Methode aber nun in Objekten der Klasse hundekarte, die ja direkt von hund abgeleitet ist?
kartewaldi#.weitererHund()
hundekarte.weitererHund()
Die erste Zeile funktioniert offensichtlich, die zweite aber nicht, da Klassenmethoden ebensowenig wie Klassenvariablen vererbt werden. Aber halt: Wenn Klassenvariablen nicht vererbt werden, was wird dann in der ersten Zeile hochgezählt?
Wer nicht aus dem Auge verloren hat, dass Klassenvariablen technisch gesehen globale Variablen sind, wird es wissen: hund.Anzahl& wird hochgezählt, was aber ziemlich sicher nicht gewünscht ist.
Um derartige Probleme zu vermeiden gibt es zwei Möglichkeiten: Zum einen sollte man Methoden, die auf Klassenvariablen schreibend zugreifen grundsätzlich als "private" kennzeichnen. Zum anderen kann man Methoden, die überhaupt nicht auf Objekteigenschaften oder andere Objektmethoden zurückgreifen, ausschließlich als Klassenmethoden definieren, indem man sie in der Klassenbeschreibung einfach wegläßt. Schließlich ist die Klassenmethode ebenso wie eine Klassenvariable schon durch ihren Namen der Klasse zugeordnet. Damit sähe unsere erweiterte Hundeklasse (hund.inc) nun so aus:
'-Begin-----------------------------------------------------------------
Class Hund = #Geburtsdatum$(10), \
#Rasse$(32), \
#Groesse%, \
setGeburtsdatum@, \
getGeburtsdatum@, \
setRasse@, \
getRasse@, \
setGroesse@, \
getGroesse@, \
bellen@
Declare Hund.Anzahl&
Proc Hund.setGeburtsdatum
Parameters Datum$
.Geburtsdatum$ = Datum$
EndProc
Proc Hund.getGeburtsdatum
Return .Geburtsdatum$
EndProc
Proc Hund.setRasse
Parameters Rasse$
.Rasse$ = Rasse$
EndProc
Proc Hund.getRasse
Return .Rasse$
EndProc
Proc Hund.setGroesse
Parameters cm%
.Groesse% = cm%
EndProc
Proc Hund.getGroesse
Return .Groesse%
EndProc
Proc Hund.bellen
Parameters text$
Print text$
EndProc
Proc Hund.weitererHund
Hund.Anzahl& = Hund.Anzahl& + 1
EndProc
'-End-------------------------------------------------------------------
Hinweis: Wenn man einen Konstruktor verwendet (siehe vorherigen Kursteil) könnte man die Zeile, die Hund.Anzahl& um 1 hochzählt natürlich auch in diesem unterbringen, so dass die Anzahl beim Erzeugen eines Hundes automatisch hochgezählt wird.
Und dieses wäre das erweiterte Programm (okurs3a.prf):
'-Begin-----------------------------------------------------------------
'-IncludeFiles--------------------------------------------------------
$I hund.inc
'---------------------------------------------------------------------
Declare Waldi#
Dim Waldi#,Hund
Hund.weitererHund()
Declare Bello#
Dim Bello#,Hund
Hund.weitererHund()
Waldi#.setGeburtsdatum("10.01.1997")
Waldi#.setRasse("Dackel")
Waldi#.setGroesse(35)
With Bello#
.setGeburtsdatum("23.09.1985")
.setRasse("Bernhardiner")
.setGroesse(65)
EndWith
With Waldi#
Print .getGeburtsdatum()
Print .getRasse()
Print @Str$(.getGroesse()) + " cm"
.bellen("WauWau!")
EndWith
With Bello#
Print .getGeburtsdatum()
Print .getRasse()
Print @Str$(.getGroesse()) + " cm"
.bellen("Knurr Kläff!")
EndWith
Print "Anzahl der Hunde: " + @Str$(Hund.Anzahl&)
WaitInput
'-End-------------------------------------------------------------------
End
Noch einmal zur Erinnerung
Klasseneigenschaften und Klassenmethoden sind Eigenschaften (Variablen) und Methoden, die für die gesamte Klasse unabhängig von gebildeten Objekten gelten. Sie werden wie globale Variablen und Prozeduren bzw. Funktionen behandelt und aufgerufen. Sie werden nicht vererbt und tauchen daher auch nicht in der Klassenbeschreibung durch den Befehl Class auf.
Objekteigenschaften und Objektmethoden sind jene Eigenschaften und Methoden, die in der Klassenbeschreibung (Class = ...) der Klasse stehen, aus der das Objekt gebildet wurde. Diese können nur im Zusammenhang mit einem Objekt benutzt werden. Jedes Objekt, das von dieser Klasse gebildet wird, hat diese Eigenschaften und Methoden.
Objektmethoden, die nicht auf andere Objekteigenschaften oder -methoden zugreifen, können auch statisch, d.h. wie eine Klassenmethode aufgerufen werden.