home *** CD-ROM | disk | FTP | other *** search
/ Global Amiga Experience / globalamigaexperience.iso / compressed / development / clusterdemo.dms / clusterdemo.adf / Sprachreport.lha / OOPCluster < prev   
Text File  |  1993-03-08  |  28KB  |  861 lines

  1. Objektorientiertes Programmieren in Cluster
  2.  
  3. 1. Grundlegende Struktur
  4.  
  5.   Objekte entsprechen in Cluster in vielen Eigenschaften den Records. Dies
  6.   drückt sich auch in der Definition aus:
  7.  
  8.   [1] $$ObjectDefinition = OBJECT
  9.                              {ident{,ident} : TypeDefinition;}
  10.                            END
  11.  
  12.  
  13.   TYPE
  14.     Node    = POINTER TO NodeObj;
  15.     NodeObj = OBJECT
  16.                 prev,next : Node;
  17.               END;
  18.  
  19.   Der deutlichste syntaktische Unterschied liegt im Schlüsselwort "OBJECT" an
  20.   der Stelle von "RECORD". Der wichtigste semantische Unterschied besteht
  21.   darin, daß ein Objekt nicht nur einen statischen Typ, sondern auch einen
  22.   dynamischen Typ besitzt (ähnlich des Records in Oberon, aber weitergehend).
  23.   Dies wird bei Vererbungen deutlich:
  24.  
  25.   TYPE
  26.     BigNode = POINTER TO OBJECT OF Node;
  27.                 name : STRING(10);
  28.               END;
  29.  
  30.   VAR n : Node;
  31.       b : BigNode;
  32.  
  33.   ...
  34.     n:=b
  35.   ...
  36.  
  37.   Die Variablen "n" und "b" haben zwei verschiedene statische Typen, nämlich
  38.   "Node" und "BigNode". Sie haben nach der Zuweisung allerdings den selben
  39.   dynamischen (d.h. zur Laufzeit) Typen, "BigNode". Dies kann auch durch den
  40.   relationalen Operator IS geprüft werden:
  41.  
  42.   ...
  43.     IF n IS BigNode THEN ... END;
  44.   ...
  45.  
  46.   Wären "BigNode" und "Node" Records gewesen, wäre der dynamische Typ
  47.   "BigNode" bei der Zuweisung an n verloren gegangen. Eine umgekehrte
  48.   Zuweisung "b:=n" wäre illegal oder zumindest sehr fraglich.
  49.   Bei Objekten wird zu jedem Objekt ein dynamischer Typ mitgeführt, so daß
  50.   die Legalität einer Zuweisung "b:=n" überprüft werden kann. Auch eine
  51.   Typumwandlung wie:
  52.  
  53.   ...
  54.     WriteString(BigNode(n).name);
  55.   ...
  56.  
  57.   kann ordnungsgemäß ausgeführt werden, da der Compiler automatisch einen
  58.   Typtest ins Programm einfügt (dies kann bei fertigen Programmen durch
  59.   "$$TypeChk:=FALSE" unterbunden werden). Wie sich aber noch zeigen wird, ist
  60.   eine derartige Zuweisung dank der Verwendung dynamischen Bindens allerdings
  61.   sehr selten.
  62.  
  63.   Der Typ eines Objekts wird auch als Klasse bzw. Klassenzugehörigkeit
  64.   bezeichnet.
  65.  
  66.   Einen Nachteil haben Objekte gegenüber Records, Objekte können nicht als
  67.   Variable existieren, nur Zeiger auf Objekte sind erlaubt.
  68.  
  69.   Der statische Typ eines Objektes ist immer durch den Zeiger auf dieses
  70.   gegeben, der dynamische Typ (also der, den das Objekt nun wirklich hat) ist
  71.   im Objekt selbst kodiert. Der dynamische Typ eines Zeigers auf ein Objekt
  72.   kann sich während der Programmausführung durch Zuweisungen ändern, der
  73.   statische Typ ist immer fest und durch die Typdefinition im Programmtext
  74.   festgelegt. Der Compiler achtet darauf, daß der dynamische Typ eines
  75.   Objekts immer dem statischen entspricht, oder aber ein Nachfolger davon ist.
  76.  
  77.  
  78. 2. Einfacherben
  79.  
  80.   Einfacherben gehorcht der selben Syntax und Semantik wie bereits von Records
  81.   bekannt:
  82.  
  83.   [2] $$ObjectDefinition = OBJECT [OF qualident;]
  84.                              {ident{,ident} : TypeDefinition;}
  85.                            END
  86.  
  87.   TYPE
  88.     Fahrzeug  = POINTER TO OBJECT
  89.                   geschwindigkeit : REAL;
  90.                   gewicht         : REAL;
  91.                 END;
  92.     Flugzeug  = POINTER TO OBJECT OF Fahrzeug;
  93.                   triebwerke      : [0..12];
  94.                 END;
  95.     Auto      = POINTER TO OBJECT OF Fahrzeug;
  96.                   raeder          : [1..8];
  97.                 END;
  98.     VW        = POINTER TO OBJECT OF Auto;
  99.                   typ             : (Kaefer,Golf,Polo,Passat,Jetta...)
  100.                 END;
  101.  
  102.   Der Nachfolger erbt alle Elemente und Fähigkeiten seines Vorgängers und kann
  103.   diesem neue hinzufügen. Eine Zuweisungskompatibilität an seinen Vorgänger
  104.   ist uneingeschränkt gegeben, der umgekehrte Fall wird durch einen Typcheck
  105.   während der Laufzeit gesichert.
  106.  
  107.   Beispiele für Zuweisungen:
  108.  
  109.   VAR fz,fz2 : Fahrzeug;
  110.       fl     : Flugzeug;
  111.       au,au2 : Auto;
  112.       vw     : VW;
  113.  
  114.     fz:=fl   => richtig (Hierbei ändert sich der dynamische Typ von fz zu
  115.                          "Flugzeug", ist also ein Nachfolger des statischen
  116.                          Typs "Fahrzeug".)
  117.     fz:=au   => richtig
  118.     fz:=vw   => richtig
  119.     au:=vw   => richtig
  120.     fl:=fz   => laufzeittest
  121.     vw:=au   => laufzeittest
  122.     fl:=au   => falsch
  123.     fl:=vw   => falsch
  124.     vw:=fl   => falsch
  125.  
  126.   Die Zugehörigkeit zu einer Klasse kann während der Laufzeit durch den
  127.   relationalen Operator "IS" geprüft werden. Der Operator ist nur für Objekte
  128.   gestattet, deren statischer Typ in linearer Nachfolge-/Vorgängerbeziehung
  129.   zur getesteten Klasse stehen.
  130.  
  131.   Beispiele für Tests:
  132.  
  133.     fz  sei Fahrzeug
  134.     fz2 sei VW
  135.     fl  sei Flugzeug
  136.     au  sei Auto
  137.     au2 sei VW
  138.     vw  sei VW
  139.  
  140.     fz  IS Fahrzeug   : statisch wahr
  141.     fz2 IS Fahrzeug   : statisch wahr
  142.     vw  IS VW         : statisch wahr
  143.     vw  IS Fahrzeug   : statisch wahr
  144.     fz2 IS Auto       : dynamisch wahr
  145.     fz2 IS VW         : dynamisch wahr
  146.     au2 IS VW         : dynamisch wahr
  147.     fz2 IS Flugzeug   : dynamisch falsch
  148.     au  IS VW         : dynamisch falsch
  149.     au2 IS Flugzeug   : fehlerhafte Anweisung, da der statische Typ von
  150.                         au2 (also Auto) kein Nachfolger von Flugzeug ist,
  151.                         also sein dynamischer Typ unmöglich ein Nachfolger
  152.                         von Flugzeug sein kann. (Die Funktion könnte auch
  153.                         immer FALSE zurückgeben, doch deutet die Verwendung
  154.                         dieses Operators in diesem Zusammenhang eher auf
  155.                         einen Programmfehler hin, deshalb ist dies Verboten).
  156.  
  157.   Der statische Typ eines Objektes kann seinem dynamischen Typ für einen
  158.   Bereich im Programm angepaßt werden:
  159.  
  160.     Auto(fz).raeder:=4;
  161.  
  162.     oder für längere Zeit:
  163.  
  164.     WITH VW(fz) DO
  165.       fz.raeder:=4;
  166.       fz.typ:=Kaefer;
  167.     END;
  168.  
  169.   Damit ist im allgemeinen ein Laufzeitcheck verbunden (in wie weit dieser
  170.   Check bei einer Mehrpassimplementierung des Compilers vermieden werden
  171.   kann, wird noch untersucht). Eventuell kann dies auch durch die Einführung
  172.   einer TYPEKEY Struktur wie:
  173.  
  174.     IF TYPEKEY fz
  175.       OF VW       THEN
  176.                     fz.typ:=Kaefer
  177.                   END
  178.       OF Flugzeug THEN
  179.                     fz.triebwerke:=4
  180.                   END
  181.     ELSE
  182.       fz.gewicht:=-1.0; | Antigravantrieb :-)
  183.     END
  184.  
  185.   Die Implementierung steht allerdings noch aus. Auch ist der Sinn dieser
  186.   Struktur bei der Verwendung dynamischen Bindens ein wenig fragwürdig.
  187.  
  188.  
  189. 3. Methoden auf Objekte
  190.  
  191.   Auch (oder gerade) auf Objekte können Methoden erklärt werden. Diese müssen
  192.   allerdings bereits bei der Typdeklaration angemeldet werden (Begründung
  193.   später). Die Methoden müssen allerdings in einem Definitionsmodul nicht noch
  194.   einmal angegeben werden, es reicht hierbei die Angabe in der Typdefinition.
  195.  
  196.   [3] $$ObjectDefinition = OBJECT [OF qualident;]
  197.                              {
  198.                               (ident{,ident} : TypeDefinition;)|
  199.                               (METHOD ident [ FormalParameter ];)
  200.                              }
  201.                            END
  202.  
  203.       $$MethodImplementation = METHOD ident.ident [FormalParameter ];
  204.                                Block ident;
  205.  
  206.   Beispiel:
  207.  
  208.   TYPE
  209.     Lebewesen  = POINTER TO OBJECT
  210.                    alter : INTEGER;
  211.                    METHOD Altere(um : INTEGER := 1);
  212.                  END;
  213.  
  214.   METHOD Lebewesen.Altere(um : INTEGER);
  215.   BEGIN
  216.     ...
  217.   END Altere;
  218.  
  219.   Der Methodenaufruf erfolgt analog zu dem für Records:
  220.  
  221.   VAR le : Lebewesen;
  222.  
  223.   ...
  224.     le.Altere;le.Altere(10);
  225.   ...
  226.  
  227.   Die Elemente (Instanzvariablen) eines Objektes sind in der Implementation
  228.   der Methode unqualifiziert bekannt:
  229.  
  230.   METHOD Lebewesen.Altere(um : INTEGER);
  231.   BEGIN
  232.     INC(alter,um);
  233.   END Altere;
  234.  
  235.   Das Objekt selbst, für das die Methode aufgerufen wurde, ist unter dem
  236.   Bezeichner SELF verfügbar:
  237.  
  238.   TYPE
  239.     Lebewesen  = POINTER TO OBJECT
  240.                    alter : INTEGER;
  241.                    METHOD Altere(um : INTEGER := 1);
  242.                    METHOD AltereStark;
  243.                  END;
  244.  
  245.   METHOD Lebewesen.AltereStark;
  246.   BEGIN
  247.     SELF.Altere(10);
  248.   END AltereStark;
  249.  
  250.  
  251. 3.1. Methoden und Erben (dynamisches Binden)
  252.  
  253.   Methoden von Objekten können beim Erben redefiniert, d.h. durch neue
  254.   Methoden, die sich dann auf die geerbte Klasse beziehen, ersetzt werden.
  255.   Die Methode muß hierbei bei der Definition des neuen Typs mit angegeben
  256.   werden. Der Typ der neuen Methode muß mit dem der bestehenden
  257.   übereinstimmen.
  258.  
  259.   TYPE
  260.     Mensch     = POINTER TO OBJECT OF Lebewesen;
  261.                    haarfarbe : (schwarz,dunkel,grau,weiss);
  262.                    METHOD Altere(um : INTEGER);
  263.                  END;
  264.  
  265.   METHOD Mensch.Altere(um : INTEGER);
  266.   BEGIN
  267.     INC(alter,um);
  268.     IF KEY alter
  269.       OF  0.. 49 THEN haarfarbe:=schwarz END
  270.       OF 50.. 59 THEN haarfarbe:=dunkel  END
  271.       OF 60.. 69 THEN haarfarbe:=grau    END
  272.     ELSE
  273.       haarfarbe:=weiss
  274.     END;
  275.   END Altere;
  276.  
  277.   Welche der Methoden wärend der Laufzeit ausgeführt werden, bestimmt der
  278.   dynamische Typ eines Objekts (im Gegensatz zu Methoden bei Records, wo der
  279.   statische Typ entscheidend ist). So würde also für ein Objekt mit dem
  280.   dynamischen Typ Mensch und dem statischen Typ Lebewesen immer die Methode
  281.   mit der Haarfarbe aufgerufen.
  282.  
  283.   Beispiel:
  284.  
  285.   VAR le : Lebewesen;
  286.       me : Mensch;
  287.  
  288.   ...
  289.     le:=me;  | Lebewesen ist sicher ein Mensch
  290.     le.Altere;
  291.   ...
  292.  
  293.   Auch der Aufruf von "AltereStark" würde letztendlich in einem Aufruf der
  294.   Haarfarbmethode gipfeln, obwohl bei der Definition dieser Methode von der
  295.   Existenz von Menschen (und der damit verbundenen Haarprobleme) noch nichts
  296.   bekannt war.
  297.  
  298.   Mit dem Schlüsselwort "SUPER" haben redefinierte Methoden Zugriff auf
  299.   Methoden der statischen Vorgängerklasse. Dies ist sinnvoll, falls die neue
  300.   Methode eigentlich eine Erweiterung der bestehenden Methode darstellt.
  301.  
  302.   METHOD Mensch.Altere(um : INTEGER);
  303.   BEGIN
  304.     SUPER.Altere(um);
  305.     IF KEY alter
  306.       OF  0.. 49 THEN haarfarbe:=schwarz END
  307.       OF 50.. 59 THEN haarfarbe:=dunkel  END
  308.       OF 60.. 69 THEN haarfarbe:=grau    END
  309.     ELSE
  310.       haarfarbe:=weiss
  311.     END;
  312.   END Altere;
  313.  
  314.  
  315. 3.2. Aufgeschobene (deferred) Methoden und Containerklassen
  316.  
  317.   Eine aufgeschobene Methode ist eine solche, die zwar vereinbart, aber
  318.   absichtlich nicht implementiert wird. Ein Aufruf einer derartigen Methode
  319.   führt zu einem Laufzeitfehler.
  320.  
  321.   [4] $$ObjectDefinition = OBJECT [OF qualident;]
  322.                              {
  323.                               (ident{,ident} : TypeDefinition;)|
  324.                               ([DEFERRED] METHOD ident [ FormalParameter ];)
  325.                              }
  326.                            END
  327.  
  328.   Aufgeschobene Methoden können später durch wirklich existierende Methoden
  329.   redefiniert werden. Dies ergibt erst den Sinn dieser Methoden. Sie dienen
  330.   dazu, ein Objekt mit Fähigkeiten zu schaffen, die auf anderen Fähigkeiten
  331.   basieren, die sehr abstrakt gehalten werden können.
  332.  
  333.   TYPE
  334.     Stream  = POINTER TO OBJECT
  335.                 termChar : CHAR;
  336.                 DEFERRED METHOD Read(VAR c : CHAR);
  337.                 DEFERRED METHOD Write(c : CHAR);
  338.                 METHOD WriteString(REF s : STRING);
  339.                 METHOD ReadString(VAR s : STRING);
  340.                 METHOD WriteLn;
  341.               END;
  342.  
  343.   METHOD Stream.WriteString(REF s : STRING);
  344.   VAR i : INTEGER;
  345.   BEGIN
  346.     FOR i:=0 TO PRED(s.len) DO
  347.       SELF.Write(s.data[i]);
  348.     END;
  349.   END WriteString;
  350.  
  351.   METHOD Stream.WriteLn;
  352.   BEGIN
  353.     SELF.Write(ASCII.lf);
  354.   END WriteLn;
  355.  
  356.   METHOD Stream.ReadString(VAR s : STRING);
  357.   VAR i : INTEGER := 0;
  358.       c : CHAR;
  359.   BEGIN
  360.     SELF.Read(c);
  361.     WHILE c NOT OF ASCII.lf," ",ASCII.tab DO
  362.       ASSERT(i<s'MAX,RangeViolation);
  363.       s.data[i]:=c;
  364.       INC(i);
  365.       SELF.Read(c);
  366.     END;
  367.     termChar:=c;
  368.     s.data[i]:=ASCII.null;
  369.   END ReadString;
  370.  
  371.   Ein Objekt dieser Klasse wäre ziemlich sinnlos, da jeder Aufruf einer seiner
  372.   Methoden zu einem Laufzeitfehler führen würde. Ein Erbe dieser Klasse kann
  373.   allerdings durch Implementation von "Read" und/oder "Write" voll funktional
  374.   werden. Es erbt auch alle erweiterten Methoden wie "WriteString" etc.
  375.   Erst durch dieses Erben entsteht eine funktionsfähige Klasse.
  376.  
  377.   TYPE
  378.     DosStream = POINTER TO OBJECT OF Stream;
  379.                   fh : Dos.FileHandlePtr;
  380.                   METHOD Read(VAR c : CHAR);
  381.                   METHOD Write(c : CHAR);
  382.                 END;
  383.  
  384.   METHOD DosStream.Read(VAR c : CHAR);
  385.   VAR i : INTEGER;
  386.   BEGIN
  387.     i:=Dos.Read(fh,c'PTR,1);
  388.     IF KEY i
  389.       OF 0 THEN RAISE(Dos.EOF)       END
  390.       OF 1 THEN RAISE(Dos.ReadError) END
  391.     END
  392.   END Read;
  393.  
  394.   METHOD DosStream.Write(c : CHAR);
  395.   BEGIN
  396.     ASSERT(Dos.Write(fh,c'PTR,1)=1,Dos.WriteError);
  397.   END Write;
  398.  
  399.   Ein weiterer Vorteil liegt darin, daß Objekte dieser Klasse zuweisungsfähig
  400.   an Objekte (bzw. Objektzeiger) der ursprünglichen Klasse sind.
  401.  
  402.   Beispiel: ein Filter, der alle Nennungen von "Gott" in "jenes höhere Wesen,
  403.   das wir verehren" (frei nach "Murkes gesammeltes Schweigen") ersetzt.
  404.  
  405.   PROCEDURE MurkeFilter(in,out : Stream);
  406.   VAR s : STRING(100);
  407.   BEGIN
  408.     TRY
  409.       LOOP
  410.         in.ReadString(s);
  411.         IF Strings.Equal(s,"Gott") THEN
  412.           out.WriteString("jenes höhere Wesen, das wir verehren")
  413.         ELSE
  414.           out.WriteString(s);
  415.         END;
  416.         out.Write(in.termChar);
  417.       END;
  418.     EXCEPT
  419.       OF Dos.EOF THEN END
  420.     END;
  421.   END MurkeFilter;
  422.  
  423.   Dieser Filter arbeitet mit allen Arten von funktionsfähigen Streams
  424.   zusammen, obwohl er kein Wissen über deren vollständige Implementierung
  425.   trägt.
  426.  
  427.   Im obigen Beispiel könnte die Methode "WriteString" in "DosStream" aus
  428.   Performancegründen ebenfalls überdefiniert werden:
  429.  
  430.   METHOD DosStream.WriteString(REF s : STRING);
  431.   BEGIN
  432.     ASSERT(Dos.WriteString(fh,s.data'PTR,s.len)=s.len,Dos.WriteError);
  433.   END WriteString;
  434.  
  435.   Grundsätzlich kann jede Methode bei jedem Erbvorgang durch eine neue
  436.   ersetzt werden.
  437.  
  438.   Eine Containerklasse ist eine Klasse, die keine eigentliche Funktionalität
  439.   besitzt, und nur durch Beerben und Redefinition der aufgeschobenen Methoden
  440.   sinnvoll wird.
  441.  
  442.  
  443. 3.3. Die Methoden "Construct" und "Destruct"
  444.  
  445.   Häufig benötigen Objekte Hilfsmittel, um ihre Fähigkeiten zu erhalten (z.B.
  446.   das "FileHandle" des Objektes "DosStream"). Diese Hilfsmittel müssen bei
  447.   der Erzeugung des Objektes alloziert bzw. initialisiert und bei der
  448.   Vernichtung des Objektes wieder freigegeben werden. Dazu dienen die
  449.   parameterlosen Methoden "Construct" und "Destruct". Sie werden bei der
  450.   Erzeugung bzw. Vernichtung eines Objektes aufgerufen.
  451.  
  452.   Beispiel:
  453.  
  454.   TYPE
  455.     DosStream = POINTER TO OBJECT OF Stream;
  456.                   fh : Dos.FileHandlePtr;
  457.                   METHOD Read(VAR c : CHAR);
  458.                   METHOD Write(c : CHAR);
  459.                   METHOD Destruct;
  460.                 END;
  461.  
  462.   METHOD DosStream.Destruct;
  463.   BEGIN
  464.     IF fh#NIL THEN
  465.       Dos.Close(fh);fh:=NIL
  466.     END;
  467.   END Destruct;
  468.  
  469.   oder ein Filterobjekt, das jedem ASCII-Zeichen ein anderes zuordnen kann:
  470.  
  471.   TYPE
  472.     Filter    = POINTER TO OBJECT;
  473.                   table : POINTER TO ARRAY CHAR OF CHAR;
  474.                   METHOD SetFilter(from,to : CHAR);
  475.                   METHOD Translate(c : CHAR):CHAR;
  476.                   METHOD Construct;
  477.                   METHOD Destruct;
  478.                 END;
  479.  
  480.   METHOD Filter.SetFilter(from,to : CHAR);
  481.   BEGIN
  482.     table[from]:=to
  483.   END SetFilter;
  484.  
  485.   METHOD Filter.Translate(c : CHAR):CHAR;
  486.   BEGIN
  487.     RETURN table[c]
  488.   END Translate;
  489.  
  490.   METHOD Filter.Construct;
  491.   VAR c : CHAR;
  492.   BEGIN
  493.     Resources.New(table);
  494.     FOR c:=CHAR'MIN TO CHAR'MAX DO
  495.       table[c]:=c;
  496.     END;
  497.   END Construct;
  498.  
  499.   METHOD Filter.Destruct;
  500.   BEGIN
  501.     Resources.Dispose(table)
  502.   END Destruct;
  503.  
  504.   Diese Methoden haben zwei große Unterschiede zu allen anderen normalen
  505.   Methoden; erstens werden sie vom Laufzeitsystem aufgerufen, zweitens wird
  506.   nicht die jüngste Methode aufgerufen, sondern alle, die sich im Stammbaum
  507.   angesammelt haben. Eine Con/Destruct Methode sollte sich also nur um Dinge
  508.   kümmern, die direkt mit der aktuellen Ausbaustufe des Objekts
  509.   zusammenhängen. Spezielle Initialisierungen sollten im "CONSTRUCTOR"
  510.   vorgenommen werden (siehe 4).
  511.  
  512.  
  513. 4. Erzeugung und Vernichtung von Objekten
  514.  
  515.   Da bei der Erzeugung von Objekten im allgemeinen Initialisierungen
  516.   vorgenommen werden müssen, scheidet ein einfaches Allozieren von vorneherein
  517.   aus. Objekte können auf zwei Arten erzeugt werden, durch einen Constructor
  518.   (eine besondere Methode) oder durch die Standardprozedur "NEW" (evtl. nicht
  519.   mehr lange). Die Vernichtung erfolgt analog durch einen Destructor bzw.
  520.   "DISPOSE".
  521.  
  522.   [5] $$ObjectDefinition = OBJECT [OF qualident;]
  523.                              {
  524.                               (ident{,ident} : TypeDefinition;)|
  525.                               ([DEFERRED]
  526.                                 (METHOD|CONSTRUCTOR|DESTRUCTOR)
  527.                                 ident [ FormalParameter ];)
  528.                              }
  529.                            END
  530.  
  531.   Beispiel:
  532.  
  533.   TYPE
  534.     DosStream = POINTER TO OBJECT OF Stream;
  535.                   fh : Dos.FileHandlePtr;
  536.  
  537.                   CONSTRUCTOR Create(REF name : STRING);
  538.                   CONSTRUCTOR Open(REF name : STRING);
  539.                   DESTRUCTOR Close;
  540.  
  541.                   METHOD Read(VAR c : CHAR);
  542.                   METHOD Write(c : CHAR);
  543.                   METHOD Destruct;
  544.                 END;
  545.  
  546.  
  547.   METHOD DosStream.Create(REF name : STRING);
  548.   BEGIN
  549.     fh:=Dos.Open(name,Dos.newFile)
  550.     ASSERT(fh#NIL,Dos.ObjectNotFound);
  551.   END Create;
  552.  
  553.   METHOD DosStream.Open(REF name : STRING);
  554.   BEGIN
  555.     fh:=Dos.Open(name,Dos.oldFile)
  556.     ASSERT(fh#NIL,Dos.ObjectNotFound);
  557.   END Open;
  558.  
  559.   METHOD DosStream.Close;BEGIN END Close;
  560.  
  561.   Wird für ein (im allgemeinen noch nicht existierendes Object) ein
  562.   Constructor aufgerufen, wird dieses erzeugt (anhand des statischen Typs).
  563.   Danach wird die Constructor Methode (in diesem Fall z.B. "Open") aufgerufen,
  564.   die weitere Initialisierungen vornehmen kann. Alle Instanzvariablen eines
  565.   Objektes werden mit 0, NIL, FALSE etc. vorinitialisiert. Die Vernichtung
  566.   läuft umgekehrt ab, erst wird der Destructor aufgerufen, dann das Objekt
  567.   vernichtet. Im obigen Beispiel ist "Close" mehr pro forma deklariert, da
  568.   die eigentliche Vernichtung des FileHandles durch die Methode "Destruct"
  569.   vorgenommen wird.
  570.  
  571.   Eine Klasse kann beliebig viele Constructoren besitzen, auch dürfen diese
  572.   als einzige Methode durch Constructoren mit anderen Parametern überdefiniert
  573.   werden, da diese statisch (aus dem statischen Typ) ermittelt werden.
  574.  
  575.   Beispiel:
  576.  
  577.   VAR in,out : DosStream;
  578.  
  579.   BEGIN
  580.     in.Open("Rede die 1.");
  581.     out.Create("Rede die 2.");
  582.     MurkeFilter(in,out);
  583.     out.Close;
  584.     in.Close
  585.   END ...
  586.  
  587.   Die Erzeugung mit "NEW" und "DISPOSE" sollte nur für sehr einfache Objekte
  588.   verwendet werden, ihre weitere Existenz ist außerdem fraglich.
  589.  
  590.     in:=NEW(DosStream);
  591.     out:=NEW(DosStream);
  592.     DISPOSE(in);
  593.     DISPOSE(out);
  594.  
  595.   Eine weitere Möglichkeit, ein Objekt zu erzeugen, ist ein bestehendes zu
  596.   verdoppeln (Klonen). Dies kann durch die Standardfunktion "CLONE" geschehen.
  597.   (Allerdings wird wohl in Zukunft eine andere Technik verwendet werden).
  598.  
  599.     p:=CLONE(q);
  600.  
  601.  
  602. 4.1. Der Unterschied zwischen "Con-"/"Destruct" und "CON-"/"DESTRUCTOREN"
  603.  
  604.   Die "CON-"/"DESTRUCTOREN" werden vom Programmierer zur Erzeugung bzw.
  605.   Vernichtung eines Objektes eingesetzt. Da es mehrere Arten gibt, wie ein
  606.   Objekt erzeugt werden kann, und auch evtl. Parameter für die
  607.   Initialisierung benötigt werden, kann eine Klasse mehrere "CON-"/
  608.   "DESTRUCTOREN" mit verschiedenen Parametern besitzen.
  609.  
  610.   Die Methoden "Con-"/"Destruct" werden vom Laufzeitsystem während der
  611.   Initialisierung und Vernichtung aufgerufen. Sie dienen einer grundlegenden
  612.   Initialisierung bzw. einer Freigabe von Betriebsmitteln, wenn ein Objekt
  613.   unter Notfallbedingungen (z.B. Laufzeitfehler, verlassen eines Kontexts).
  614.   Sie können auch die Verwaltung von Betriebsmitteln an übergeordnete
  615.   Schichten verheimlichen.
  616.  
  617.  
  618. 5. Mehrfacherben (multiple inheritance)
  619.  
  620.   Oft ist es sinnvoll, wenn eine Klasse nicht nur von einer Vorgängerklasse
  621.   sondern von beliebig vielen erben kann.
  622.  
  623.   [6] $$ObjectDefinition = OBJECT [OF qualident{,qualident};]
  624.                              {
  625.                               (ident{,ident} : TypeDefinition;)|
  626.                               ([DEFERRED]
  627.                                 (METHOD|CONSTRUCTOR|DESTRUCTOR)
  628.                                 ident [ FormalParameter ];)
  629.                              }
  630.                            END
  631.  
  632.   Beispiel:
  633.  
  634.   TYPE
  635.     InputStream   = POINTER TO OBJECT
  636.                       termChar : CHAR;
  637.                       DEFERRED METHOD Read(VAR c : CHAR);
  638.                       METHOD ReadString(VAR s : STRING);
  639.                     END;
  640.     OutputStream  = POINTER TO OBJECT
  641.                       DEFERRED METHOD Write(c : CHAR);
  642.                       METHOD WriteString(s : STRING);
  643.                       METHOD WriteLn;
  644.                     END;
  645.     InOutStream   = POINTER TO OBJECT OF InputStream,OutputStream;
  646.                     END;
  647.  
  648.  
  649.   Ein Objekt der Klasse "InOutStream" vereinigt die Fähigkeiten eines
  650.   "InputStream" mit denen eines "OutputStream" und ist auch
  651.   zuweisungskompatibel zu beiden.
  652.  
  653.   Beispiele für legale Anweisungen/Ausdrücke
  654.  
  655.   VAR in  : InputStream;
  656.       out : OutputStream;
  657.       io  : InOutStream;
  658.  
  659.     ...
  660.     in:=io;
  661.     out:=io;
  662.  
  663.     IF in  IS InOutStream THEN ...
  664.     IF out IS InOutStream THEN ...
  665.     ...
  666.  
  667.     MurkeFilter(io,out);
  668.  
  669.     falls dieser definiert wird als
  670.  
  671.     PROCEDURE MurkeFilter(in  : InputStream;
  672.                           out : OutputStream);
  673.     ...
  674.  
  675.  
  676. 5.1. Probleme beim Mehrfacherben
  677.  
  678.   Schwierig wird Mehrfacherben, wenn in der Hierarchie gleiche Bezeichner
  679.   auftauchen. Dies kann auf zwei Arten geschehen, erstens, in einem der Väter
  680.   taucht ein Bezeichner auf, der auch in einem anderen existiert, oder
  681.   zweitens, eine Klasse ist zweimal Erbe derselben Klasse. (ACHTUNG, der
  682.   Compiler führt zur Zeit noch keinen Test bei Mehrfacherben aus, es kann
  683.   also unerkannt zu Problemen kommen. Dieser Mangel wird demnächst behoben.)
  684.  
  685.   Diesem Problem kann durch Qualifizierung beigekommen werden, dabei wird
  686.   einem oder mehreren Elternteilen ein qualifizierender Bezeichner
  687.   beigestellt, der bei der Referenzierung ihrer/s Elemente benutzt werden
  688.   kann.
  689.  
  690.   [7] $$ObjectDefinition = OBJECT [OF qualident [AS ident]
  691.                                     {,qualident [AS ident]};]
  692.                              {
  693.                               (ident{,ident} : TypeDefinition;)|
  694.                               ([DEFERRED]
  695.                                 (METHOD|CONSTRUCTOR|DESTRUCTOR)
  696.                                 ident [ FormalParameter ];)
  697.                              }
  698.                            END
  699.  
  700.   Beispiel: Knoten für eine Kreuzliste:
  701.  
  702.   TYPE
  703.     DoubleNode = POINTER TO OBJECT OF Node AS h,Node AS v; END;
  704.  
  705.   VAR dn : DoubleNode;
  706.  
  707.   Auf die Elemente der "DoubleNode" kann nun über die qualifizierenden
  708.   Bezeichner ".h" und ".v" zugegriffen werden. (Die volle Mächtigkeit kommt
  709.   erst durch Generizität zum Tragen.)
  710.  
  711.   Eine andere Lösung wäre die, daß wenn eine Klasse in der Hierarchie mehrmals
  712.   auftaucht, sie dennoch nur einmal vorhanden ist. Dies wäre sehr sinnvoll für
  713.   verschiedene Anwendungen, wirft aber neue Probleme auf, die im Moment eine
  714.   Implementation noch verzögern (z.B. wenn aus der bestehenden Hierarchie zwei
  715.   verschiedene Methodenredefinitionen für diese Klasse existieren).
  716.  
  717.  
  718. 6. Objekte und Generizität
  719.  
  720.   Generizität bedeutet, daß Module mit abstrakten Datentypen definiert werden
  721.   können, die dann für tatsächlich existierende Typen ausgeprägt werden. Die
  722.   aktuellen Typen können dabei durch Forderungen (im allgemeinen geschieht
  723.   dies durch eine Nachfolger-Vorgänger Bedingung) eingegrenzt werden.
  724.  
  725.   Es stellt sich die Frage, wozu generische Module, wenn doch der Typ eines
  726.   Objektes auch dynamisch ermittelt werden kann und somit also keine illegalen
  727.   Zuweisungen oder Verwendungen möglich sind.
  728.  
  729.   Die Antwort ist einfach, Generizität erhöht die statische Sicherheit eines
  730.   Programmes, vermeidet somit sowohl unnötige Typchecks als auch mögliche
  731.   Laufzeitfehler.
  732.  
  733.   Die Regeln für generische Module sind im Prinzip dieselben wie die bei
  734.   Records. Lediglich durch das Mehrfacherben ergibt sich eine Erweiterung.
  735.  
  736.   Beispiel:
  737.  
  738.   Definition:
  739.  
  740.     DEFINITION MODULE Lists ( Node : POINTER TO NodeObj);
  741.  
  742.       TYPE
  743.         NodeObj = OBJECT
  744.                     prev,next : Node;
  745.                   END;
  746.         List    = POINTER TO OBJECT
  747.                     first,last : Node;
  748.                     METHOD InsertFirst(n : Node);
  749.                     METHOD RemoveNode(n : Node);
  750.                   END;
  751.  
  752.     END Lists;
  753.  
  754.   Ausprägung:
  755.  
  756.     TYPE
  757.       TextNode = POINTER TO TextNodeObj;
  758.  
  759.     DEFINITION MODULE TextLists = Lists(TextNode);
  760.  
  761.     TYPE
  762.       TextNodeObj = OBJECT OF TextLists.Node;
  763.                       text : CLASSPTR TO STRING
  764.                     END;
  765.       TextList    = TextLists.List;
  766.  
  767.   Mehrfache Ausprägung:
  768.  
  769.     TYPE
  770.       Edge       = POINTER TO EdgeObj;
  771.  
  772.     DEFINITION MODULE FLists     = Lists(Edge.fnode);
  773.     DEFINITION MODULE TLists     = Lists(Edge.tnode);
  774.  
  775.     TYPE
  776.       Vertex     = POINTER TO OBJECT OF FLists.List AS from,
  777.                                         TLists.List AS to;
  778.                    END;
  779.       EdgeObj    = OBJECT OF FLists.Node AS fnode,
  780.                              TLIsts.Node AS tnode;
  781.                      from,to : Vertex;
  782.                      METHOD Add(from,to : Vertex);
  783.                      METHOD Remove;
  784.                    END;
  785.  
  786.      METHOD Edge.Add(from,to : Vertex);
  787.      BEGIN
  788.        from.from.InsertFirst(SELF);SELF.from:=from;
  789.        to.to.InsertFirst(SELF);SELF.to:=to;
  790.      END Add;
  791.  
  792.      METHOD Edge.Remove;
  793.      BEGIN
  794.        from.from.Remove(SELF);from:=NIL;
  795.        to.to.Remove(SELF);to:=NIL;
  796.      END Remove;
  797.  
  798.   Anhand der generischen Ausprägung der beiden Listenknoten in "Edge" wird
  799.   beim Aufruf von "InsertFirst"/"Remove" der richtige der beiden Knoten
  800.   automatisch erkannt. Andernfalls müßten die Knoten in der Kante über die
  801.   Qualifizierung bezeichnet werden.
  802.  
  803.  
  804. 7. Resourcetracking mit Objekten
  805.  
  806.   Objekte werden wie alle anderen Speicherstücke auch über Resources
  807.   alloziert. Die dazu verwendeten Funktionen sind jedoch gegenüber dem
  808.   Programmierer durch Constructoren und Destructoren versteckt. Objekte
  809.   existieren relativ zu einem Kontext; bei dessen Vernichtung werden auch sie
  810.   vernichtet. Um objektbezogene Resourcen (wie zusätzlichen Speicher oder
  811.   Systemelemente) immer mit dem Objekt (also auch bei Freigabe des Objekts
  812.   durch Kontextvernichtung) freizugeben, sollten "Destruct"-Methoden verwendet
  813.   werden (siehe auch Beispiel in 3.3).
  814.  
  815.   Ein anderes Problem besteht darin, daß Objekte immer zum aktuellen Kontext
  816.   erzeugt werden. Dies ist in manchen Fällen nicht wünschenswert. Dafür
  817.   besteht die Möglichkeit, den Kontext eines Objektes zu ändern, es also in
  818.   einen anderen Existenzbereich umzuhängen. Dies wird vorläufig durch die
  819.   Resources Funktion "ChangeObjContext" realisiert.
  820.  
  821.   Beispiel:
  822.  
  823.   TYPE
  824.     DosStream = POINTER TO OBJECT OF Stream;
  825.                   fh : Dos.FileHandlePtr;
  826.  
  827.                   CONSTRUCTOR Create(REF name : STRING;con : Context := NIL);
  828.                   CONSTRUCTOR Open(REF name : STRING;con : Context := NIL);
  829.                   DESTRUCTOR Close;
  830.  
  831.                   METHOD Read(VAR c : CHAR);
  832.                   METHOD Write(c : CHAR);
  833.                   METHOD Destruct;
  834.                 END;
  835.  
  836.  
  837.   METHOD DosStream.Create(REF name : STRING;con : Context);
  838.   BEGIN
  839.     IF con#NIL THEN
  840.       Resources.ChangeObjContext(SELF,con);
  841.     END;
  842.     fh:=Dos.Open(name,Dos.newFile)
  843.     ASSERT(fh#NIL,Dos.ObjectNotFound);
  844.   END Create;
  845.  
  846.   METHOD DosStream.Open(REF name : STRING;con : Context);
  847.   BEGIN
  848.     IF con#NIL THEN
  849.       Resources.ChangeObjContext(SELF,con);
  850.     END;
  851.     fh:=Dos.Open(name,Dos.oldFile)
  852.     ASSERT(fh#NIL,Dos.ObjectNotFound);
  853.   END Open;
  854.  
  855.   METHOD DosStream.Close;BEGIN END Close;
  856.  
  857.   Die Funktion "ChangeObjContext" wird, wenn diese Lösung beibehalten wird,
  858.   zu einer Standardfunktion erhoben.
  859.  
  860.  
  861.