4.11.1 Verdeckung von globalen Handlern durch Methoden
4.11.2 Nicht-Skriptobjekte als ancestor

4.11 Vererbung mit the ancestor

Bei rein objektorientierten Programmiersprachen wie Java ist Vererbung eine wichtige Technik bei der Erstellung eigener Klassen. Vererbung ist ein Programmiersprachenkonzept für die Umsetzung einer Relation zwischen einer Ober- und einer Unterklasse, wodurch Unterklassen die Eigenschaften ihrer Oberklassen mitbenutzen können.10 Die Unterklasse stellt dabei eine Spezialisierung der Oberklasse dar. Sie überschreibt Methoden der Oberklasse bzw. fügt eigene Methoden und Eigenschaften hinzu. Die Oberklasse ist analog dazu die Generalisierung der Unterklasse. In Java sind alle Klassen von der Klasse java.lang.Object abgeleitet. Dadurch entsteht ein Hierarchie aller Klassen, die sich nach unten hin immer weiter verzweigt.

Vererbungshierarchie der Java-Klasse Component im Package AWT 11

In Lingo besitzt Vererbung bei weitem nicht einen so hohen Stellenwert, sondern wird eher nur für Spezialfälle eingesetzt. Das Konzept ist weit dynamischer als das in streng objektorientierten Sprachen. Einem Objekt wird erst zur Laufzeit ein ancestor zugewiesen, dessen Methoden und Eigenschaften dann mitbenutzt werden können. Der ancestor kann jederzeit gewechselt oder gelöscht werden. Der Nachteil dieser Methode besteht darin, dass keine feste Struktur vorgegeben ist. Es kann daher nicht schon beim Kompilieren festgestellt werden, ob ein Objekt eine bestimmte Methode oder Eigenschaft implementiert, sondern erst zur Laufzeit. Im Gegensatz zu Java kann in Lingo auch nicht von Ober- oder Unterklasse geredet werden, da die ancestor-Eigenschaft nicht auf Klassen, sondern auf Instanzen verweist. Es ist also treffender, von Ober- und Unterobjekten zu reden, wobei der ancestor das Oberobjekt und das Objekt, das den ancestor definiert, das Unterobjekt ist.

Das Aufrufen einer Methode läuft in Lingo nach folgendem Schema ab:
Zuerst wird die Methode des beim Anruf angegebenen Objektes aufgerufen. Hat dieses Objekt keine passende Methode, prüft Lingo, ob ein ancestor definiert ist. Wenn der ancestor die aufzurufende Methode implementiert, wird diese ausgeführt, ansonsten die seines ancestors, und so weiter. Wenn keine passende Methode zu finden ist, wird in der Regel eine Fehlermeldung ausgegeben.

Folgendes Beispiel soll dies verdeutlichen. Das Script ZeitObjekt implementiert die Methoden laufzeit() und zeit():

on laufzeit me
put "Dieser Rechner läuft seit " &
(the milliseconds/1000) & " Sekunden"
end laufzeit

on zeit me
put "Es ist genau " & the long time
end zeit

Das Script DatumObjekt implementiert die Methoden datum() und zeit(). Als ancestor wird ein von ZeitObjekt abgeleitetes Objekt definiert:

property ancestor

on new me
ancestor = script("ZeitObjekt").new()
return me
end new

on datum me
put "Heute ist " & the date
end

on zeit me
put "Es ist " & the time
end zeit

Jetzt können die Methoden des ZeitObjektes ebenfalls aufgerufen werden:

datumObj = script("DatumObjekt").new()
datumObj.datum()
-- "Heute ist 24.04.00"
datumObj.laufzeit()
-- "Dieser Rechner läuft seit 324 Sekunden."

Folgendes Diagramm soll dies noch einmal verdeutlichen:

Die Methode zeit() des ancestors wird von der gleichnamigen Methode des DatumObjekts verdeckt. Will man diese Methode aufrufen, muss man sie über die ancestor-Eigenschaft adressieren:

datumObj.zeit()
--"Es ist 13:22"
datumObj.ancestor.zeit()
-- "Es ist genau 13:22:32"


Achtung
Die Objektreferenz me enthält immer die Objektreferenz des beim Aufruf angegebenen Objektes. Dadurch kann ein ancestor die Methoden des Unterobjektes aufrufen.

Folgende Methode im ZeitObjekt soll dies verdeutlichen:

on zeigeReferenz me
put me
me.zeit()
end zeigeReferenz

Diese Methode kann auch Methoden aus dem Unterobjekt aufrufen. Im folgenden wird aus dem ZeitObjekt die Methode zeit() des DatumObjekts aufgerufen:

datumObj = script("DatumObjekt").new()
put datumObj
-- <offspring "DatumObjekt" 2 1023435>
datumObj.zeigeReferenz()
-- <offspring "DatumObjekt" 2 1023435>
-- "Es ist 13:32"

