Objektorientiertes Programmieren in Cluster

1. Grundlegende Struktur

  Objekte entsprechen in Cluster in vielen Eigenschaften den Records. Dies
  drckt 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 Schlsselwort "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, nmlich
  "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 geprft werden:

  ...
    IF n IS BigNode THEN ... END;
  ...

  Wren "BigNode" und "Node" Records gewesen, wre der dynamische Typ
  "BigNode" bei der Zuweisung an n verloren gegangen. Eine umgekehrte
  Zuweisung "b:=n" wre illegal oder zumindest sehr fraglich.
  Bei Objekten wird zu jedem Objekt ein dynamischer Typ mitgefhrt, so da
  die Legalitt einer Zuweisung "b:=n" berprft werden kann. Auch eine
  Typumwandlung wie:

  ...
    WriteString(BigNode(n).name);
  ...

  kann ordnungsgem ausgefhrt werden, da der Compiler automatisch einen
  Typtest ins Programm einfgt (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. Klassenzugehrigkeit
  bezeichnet.

  Einen Nachteil haben Objekte gegenber Records, Objekte knnen 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 whrend der Programmausfhrung 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 Fhigkeiten seines Vorgngers und kann
  diesem neue hinzufgen. Eine Zuweisungskompatibilitt an seinen Vorgnger
  ist uneingeschrnkt gegeben, der umgekehrte Fall wird durch einen Typcheck
  whrend der Laufzeit gesichert.

  Beispiele fr 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 Zugehrigkeit zu einer Klasse kann whrend der Laufzeit durch den
  relationalen Operator "IS" geprft werden. Der Operator ist nur fr Objekte
  gestattet, deren statischer Typ in linearer Nachfolge-/Vorgngerbeziehung
  zur getesteten Klasse stehen.

  Beispiele fr 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 unmglich ein Nachfolger
                        von Flugzeug sein kann. (Die Funktion knnte auch
                        immer FALSE zurckgeben, 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 fr einen
  Bereich im Programm angepat werden:

    Auto(fz).raeder:=4;

    oder fr lngere 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 Einfhrung
  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 fragwrdig.


3. Methoden auf Objekte

  Auch (oder gerade) auf Objekte knnen Methoden erklrt werden. Diese mssen
  allerdings bereits bei der Typdeklaration angemeldet werden (Begrndung
  spter). Die Methoden mssen 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 fr 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, fr das die Methode aufgerufen wurde, ist unter dem
  Bezeichner SELF verfgbar:

  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 knnen 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 wrend der Laufzeit ausgefhrt werden, bestimmt der
  dynamische Typ eines Objekts (im Gegensatz zu Methoden bei Records, wo der
  statische Typ entscheidend ist). So wrde also fr 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" wrde 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 Schlsselwort "SUPER" haben redefinierte Methoden Zugriff auf
  Methoden der statischen Vorgngerklasse. 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
  fhrt zu einem Laufzeitfehler.

  [4] $$ObjectDefinition = OBJECT [OF qualident;]
                             {
                              (ident{,ident} : TypeDefinition;)|
                              ([DEFERRED] METHOD ident [ FormalParameter ];)
                             }
                           END

  Aufgeschobene Methoden knnen spter durch wirklich existierende Methoden
  redefiniert werden. Dies ergibt erst den Sinn dieser Methoden. Sie dienen
  dazu, ein Objekt mit Fhigkeiten zu schaffen, die auf anderen Fhigkeiten
  basieren, die sehr abstrakt gehalten werden knnen.

  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 wre ziemlich sinnlos, da jeder Aufruf einer seiner
  Methoden zu einem Laufzeitfehler fhren wrde. 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 funktionsfhige 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 zuweisungsfhig
  an Objekte (bzw. Objektzeiger) der ursprnglichen Klasse sind.

  Beispiel: ein Filter, der alle Nennungen von "Gott" in "jenes hhere 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 hhere 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 funktionsfhigen Streams
  zusammen, obwohl er kein Wissen ber deren vollstndige Implementierung
  trgt.

  Im obigen Beispiel knnte die Methode "WriteString" in "DosStream" aus
  Performancegrnden 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;

  Grundstzlich kann jede Methode bei jedem Erbvorgang durch eine neue
  ersetzt werden.

  Eine Containerklasse ist eine Klasse, die keine eigentliche Funktionalitt
  besitzt, und nur durch Beerben und Redefinition der aufgeschobenen Methoden
  sinnvoll wird.


3.3. Die Methoden "Construct" und "Destruct"

  Hufig bentigen Objekte Hilfsmittel, um ihre Fhigkeiten zu erhalten (z.B.
  das "FileHandle" des Objektes "DosStream"). Diese Hilfsmittel mssen 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 groe Unterschiede zu allen anderen normalen
  Methoden; erstens werden sie vom Laufzeitsystem aufgerufen, zweitens wird
  nicht die jngste Methode aufgerufen, sondern alle, die sich im Stammbaum
  angesammelt haben. Eine Con/Destruct Methode sollte sich also nur um Dinge
  kmmern, die direkt mit der aktuellen Ausbaustufe des Objekts
  zusammenhngen. 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 mssen, scheidet ein einfaches Allozieren von vorneherein
  aus. Objekte knnen 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 fr 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
  luft 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 drfen 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 fr sehr einfache Objekte
  verwendet werden, ihre weitere Existenz ist auerdem fraglich.

    in:=NEW(DosStream);
    out:=NEW(DosStream);
    DISPOSE(in);
    DISPOSE(out);

  Eine weitere Mglichkeit, 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 fr die
  Initialisierung bentigt werden, kann eine Klasse mehrere "CON-"/
  "DESTRUCTOREN" mit verschiedenen Parametern besitzen.

  Die Methoden "Con-"/"Destruct" werden vom Laufzeitsystem whrend 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 knnen auch die Verwaltung von Betriebsmitteln an bergeordnete
  Schichten verheimlichen.


5. Mehrfacherben (multiple inheritance)

  Oft ist es sinnvoll, wenn eine Klasse nicht nur von einer Vorgngerklasse
  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 Fhigkeiten eines
  "InputStream" mit denen eines "OutputStream" und ist auch
  zuweisungskompatibel zu beiden.

  Beispiele fr legale Anweisungen/Ausdrcke

  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 Vter
  taucht ein Bezeichner auf, der auch in einem anderen existiert, oder
  zweitens, eine Klasse ist zweimal Erbe derselben Klasse. (ACHTUNG, der
  Compiler fhrt zur Zeit noch keinen Test bei Mehrfacherben aus, es kann
  also unerkannt zu Problemen kommen. Dieser Mangel wird demnchst 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 fr 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 Mchtigkeit kommt
  erst durch Generizitt zum Tragen.)

  Eine andere Lsung wre die, da wenn eine Klasse in der Hierarchie mehrmals
  auftaucht, sie dennoch nur einmal vorhanden ist. Dies wre sehr sinnvoll fr
  verschiedene Anwendungen, wirft aber neue Probleme auf, die im Moment eine
  Implementation noch verzgern (z.B. wenn aus der bestehenden Hierarchie zwei
  verschiedene Methodenredefinitionen fr diese Klasse existieren).


6. Objekte und Generizitt

  Generizitt bedeutet, da Module mit abstrakten Datentypen definiert werden
  knnen, die dann fr tatschlich existierende Typen ausgeprgt werden. Die
  aktuellen Typen knnen dabei durch Forderungen (im allgemeinen geschieht
  dies durch eine Nachfolger-Vorgnger 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 mglich sind.

  Die Antwort ist einfach, Generizitt erhht die statische Sicherheit eines
  Programmes, vermeidet somit sowohl unntige Typchecks als auch mgliche
  Laufzeitfehler.

  Die Regeln fr 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;

  Ausprgung:

    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 Ausprgung:

    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 Ausprgung der beiden Listenknoten in "Edge" wird
  beim Aufruf von "InsertFirst"/"Remove" der richtige der beiden Knoten
  automatisch erkannt. Andernfalls mten die Knoten in der Kante ber die
  Qualifizierung bezeichnet werden.


7. Resourcetracking mit Objekten

  Objekte werden wie alle anderen Speicherstcke auch ber Resources
  alloziert. Die dazu verwendeten Funktionen sind jedoch gegenber dem
  Programmierer durch Constructoren und Destructoren versteckt. Objekte
  existieren relativ zu einem Kontext; bei dessen Vernichtung werden auch sie
  vernichtet. Um objektbezogene Resourcen (wie zustzlichen 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 Fllen nicht wnschenswert. Dafr
  besteht die Mglichkeit, den Kontext eines Objektes zu ndern, es also in
  einen anderen Existenzbereich umzuhngen. Dies wird vorlufig 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 Lsung beibehalten wird,
  zu einer Standardfunktion erhoben.


