home *** CD-ROM | disk | FTP | other *** search
- Objektorientiertes Programmieren in Cluster
-
- 1. Grundlegende Struktur
-
- Objekte entsprechen in Cluster in vielen Eigenschaften den Records. Dies
- drückt sich auch in der Definition aus:
-
- [1] $$ObjectDefinition = OBJECT
- {ident{,ident} : TypeDefinition;}
- END
-
-
- TYPE
- Node = POINTER TO NodeObj;
- NodeObj = OBJECT
- prev,next : Node;
- END;
-
- Der deutlichste syntaktische Unterschied liegt im Schlüsselwort "OBJECT" an
- der Stelle von "RECORD". Der wichtigste semantische Unterschied besteht
- darin, daß ein Objekt nicht nur einen statischen Typ, sondern auch einen
- dynamischen Typ besitzt (ähnlich des Records in Oberon, aber weitergehend).
- Dies wird bei Vererbungen deutlich:
-
- TYPE
- BigNode = POINTER TO OBJECT OF Node;
- name : STRING(10);
- END;
-
- VAR n : Node;
- b : BigNode;
-
- ...
- n:=b
- ...
-
- Die Variablen "n" und "b" haben zwei verschiedene statische Typen, nämlich
- "Node" und "BigNode". Sie haben nach der Zuweisung allerdings den selben
- dynamischen (d.h. zur Laufzeit) Typen, "BigNode". Dies kann auch durch den
- relationalen Operator IS geprüft werden:
-
- ...
- IF n IS BigNode THEN ... END;
- ...
-
- Wären "BigNode" und "Node" Records gewesen, wäre der dynamische Typ
- "BigNode" bei der Zuweisung an n verloren gegangen. Eine umgekehrte
- Zuweisung "b:=n" wäre illegal oder zumindest sehr fraglich.
- Bei Objekten wird zu jedem Objekt ein dynamischer Typ mitgeführt, so daß
- die Legalität einer Zuweisung "b:=n" überprüft werden kann. Auch eine
- Typumwandlung wie:
-
- ...
- WriteString(BigNode(n).name);
- ...
-
- kann ordnungsgemäß ausgeführt werden, da der Compiler automatisch einen
- Typtest ins Programm einfügt (dies kann bei fertigen Programmen durch
- "$$TypeChk:=FALSE" unterbunden werden). Wie sich aber noch zeigen wird, ist
- eine derartige Zuweisung dank der Verwendung dynamischen Bindens allerdings
- sehr selten.
-
- Der Typ eines Objekts wird auch als Klasse bzw. Klassenzugehörigkeit
- bezeichnet.
-
- Einen Nachteil haben Objekte gegenüber Records, Objekte können nicht als
- Variable existieren, nur Zeiger auf Objekte sind erlaubt.
-
- Der statische Typ eines Objektes ist immer durch den Zeiger auf dieses
- gegeben, der dynamische Typ (also der, den das Objekt nun wirklich hat) ist
- im Objekt selbst kodiert. Der dynamische Typ eines Zeigers auf ein Objekt
- kann sich während der Programmausführung durch Zuweisungen ändern, der
- statische Typ ist immer fest und durch die Typdefinition im Programmtext
- festgelegt. Der Compiler achtet darauf, daß der dynamische Typ eines
- Objekts immer dem statischen entspricht, oder aber ein Nachfolger davon ist.
-
-
- 2. Einfacherben
-
- Einfacherben gehorcht der selben Syntax und Semantik wie bereits von Records
- bekannt:
-
- [2] $$ObjectDefinition = OBJECT [OF qualident;]
- {ident{,ident} : TypeDefinition;}
- END
-
- TYPE
- Fahrzeug = POINTER TO OBJECT
- geschwindigkeit : REAL;
- gewicht : REAL;
- END;
- Flugzeug = POINTER TO OBJECT OF Fahrzeug;
- triebwerke : [0..12];
- END;
- Auto = POINTER TO OBJECT OF Fahrzeug;
- raeder : [1..8];
- END;
- VW = POINTER TO OBJECT OF Auto;
- typ : (Kaefer,Golf,Polo,Passat,Jetta...)
- END;
-
- Der Nachfolger erbt alle Elemente und Fähigkeiten seines Vorgängers und kann
- diesem neue hinzufügen. Eine Zuweisungskompatibilität an seinen Vorgänger
- ist uneingeschränkt gegeben, der umgekehrte Fall wird durch einen Typcheck
- während der Laufzeit gesichert.
-
- Beispiele für Zuweisungen:
-
- VAR fz,fz2 : Fahrzeug;
- fl : Flugzeug;
- au,au2 : Auto;
- vw : VW;
-
- fz:=fl => richtig (Hierbei ändert sich der dynamische Typ von fz zu
- "Flugzeug", ist also ein Nachfolger des statischen
- Typs "Fahrzeug".)
- fz:=au => richtig
- fz:=vw => richtig
- au:=vw => richtig
- fl:=fz => laufzeittest
- vw:=au => laufzeittest
- fl:=au => falsch
- fl:=vw => falsch
- vw:=fl => falsch
-
- Die Zugehörigkeit zu einer Klasse kann während der Laufzeit durch den
- relationalen Operator "IS" geprüft werden. Der Operator ist nur für Objekte
- gestattet, deren statischer Typ in linearer Nachfolge-/Vorgängerbeziehung
- zur getesteten Klasse stehen.
-
- Beispiele für Tests:
-
- fz sei Fahrzeug
- fz2 sei VW
- fl sei Flugzeug
- au sei Auto
- au2 sei VW
- vw sei VW
-
- fz IS Fahrzeug : statisch wahr
- fz2 IS Fahrzeug : statisch wahr
- vw IS VW : statisch wahr
- vw IS Fahrzeug : statisch wahr
- fz2 IS Auto : dynamisch wahr
- fz2 IS VW : dynamisch wahr
- au2 IS VW : dynamisch wahr
- fz2 IS Flugzeug : dynamisch falsch
- au IS VW : dynamisch falsch
- au2 IS Flugzeug : fehlerhafte Anweisung, da der statische Typ von
- au2 (also Auto) kein Nachfolger von Flugzeug ist,
- also sein dynamischer Typ unmöglich ein Nachfolger
- von Flugzeug sein kann. (Die Funktion könnte auch
- immer FALSE zurückgeben, doch deutet die Verwendung
- dieses Operators in diesem Zusammenhang eher auf
- einen Programmfehler hin, deshalb ist dies Verboten).
-
- Der statische Typ eines Objektes kann seinem dynamischen Typ für einen
- Bereich im Programm angepaßt werden:
-
- Auto(fz).raeder:=4;
-
- oder für längere Zeit:
-
- WITH VW(fz) DO
- fz.raeder:=4;
- fz.typ:=Kaefer;
- END;
-
- Damit ist im allgemeinen ein Laufzeitcheck verbunden (in wie weit dieser
- Check bei einer Mehrpassimplementierung des Compilers vermieden werden
- kann, wird noch untersucht). Eventuell kann dies auch durch die Einführung
- einer TYPEKEY Struktur wie:
-
- IF TYPEKEY fz
- OF VW THEN
- fz.typ:=Kaefer
- END
- OF Flugzeug THEN
- fz.triebwerke:=4
- END
- ELSE
- fz.gewicht:=-1.0; | Antigravantrieb :-)
- END
-
- Die Implementierung steht allerdings noch aus. Auch ist der Sinn dieser
- Struktur bei der Verwendung dynamischen Bindens ein wenig fragwürdig.
-
-
- 3. Methoden auf Objekte
-
- Auch (oder gerade) auf Objekte können Methoden erklärt werden. Diese müssen
- allerdings bereits bei der Typdeklaration angemeldet werden (Begründung
- später). Die Methoden müssen allerdings in einem Definitionsmodul nicht noch
- einmal angegeben werden, es reicht hierbei die Angabe in der Typdefinition.
-
- [3] $$ObjectDefinition = OBJECT [OF qualident;]
- {
- (ident{,ident} : TypeDefinition;)|
- (METHOD ident [ FormalParameter ];)
- }
- END
-
- $$MethodImplementation = METHOD ident.ident [FormalParameter ];
- Block ident;
-
- Beispiel:
-
- TYPE
- Lebewesen = POINTER TO OBJECT
- alter : INTEGER;
- METHOD Altere(um : INTEGER := 1);
- END;
-
- METHOD Lebewesen.Altere(um : INTEGER);
- BEGIN
- ...
- END Altere;
-
- Der Methodenaufruf erfolgt analog zu dem für Records:
-
- VAR le : Lebewesen;
-
- ...
- le.Altere;le.Altere(10);
- ...
-
- Die Elemente (Instanzvariablen) eines Objektes sind in der Implementation
- der Methode unqualifiziert bekannt:
-
- METHOD Lebewesen.Altere(um : INTEGER);
- BEGIN
- INC(alter,um);
- END Altere;
-
- Das Objekt selbst, für das die Methode aufgerufen wurde, ist unter dem
- Bezeichner SELF verfügbar:
-
- TYPE
- Lebewesen = POINTER TO OBJECT
- alter : INTEGER;
- METHOD Altere(um : INTEGER := 1);
- METHOD AltereStark;
- END;
-
- METHOD Lebewesen.AltereStark;
- BEGIN
- SELF.Altere(10);
- END AltereStark;
-
-
- 3.1. Methoden und Erben (dynamisches Binden)
-
- Methoden von Objekten können beim Erben redefiniert, d.h. durch neue
- Methoden, die sich dann auf die geerbte Klasse beziehen, ersetzt werden.
- Die Methode muß hierbei bei der Definition des neuen Typs mit angegeben
- werden. Der Typ der neuen Methode muß mit dem der bestehenden
- übereinstimmen.
-
- TYPE
- Mensch = POINTER TO OBJECT OF Lebewesen;
- haarfarbe : (schwarz,dunkel,grau,weiss);
- METHOD Altere(um : INTEGER);
- END;
-
- METHOD Mensch.Altere(um : INTEGER);
- BEGIN
- INC(alter,um);
- IF KEY alter
- OF 0.. 49 THEN haarfarbe:=schwarz END
- OF 50.. 59 THEN haarfarbe:=dunkel END
- OF 60.. 69 THEN haarfarbe:=grau END
- ELSE
- haarfarbe:=weiss
- END;
- END Altere;
-
- Welche der Methoden wärend der Laufzeit ausgeführt werden, bestimmt der
- dynamische Typ eines Objekts (im Gegensatz zu Methoden bei Records, wo der
- statische Typ entscheidend ist). So würde also für ein Objekt mit dem
- dynamischen Typ Mensch und dem statischen Typ Lebewesen immer die Methode
- mit der Haarfarbe aufgerufen.
-
- Beispiel:
-
- VAR le : Lebewesen;
- me : Mensch;
-
- ...
- le:=me; | Lebewesen ist sicher ein Mensch
- le.Altere;
- ...
-
- Auch der Aufruf von "AltereStark" würde letztendlich in einem Aufruf der
- Haarfarbmethode gipfeln, obwohl bei der Definition dieser Methode von der
- Existenz von Menschen (und der damit verbundenen Haarprobleme) noch nichts
- bekannt war.
-
- Mit dem Schlüsselwort "SUPER" haben redefinierte Methoden Zugriff auf
- Methoden der statischen Vorgängerklasse. Dies ist sinnvoll, falls die neue
- Methode eigentlich eine Erweiterung der bestehenden Methode darstellt.
-
- METHOD Mensch.Altere(um : INTEGER);
- BEGIN
- SUPER.Altere(um);
- IF KEY alter
- OF 0.. 49 THEN haarfarbe:=schwarz END
- OF 50.. 59 THEN haarfarbe:=dunkel END
- OF 60.. 69 THEN haarfarbe:=grau END
- ELSE
- haarfarbe:=weiss
- END;
- END Altere;
-
-
- 3.2. Aufgeschobene (deferred) Methoden und Containerklassen
-
- Eine aufgeschobene Methode ist eine solche, die zwar vereinbart, aber
- absichtlich nicht implementiert wird. Ein Aufruf einer derartigen Methode
- führt zu einem Laufzeitfehler.
-
- [4] $$ObjectDefinition = OBJECT [OF qualident;]
- {
- (ident{,ident} : TypeDefinition;)|
- ([DEFERRED] METHOD ident [ FormalParameter ];)
- }
- END
-
- Aufgeschobene Methoden können später durch wirklich existierende Methoden
- redefiniert werden. Dies ergibt erst den Sinn dieser Methoden. Sie dienen
- dazu, ein Objekt mit Fähigkeiten zu schaffen, die auf anderen Fähigkeiten
- basieren, die sehr abstrakt gehalten werden können.
-
- TYPE
- Stream = POINTER TO OBJECT
- termChar : CHAR;
- DEFERRED METHOD Read(VAR c : CHAR);
- DEFERRED METHOD Write(c : CHAR);
- METHOD WriteString(REF s : STRING);
- METHOD ReadString(VAR s : STRING);
- METHOD WriteLn;
- END;
-
- METHOD Stream.WriteString(REF s : STRING);
- VAR i : INTEGER;
- BEGIN
- FOR i:=0 TO PRED(s.len) DO
- SELF.Write(s.data[i]);
- END;
- END WriteString;
-
- METHOD Stream.WriteLn;
- BEGIN
- SELF.Write(ASCII.lf);
- END WriteLn;
-
- METHOD Stream.ReadString(VAR s : STRING);
- VAR i : INTEGER := 0;
- c : CHAR;
- BEGIN
- SELF.Read(c);
- WHILE c NOT OF ASCII.lf," ",ASCII.tab DO
- ASSERT(i<s'MAX,RangeViolation);
- s.data[i]:=c;
- INC(i);
- SELF.Read(c);
- END;
- termChar:=c;
- s.data[i]:=ASCII.null;
- END ReadString;
-
- Ein Objekt dieser Klasse wäre ziemlich sinnlos, da jeder Aufruf einer seiner
- Methoden zu einem Laufzeitfehler führen würde. Ein Erbe dieser Klasse kann
- allerdings durch Implementation von "Read" und/oder "Write" voll funktional
- werden. Es erbt auch alle erweiterten Methoden wie "WriteString" etc.
- Erst durch dieses Erben entsteht eine funktionsfähige Klasse.
-
- TYPE
- DosStream = POINTER TO OBJECT OF Stream;
- fh : Dos.FileHandlePtr;
- METHOD Read(VAR c : CHAR);
- METHOD Write(c : CHAR);
- END;
-
- METHOD DosStream.Read(VAR c : CHAR);
- VAR i : INTEGER;
- BEGIN
- i:=Dos.Read(fh,c'PTR,1);
- IF KEY i
- OF 0 THEN RAISE(Dos.EOF) END
- OF 1 THEN RAISE(Dos.ReadError) END
- END
- END Read;
-
- METHOD DosStream.Write(c : CHAR);
- BEGIN
- ASSERT(Dos.Write(fh,c'PTR,1)=1,Dos.WriteError);
- END Write;
-
- Ein weiterer Vorteil liegt darin, daß Objekte dieser Klasse zuweisungsfähig
- an Objekte (bzw. Objektzeiger) der ursprünglichen Klasse sind.
-
- Beispiel: ein Filter, der alle Nennungen von "Gott" in "jenes höhere Wesen,
- das wir verehren" (frei nach "Murkes gesammeltes Schweigen") ersetzt.
-
- PROCEDURE MurkeFilter(in,out : Stream);
- VAR s : STRING(100);
- BEGIN
- TRY
- LOOP
- in.ReadString(s);
- IF Strings.Equal(s,"Gott") THEN
- out.WriteString("jenes höhere Wesen, das wir verehren")
- ELSE
- out.WriteString(s);
- END;
- out.Write(in.termChar);
- END;
- EXCEPT
- OF Dos.EOF THEN END
- END;
- END MurkeFilter;
-
- Dieser Filter arbeitet mit allen Arten von funktionsfähigen Streams
- zusammen, obwohl er kein Wissen über deren vollständige Implementierung
- trägt.
-
- Im obigen Beispiel könnte die Methode "WriteString" in "DosStream" aus
- Performancegründen ebenfalls überdefiniert werden:
-
- METHOD DosStream.WriteString(REF s : STRING);
- BEGIN
- ASSERT(Dos.WriteString(fh,s.data'PTR,s.len)=s.len,Dos.WriteError);
- END WriteString;
-
- Grundsätzlich kann jede Methode bei jedem Erbvorgang durch eine neue
- ersetzt werden.
-
- Eine Containerklasse ist eine Klasse, die keine eigentliche Funktionalität
- besitzt, und nur durch Beerben und Redefinition der aufgeschobenen Methoden
- sinnvoll wird.
-
-
- 3.3. Die Methoden "Construct" und "Destruct"
-
- Häufig benötigen Objekte Hilfsmittel, um ihre Fähigkeiten zu erhalten (z.B.
- das "FileHandle" des Objektes "DosStream"). Diese Hilfsmittel müssen bei
- der Erzeugung des Objektes alloziert bzw. initialisiert und bei der
- Vernichtung des Objektes wieder freigegeben werden. Dazu dienen die
- parameterlosen Methoden "Construct" und "Destruct". Sie werden bei der
- Erzeugung bzw. Vernichtung eines Objektes aufgerufen.
-
- Beispiel:
-
- TYPE
- DosStream = POINTER TO OBJECT OF Stream;
- fh : Dos.FileHandlePtr;
- METHOD Read(VAR c : CHAR);
- METHOD Write(c : CHAR);
- METHOD Destruct;
- END;
-
- METHOD DosStream.Destruct;
- BEGIN
- IF fh#NIL THEN
- Dos.Close(fh);fh:=NIL
- END;
- END Destruct;
-
- oder ein Filterobjekt, das jedem ASCII-Zeichen ein anderes zuordnen kann:
-
- TYPE
- Filter = POINTER TO OBJECT;
- table : POINTER TO ARRAY CHAR OF CHAR;
- METHOD SetFilter(from,to : CHAR);
- METHOD Translate(c : CHAR):CHAR;
- METHOD Construct;
- METHOD Destruct;
- END;
-
- METHOD Filter.SetFilter(from,to : CHAR);
- BEGIN
- table[from]:=to
- END SetFilter;
-
- METHOD Filter.Translate(c : CHAR):CHAR;
- BEGIN
- RETURN table[c]
- END Translate;
-
- METHOD Filter.Construct;
- VAR c : CHAR;
- BEGIN
- Resources.New(table);
- FOR c:=CHAR'MIN TO CHAR'MAX DO
- table[c]:=c;
- END;
- END Construct;
-
- METHOD Filter.Destruct;
- BEGIN
- Resources.Dispose(table)
- END Destruct;
-
- Diese Methoden haben zwei große Unterschiede zu allen anderen normalen
- Methoden; erstens werden sie vom Laufzeitsystem aufgerufen, zweitens wird
- nicht die jüngste Methode aufgerufen, sondern alle, die sich im Stammbaum
- angesammelt haben. Eine Con/Destruct Methode sollte sich also nur um Dinge
- kümmern, die direkt mit der aktuellen Ausbaustufe des Objekts
- zusammenhängen. Spezielle Initialisierungen sollten im "CONSTRUCTOR"
- vorgenommen werden (siehe 4).
-
-
- 4. Erzeugung und Vernichtung von Objekten
-
- Da bei der Erzeugung von Objekten im allgemeinen Initialisierungen
- vorgenommen werden müssen, scheidet ein einfaches Allozieren von vorneherein
- aus. Objekte können auf zwei Arten erzeugt werden, durch einen Constructor
- (eine besondere Methode) oder durch die Standardprozedur "NEW" (evtl. nicht
- mehr lange). Die Vernichtung erfolgt analog durch einen Destructor bzw.
- "DISPOSE".
-
- [5] $$ObjectDefinition = OBJECT [OF qualident;]
- {
- (ident{,ident} : TypeDefinition;)|
- ([DEFERRED]
- (METHOD|CONSTRUCTOR|DESTRUCTOR)
- ident [ FormalParameter ];)
- }
- END
-
- Beispiel:
-
- TYPE
- DosStream = POINTER TO OBJECT OF Stream;
- fh : Dos.FileHandlePtr;
-
- CONSTRUCTOR Create(REF name : STRING);
- CONSTRUCTOR Open(REF name : STRING);
- DESTRUCTOR Close;
-
- METHOD Read(VAR c : CHAR);
- METHOD Write(c : CHAR);
- METHOD Destruct;
- END;
-
-
- METHOD DosStream.Create(REF name : STRING);
- BEGIN
- fh:=Dos.Open(name,Dos.newFile)
- ASSERT(fh#NIL,Dos.ObjectNotFound);
- END Create;
-
- METHOD DosStream.Open(REF name : STRING);
- BEGIN
- fh:=Dos.Open(name,Dos.oldFile)
- ASSERT(fh#NIL,Dos.ObjectNotFound);
- END Open;
-
- METHOD DosStream.Close;BEGIN END Close;
-
- Wird für ein (im allgemeinen noch nicht existierendes Object) ein
- Constructor aufgerufen, wird dieses erzeugt (anhand des statischen Typs).
- Danach wird die Constructor Methode (in diesem Fall z.B. "Open") aufgerufen,
- die weitere Initialisierungen vornehmen kann. Alle Instanzvariablen eines
- Objektes werden mit 0, NIL, FALSE etc. vorinitialisiert. Die Vernichtung
- läuft umgekehrt ab, erst wird der Destructor aufgerufen, dann das Objekt
- vernichtet. Im obigen Beispiel ist "Close" mehr pro forma deklariert, da
- die eigentliche Vernichtung des FileHandles durch die Methode "Destruct"
- vorgenommen wird.
-
- Eine Klasse kann beliebig viele Constructoren besitzen, auch dürfen diese
- als einzige Methode durch Constructoren mit anderen Parametern überdefiniert
- werden, da diese statisch (aus dem statischen Typ) ermittelt werden.
-
- Beispiel:
-
- VAR in,out : DosStream;
-
- BEGIN
- in.Open("Rede die 1.");
- out.Create("Rede die 2.");
- MurkeFilter(in,out);
- out.Close;
- in.Close
- END ...
-
- Die Erzeugung mit "NEW" und "DISPOSE" sollte nur für sehr einfache Objekte
- verwendet werden, ihre weitere Existenz ist außerdem fraglich.
-
- in:=NEW(DosStream);
- out:=NEW(DosStream);
- DISPOSE(in);
- DISPOSE(out);
-
- Eine weitere Möglichkeit, ein Objekt zu erzeugen, ist ein bestehendes zu
- verdoppeln (Klonen). Dies kann durch die Standardfunktion "CLONE" geschehen.
- (Allerdings wird wohl in Zukunft eine andere Technik verwendet werden).
-
- p:=CLONE(q);
-
-
- 4.1. Der Unterschied zwischen "Con-"/"Destruct" und "CON-"/"DESTRUCTOREN"
-
- Die "CON-"/"DESTRUCTOREN" werden vom Programmierer zur Erzeugung bzw.
- Vernichtung eines Objektes eingesetzt. Da es mehrere Arten gibt, wie ein
- Objekt erzeugt werden kann, und auch evtl. Parameter für die
- Initialisierung benötigt werden, kann eine Klasse mehrere "CON-"/
- "DESTRUCTOREN" mit verschiedenen Parametern besitzen.
-
- Die Methoden "Con-"/"Destruct" werden vom Laufzeitsystem während der
- Initialisierung und Vernichtung aufgerufen. Sie dienen einer grundlegenden
- Initialisierung bzw. einer Freigabe von Betriebsmitteln, wenn ein Objekt
- unter Notfallbedingungen (z.B. Laufzeitfehler, verlassen eines Kontexts).
- Sie können auch die Verwaltung von Betriebsmitteln an übergeordnete
- Schichten verheimlichen.
-
-
- 5. Mehrfacherben (multiple inheritance)
-
- Oft ist es sinnvoll, wenn eine Klasse nicht nur von einer Vorgängerklasse
- sondern von beliebig vielen erben kann.
-
- [6] $$ObjectDefinition = OBJECT [OF qualident{,qualident};]
- {
- (ident{,ident} : TypeDefinition;)|
- ([DEFERRED]
- (METHOD|CONSTRUCTOR|DESTRUCTOR)
- ident [ FormalParameter ];)
- }
- END
-
- Beispiel:
-
- TYPE
- InputStream = POINTER TO OBJECT
- termChar : CHAR;
- DEFERRED METHOD Read(VAR c : CHAR);
- METHOD ReadString(VAR s : STRING);
- END;
- OutputStream = POINTER TO OBJECT
- DEFERRED METHOD Write(c : CHAR);
- METHOD WriteString(s : STRING);
- METHOD WriteLn;
- END;
- InOutStream = POINTER TO OBJECT OF InputStream,OutputStream;
- END;
-
-
- Ein Objekt der Klasse "InOutStream" vereinigt die Fähigkeiten eines
- "InputStream" mit denen eines "OutputStream" und ist auch
- zuweisungskompatibel zu beiden.
-
- Beispiele für legale Anweisungen/Ausdrücke
-
- VAR in : InputStream;
- out : OutputStream;
- io : InOutStream;
-
- ...
- in:=io;
- out:=io;
-
- IF in IS InOutStream THEN ...
- IF out IS InOutStream THEN ...
- ...
-
- MurkeFilter(io,out);
-
- falls dieser definiert wird als
-
- PROCEDURE MurkeFilter(in : InputStream;
- out : OutputStream);
- ...
-
-
- 5.1. Probleme beim Mehrfacherben
-
- Schwierig wird Mehrfacherben, wenn in der Hierarchie gleiche Bezeichner
- auftauchen. Dies kann auf zwei Arten geschehen, erstens, in einem der Väter
- taucht ein Bezeichner auf, der auch in einem anderen existiert, oder
- zweitens, eine Klasse ist zweimal Erbe derselben Klasse. (ACHTUNG, der
- Compiler führt zur Zeit noch keinen Test bei Mehrfacherben aus, es kann
- also unerkannt zu Problemen kommen. Dieser Mangel wird demnächst behoben.)
-
- Diesem Problem kann durch Qualifizierung beigekommen werden, dabei wird
- einem oder mehreren Elternteilen ein qualifizierender Bezeichner
- beigestellt, der bei der Referenzierung ihrer/s Elemente benutzt werden
- kann.
-
- [7] $$ObjectDefinition = OBJECT [OF qualident [AS ident]
- {,qualident [AS ident]};]
- {
- (ident{,ident} : TypeDefinition;)|
- ([DEFERRED]
- (METHOD|CONSTRUCTOR|DESTRUCTOR)
- ident [ FormalParameter ];)
- }
- END
-
- Beispiel: Knoten für eine Kreuzliste:
-
- TYPE
- DoubleNode = POINTER TO OBJECT OF Node AS h,Node AS v; END;
-
- VAR dn : DoubleNode;
-
- Auf die Elemente der "DoubleNode" kann nun über die qualifizierenden
- Bezeichner ".h" und ".v" zugegriffen werden. (Die volle Mächtigkeit kommt
- erst durch Generizität zum Tragen.)
-
- Eine andere Lösung wäre die, daß wenn eine Klasse in der Hierarchie mehrmals
- auftaucht, sie dennoch nur einmal vorhanden ist. Dies wäre sehr sinnvoll für
- verschiedene Anwendungen, wirft aber neue Probleme auf, die im Moment eine
- Implementation noch verzögern (z.B. wenn aus der bestehenden Hierarchie zwei
- verschiedene Methodenredefinitionen für diese Klasse existieren).
-
-
- 6. Objekte und Generizität
-
- Generizität bedeutet, daß Module mit abstrakten Datentypen definiert werden
- können, die dann für tatsächlich existierende Typen ausgeprägt werden. Die
- aktuellen Typen können dabei durch Forderungen (im allgemeinen geschieht
- dies durch eine Nachfolger-Vorgänger Bedingung) eingegrenzt werden.
-
- Es stellt sich die Frage, wozu generische Module, wenn doch der Typ eines
- Objektes auch dynamisch ermittelt werden kann und somit also keine illegalen
- Zuweisungen oder Verwendungen möglich sind.
-
- Die Antwort ist einfach, Generizität erhöht die statische Sicherheit eines
- Programmes, vermeidet somit sowohl unnötige Typchecks als auch mögliche
- Laufzeitfehler.
-
- Die Regeln für generische Module sind im Prinzip dieselben wie die bei
- Records. Lediglich durch das Mehrfacherben ergibt sich eine Erweiterung.
-
- Beispiel:
-
- Definition:
-
- DEFINITION MODULE Lists ( Node : POINTER TO NodeObj);
-
- TYPE
- NodeObj = OBJECT
- prev,next : Node;
- END;
- List = POINTER TO OBJECT
- first,last : Node;
- METHOD InsertFirst(n : Node);
- METHOD RemoveNode(n : Node);
- END;
-
- END Lists;
-
- Ausprägung:
-
- TYPE
- TextNode = POINTER TO TextNodeObj;
-
- DEFINITION MODULE TextLists = Lists(TextNode);
-
- TYPE
- TextNodeObj = OBJECT OF TextLists.Node;
- text : CLASSPTR TO STRING
- END;
- TextList = TextLists.List;
-
- Mehrfache Ausprägung:
-
- TYPE
- Edge = POINTER TO EdgeObj;
-
- DEFINITION MODULE FLists = Lists(Edge.fnode);
- DEFINITION MODULE TLists = Lists(Edge.tnode);
-
- TYPE
- Vertex = POINTER TO OBJECT OF FLists.List AS from,
- TLists.List AS to;
- END;
- EdgeObj = OBJECT OF FLists.Node AS fnode,
- TLIsts.Node AS tnode;
- from,to : Vertex;
- METHOD Add(from,to : Vertex);
- METHOD Remove;
- END;
-
- METHOD Edge.Add(from,to : Vertex);
- BEGIN
- from.from.InsertFirst(SELF);SELF.from:=from;
- to.to.InsertFirst(SELF);SELF.to:=to;
- END Add;
-
- METHOD Edge.Remove;
- BEGIN
- from.from.Remove(SELF);from:=NIL;
- to.to.Remove(SELF);to:=NIL;
- END Remove;
-
- Anhand der generischen Ausprägung der beiden Listenknoten in "Edge" wird
- beim Aufruf von "InsertFirst"/"Remove" der richtige der beiden Knoten
- automatisch erkannt. Andernfalls müßten die Knoten in der Kante über die
- Qualifizierung bezeichnet werden.
-
-
- 7. Resourcetracking mit Objekten
-
- Objekte werden wie alle anderen Speicherstücke auch über Resources
- alloziert. Die dazu verwendeten Funktionen sind jedoch gegenüber dem
- Programmierer durch Constructoren und Destructoren versteckt. Objekte
- existieren relativ zu einem Kontext; bei dessen Vernichtung werden auch sie
- vernichtet. Um objektbezogene Resourcen (wie zusätzlichen Speicher oder
- Systemelemente) immer mit dem Objekt (also auch bei Freigabe des Objekts
- durch Kontextvernichtung) freizugeben, sollten "Destruct"-Methoden verwendet
- werden (siehe auch Beispiel in 3.3).
-
- Ein anderes Problem besteht darin, daß Objekte immer zum aktuellen Kontext
- erzeugt werden. Dies ist in manchen Fällen nicht wünschenswert. Dafür
- besteht die Möglichkeit, den Kontext eines Objektes zu ändern, es also in
- einen anderen Existenzbereich umzuhängen. Dies wird vorläufig durch die
- Resources Funktion "ChangeObjContext" realisiert.
-
- Beispiel:
-
- TYPE
- DosStream = POINTER TO OBJECT OF Stream;
- fh : Dos.FileHandlePtr;
-
- CONSTRUCTOR Create(REF name : STRING;con : Context := NIL);
- CONSTRUCTOR Open(REF name : STRING;con : Context := NIL);
- DESTRUCTOR Close;
-
- METHOD Read(VAR c : CHAR);
- METHOD Write(c : CHAR);
- METHOD Destruct;
- END;
-
-
- METHOD DosStream.Create(REF name : STRING;con : Context);
- BEGIN
- IF con#NIL THEN
- Resources.ChangeObjContext(SELF,con);
- END;
- fh:=Dos.Open(name,Dos.newFile)
- ASSERT(fh#NIL,Dos.ObjectNotFound);
- END Create;
-
- METHOD DosStream.Open(REF name : STRING;con : Context);
- BEGIN
- IF con#NIL THEN
- Resources.ChangeObjContext(SELF,con);
- END;
- fh:=Dos.Open(name,Dos.oldFile)
- ASSERT(fh#NIL,Dos.ObjectNotFound);
- END Open;
-
- METHOD DosStream.Close;BEGIN END Close;
-
- Die Funktion "ChangeObjContext" wird, wenn diese Lösung beibehalten wird,
- zu einer Standardfunktion erhoben.
-
-
-