Wird die Methode über der ancestor-Eigenschaft aufgerufen, enthält me eine Referenz auf das Oberobjekt. Deswegen können keine Methoden und Eigenschaften des DatumObjektes aufgerufen werden. In unserem Beispiel wird die zeit()-Methode des ZeitObjekts aufgerufen:

DatumObj.ancestor.zeigeReferenz()
-- <offspring "ZeitObjekt" 2 2345e34>
-- "Es ist genau 13:32:15"


Wichtig
Wird vom ancestor auf Eigenschaften eines Unterobjekts oder vom Unterobjekt auf Eigenschaften des ancestors zurückgegriffen, muss unbedingt me angegeben werden, also me.Eigenschaft (oder the Eigenschaft of me).

Im Unterkapitel "Das Director Event-Modell" wird ein Beispiel aus der Programmierpraxis angeführt, das sich genau diesen Umstand zunutze macht, um einen Director-Bug unter Windows zu umgehen.

Durch die dynamische Zuweisung der Oberobjekte ergeben sich zwei Möglichkeiten als Objektbeziehungen.

1. Jedes Objekt hat ein eigenes Oberobjekt.

Diese Art der Beziehung wird verwendet, wenn das Oberobjekt das Unterobjekt um spezifische Eigenschaften erweitert. Beispielsweise kann ein Objekt durch ein Animationsobjekt erweitert werden, das die jeweils sichtbare Phase zeigt.

2. Mehrere Objekte haben ein gemeinsames Oberobjekt.

Diese Beziehung ist sinnvoll, wenn zwei Objekte gemeinsame Daten benutzen sollen. Beispielsweise könnten zwei Objekte die gleiche Animation benutzen, die aber an unterschiedlichen Stellen auf der Bühne auftreten soll. Auf diese Weise wird Speicher gespart, da mehrere Objekte sich Ressourcen teilen können.

4.11.1 Verdeckung von globalen Handlern durch Methoden

Bei der Auswahl von Methodennamen ist darauf zu achten, dass es keine gleichlautenden globalen Handler gibt, die damit verdeckt werden. Wenn zum Beispiel ein Objekt die Methode string definiert, wird bei einem Aufruf von string(objekt) nicht der Lingo-Befehl string(), sondern, sondern die interne Methode ausgeführt. Schuld daran ist der Umstand, dass methode(objekt) und objekt.methode() für Lingo gleichwertige Ausdrücke sind. Da es kein Objekt gibt, das den Film oder die Laufzeitumgebung repräsentiert, kann man nicht deutlich machen, dass der globale Handler string und nicht die Methode string gemeint ist. Das Gleiche gilt auch für Eigenschaften: wird eine Eigenschaft namens ilk definiert, wird Objekt.ilk nicht den Typ der Variablen, sondern den Wert der Eigenschaft zurückliefern.

Dieses Problem ist übrigens nicht auf Objekte beschränkt. Wird in einer Eigenschaftliste die Eigenschaft #count definiert, liefert der Aufruf Eigenschaftliste.count nicht mehr die Anzahl der Listenelemente, sondern den Wert der Eigenschaft count zurück. Dieses Problem tritt nicht auf, wenn man statt der Eigenschaft count die Methode count() benutzt.

4.11.2 Nicht-Skriptobjekte als ancestor

Ab Director 7 ist es möglich, jedes beliebige Objekt zum ancestor eines Objektskriptes zu wählen. Auf diese Weise können Eigenschaften (und auch Methoden) von Director-Objekten geerbt werden.

Das Skript scaleSprite erweitert beispielsweise ein Sprite um die Methode scale(), die das Sprite um einen bestimmten Prozentsatz skaliert.

property ancestor

on new me, spriteNumber
-- Erzeugt ein skalierbares Sprite in Spritekanal spriteNumber
ancestor = sprite(spriteNumber)
return me
end new

on scale me, percent
-- Skaliert das Sprite auf percent Prozent der Originalgröße
me.width = me.member.width * percent / 100
me.height = me.member.height * percent / 100
end scale

Folgende Zeilen skalieren Sprite 3 um 200%:

meinSprite = sprite("scaleSprite").new(3)
meinSprite.scale(200)

Dabei bleibt der Zugriff auf alle Sprite-Eigenschaften erhalten:

put meinSprite.loc
-- point(320, 240)


Anmerkung
Prinzipiell wäre es auch denkbar, eine Liste als ancestor einzusetzen. Allerdings gibt es dann einen Konflikt mit der Methode count(); die Objektmethode, die die Anzahl der Eigenschaften zählt, würde die Listenmethode count(), die die Listeneinträge zählt, überdecken. Da beide Methoden unterschiedliche Aufgaben erfüllen, sollte dies jedoch nicht passieren. Es nützt auch nichts, eine eigene Implementierung der count()-Methode zu schreiben, denn der Konflikt würde bleiben: count() kann nur entweder die Anzahl der Eigenschaften oder die der Listenelemente zurückgeben.


4.10 Die Speicherverwaltung in Lingo 4.12 Beispiel 4: Warhol's Flowers