Oberon-2

Hasznos technikák

4.1 Objektumok inicializálása

A legtöbb objektumot mielõtt használjuk inicializálni kell. Az adatmezõi értéket kell hogy kapjanak, és gyakran segédobjektumokat is létre kell hoznunk, amelyek az objektum mûködéséhez szükségesek.

Inicializáló eljárások

Az Oberon-2-nek nincs speciális nyelvi eszköze az objektumok inicializálására, közönséges eljárásokat használ helyettük. Minden T osztályhoz egy InitT nevû eljárás kell írnunk, amely kezeli az összes inicializálással kapcsolatos feladatot. Ha T deklarációja ilyen:
  TYPE
    T = POINTER TO TDesc;
    TDesc = RECORD
   x: INTEGER;
    y: REAL;
   END;

akkor az inicializáló eljárás:
  PROCEDURE InitT(t:T; x: INTEGER; y: REAL);
  BEGIN
  t.x:=x; t.y:=y;
  END InitT;

Minden újonnan készített T objektumot inicializálni kell az InitT-vel:
  NEW(t); InitT(t,x,y);

Az inicializáló eljárások jobbak, mint a metódusok

Azt ajánljuk, hogy az InitT-t inkább eljárásként, mint metódusként implementáljuk. Tegyük fel, hogy létezik egy T1 származtatott osztály is:
TYPE
  T1 = POINTER TO TDesc1;
  TDesc 1 = RECORD(TDesc)
  z: CHAR;
  END;
Ha az InitT egy metódus lenne, felül kéne definiálni T1-ben és ki kéne egészíteni egy paraméterrel, ami z-t inicializálja. Azonban ez nem megengedett, a felül definiáló metódusokhoz nem adhatunk hozzá új paramétereket. Ha az inicializálás eljárás, akkor nincs ilyen probléma:
  PROCEDURE InitT1(t:T1; x: INTEGER; y: REAL; z: CHAR);
  BEGIN
    InitT(t,x,y); t.z:=z;
  END InitT1;
A bázisosztály adatmezõit az InitT eljárás inicializálja.

Az inicializáló eljárásnak nem szabad új inicializált objektumot készítenie

Kisértésbe eshetünk, hogy úgy implementáljuk az inicializáló eljárást, hogy készítsen egy új, inicializált objektumot:
  PROCEDURE NewT(x: INTEGER; y: REAL) : T;
    VAR t:T;
    BEGIN
    NEW(t); t.x:=x; t.y:=y; RETURN t;
    END NewT;
Azonban ez nem ajánlott, mert NewT-t nem használhatjuk fel T inicializálására a NewT1 eljárás során:
  PROCEDURE NewT1(x: INTEGER; y: REAL; z: CHAR) : T1;
    VAR t1:T1;
    BEGIN
    t1:=NewT(x,y); (* hiba: NewT egy T objektummal es nem egy  T1-gyel ter vissza *)
    t1.z:=z; RETURN t1;
    END NewT;

4.2 A rendszer futás idejű bővítése

Az elõbbi fejezetben bemutattunk egy grafikus szerkesztõt, amely új objektumokkal (vonal, háromszög, kör) bõvíthetõ ki futás közben, amelyek a szerkesztõ implementálásakor még nem ismertek. Ez a fejezet elmagyarázza hogyan vihetjük végbe ez.

Grafikus szerkesztõ kibõvítése

Tekintsük át a grafikus szerkesztõ példáját: A szerkesztõ nem közvetlenül háromszögekkel vagy körökkel dolgozik, hanem a Figure absztrakt osztály különbözõ objektumaival. Ezeket a Figures modulban deklaráltuk és rendelkeznek az elõre meghatározott interface-szel is.
  DEFINITION Figures;
    TYPE
   Figure = POINTER TO FigureDesc;
   FigureDesc = RECORD
     selected: BOOLEAN;
     PROCEDURE (f: Figure) Draw;
     PROCEDURE (f: Figure) Move(dx,dy: INTEGER);
     ...
   END;
   END Figures;
A FigureFrames a szerkesztõ egy másik modulja, ami tartalmazza a Frame osztályt, ami megjeleníti az alakzatokat és kezeli a felhasználói inputot. A Frame osztály fenntart egy listát a megjelenítendõ objektumokról, amelybe az Install üzenettel lehet új objektumot beszúrni.
  DEFINITION
  IMPORT Figures, Viewers;

  TYPE
    Frame = POINTER TO FrameDesc;
    FrameDesc = Record (Viewers.FrameDesc)
    figures: Figures.Figure; (* az alakzatok listaja ebben a keretben *)
    ...
    PROCEDURE (f: Frame) Install (x: Figures.Figure);
    ...
    END;
  VAR 
    currentFrame: Frame; (* az aktualis keret *)
    ...
  END FigureFrames;

Ez a szerkesztõ magja. Nem szükséges tudnunk az implementálás közben, hogy milyen alakzatok fognak létezni a késõbbiekben. A szerkesztõ képes dolgozni a Figure osztály bármely leszármazottjával.

Ellipszisek hozzáadása

Hogy kibõvítsük a szerkesztõnket ellipszisekkel, a következõket kell tennünk:
(1) Származtassunk egy Ellipse osztályt a Figure osztályból:

  TYPE
    Ellipse = POINTER TO EllipseDesc;
    EllipseDesc = RECORD(Figures.FigureDesc)
    x,y: INTEGER; (* kozeppont koordinatai*)
    a,b: INTEGER; (* az atlok *)
    END;
(2) Definiáljuk felül az örökölt metódusokat:
  PROCEDURE (e:Ellipse) Draw;
  BEGIN ... (* ellipszis kirajzolasa *)
  END Draw;
  ...
(3) Valósítsuk meg a New parancsot, amelyben létrehozunk egy ellipszist, és hozzáadjuk az aktuális keret (FigureFrame.currentFrame) alakzatlistájához.
  PROCEDURE New;
    VAR e: Ellipse;
  BEGIN
    NEW(e);
    ... (* e.x, e.y, e.a és e.b bekerese *)
    FigureFrames.currentFrame.Install(e);
  END New;

Mindezt csomagoljuk be egy új Ellipses nevû modulba. A szerkesztõ már látezõ moduljait ez a mûvelet nem érinti. Azért, hogy kirajzoljunk egy új ellipszis objektumot, meg kell hívnunk az Ellipsis.New parancsot. A következõk fognak történni:
(1) Ha az ELllipses modul még nincs betöltve, akkor betöltõdik, és hozzálinkelõdik a szerkesztõhõz.
(2) A New utasítás lefut. Készül egy ellipszis objektum és beillesztõdik az aktuális keret alakzatlistájába.
(3) A keret küld egy Draw üzenetet az újonnan beillesztett alakzatnak (annélkül hogy tudná a típusát), aminek hatására az ellipszis kirajzolódik.
A következõ ábra bemutatja a modulok és az adatszerkezetek közti kapcsolatot:

Az Ellipses modul dinamikus betöltése

Megjegyezzük, hogy az Ellipses modul ingény szerint töltõdik be és linkelõdik a szerkesztõhöz. Sem a Figures sem a FigureFrames modul nem tud az Ellipses modulról (nincs IMPORT). Így ezek sokkal elõbb fordíthatóak és használhatóak, jóval az Ellipses modul megírása elõtt. Másrészrõl, az Ellipses modul importálja és használja a Figures és a FigureFrames modulokat.

Figyelmeztetések (Up-call)

A szerkesztõ magja a dinamikus kötésnek köszönhetõen tud dolgozni az ellipszisekkel. A szerkesztõ az ellipszis objektumot a Figure absztrakt osztály egy páldányának látja, és via üzenetekkel kommunikál vele, meghívva az Ellipses osztály metódusait, amelyek magasabban helyezkednek el az import hierarchiában. Számos metódushívás ezért eredményez figyelmeztetést. A szerkesztõ olyan metódusokat hív meg, amiket nem is ismer. Csak a felhasználó tudja kiadni az Ellipses.New utasítást, mert csak õ tud a létezésérõl.

4.3 Perzisztens objektumok

Egy objektumot perzisztensnek (kitartó) nevezünk, ha túléli az õt létrehozó pogramot. Késõbb ugyananaz a program, illetve más programok (esetleg más számítógépen) megtalálják az objektumot ugyanabban az állapotban, amiben a készítõ program hagyta. Egyik módja annak, hogy perzisztens objektumokat készítsünk, hogy kiírjuk õket egy fájlba, majd ha szükség van rájuk, akkor beolvassuk õket. Ez csak akkor valósítható meg, ha ismerjük az objektum belsõ felépítését. Azonban, ha az objektum szerkezete ismeretlen, mint az elõzõ fejezetben, az írás és az olvasás sokkal komplikáltabb. A kérdés az, hogy hogyan töltsünk be, ill. tároljunk egy objektomut, aminek mág a szerkezetét sem ismerejük.

Ismeretlen típusú objektumok I/O-ja

A tárolás jóval könnyebb feladat: Küldjünk az objektumnak egy Store mûveletet, mire az kiirja az adatait a megadott fájlba. Végülis minden objektum ismeri a saját felépítését. Azonban a betöltés nem olyan egyszerû, hogy küldünk egy Load üzenetet, hisz az objektum még nem is létezik. Elõbb létre kell hoznunk, amihez szintén ismernünk kell a típusát. Mit tegyünk ilyenkor?! A megoldás az, hogy nem csak az objektum értékeit, de a típusnevét is eltároljuk. Az 5. ábra két alakzat reprezentációját mutatja meg a memóriában ill. egy fájlban. [***]

Már csak egy módszerre van szükségünk, hogy hozzárendeljük az objektumhoz a típusnevét, hogy késõbb eltárolhassuk, ill új objektumot hozhassunk létre.

Típusleírók (decriptor)

Az Oberonban minden objektum tartalmaz egy mutatót a típusleírójára, amely a közönséges programozók számára láthatatlan. A típusleíró futásidejû típus információkat tartalmaz, többek között az objektum típusának a nevét is. Egy osztály összes objektumának ugyanolyan típusleírója van.

A típusleírók segítségével implementálhatunk egy ObjToName eljárást, ami visszaadja az adott objektum típusnevét, valamint egy NameToObj eljárást, ami egy új objektumot készít a megadott típussal. (Az OS mosul is ezt az interface-t használja)
  DEFINITION Objects;

  TYPE
    Object = POINTER TO ObjectDesc;
    ObjectDesc = RECORD
   PROCEDURE(x: Object) Load (VAR r: OS.Rider); (* absztrakt *)
   PROCEDURE(x: Object) Store (VAR r: OS.Rider); (* absztrakt *)
    END;

  PROCEDURE ObjToName (x: Object; VAR name: ARRAY OF CHAR);
  PROCEDURE NameToObj (VAR name: ARRAY OF CHAR; x: Object);

  END Objects;

A Figure osztályt az Objekt osztályból kell leszármaztatnunk, hogy hivatkozhassunk az ObjToName és a NameToObj eljárásokra, valamint a Load és Store üzeneteket küldhessünk az alakzatoknak.
  TYPE
  Figure = POINTER TO FigureDesc;
  FigureDesc = RECORD (Objects.ObjectDesc)
  Next: Figure;
  ...
  PROCEDURE(f: Figure) Load (VAR r: OS.Rider);
  PROCEDURE(f: Figure) Store (VAR r: OS.Rider);
  ...
  END;
Most már készen állunk arra, hogy elmentsünk alakzatokat egy fájlba, ill. hogy betöltsük õket. A következõ eljárások megoldják ezeket a feladatokat:
  PROCEDURE WriteFigure (VAR r: OS.Rider; x: Figure);
    VAR name: ARRAY 64 OF CHAR;
  BEGIN
    IF x = NIL THEN r.WriteString("");
    ELSE Objects.ObjToName(x, name);
    r.WriteString(name);
    x.Store();
    END
  END WriteFigure;

  PROCEDURE ReadFigure (VAR r: OS.Rider; x: Figure);
    VAR name: ARRAY 64 OF CHAR;
    y: Objects.Object;
  BEGIN
    r.ReadString(name);
    IF name = "" THEN x = NIL;
    ELSE Objects.NameToobj(name, y);
    x:= y(Figure);
    x.Load(r);
    END
  END WriteFigure;

Ha x az alakzatok listájának fejeleme, a teljes lista a következõ módon tárolható:
  WHILE x # NIL DO WriteFigure(r,x);
  x:=x.next;
  END;
  WriteFigure(r,NIL); 
A következõ kifejezések egy listát olvanak ki a head fejelem segítségével:
 
  ReadFigure(r,x);
  head:=x;
  WHILE x # NIL DO ReadFigure(r,x.next);
  x:=x.next;
  END;
Az alakzatok I/O-ja most már szimmetrikus és teljesen általános. Az alakzatok összes jövõbeli kiterjesztése tárolható a WriteFigure, és betölthetõ a ReadFigure eljárásokkal análkül, hogy bármit is változtatnánk rajtuk. Egy új Figure osztályban mindössze a Load és a Store metódusokat kell felüldefiniálnuk.
Azokban a rendszerekben, ahol a típusnevek futási idõben nem elérhetõk, a következõ lehetõség marad: Mielõtt eltárolnánk egy objektumot, küldünk egy GetTypeName üzenetet, mire az objektum visszaadja a típusnevét. Ez a név eltárolható az objketum értékei között. Az objektumok betöltéséhez készítünk egy táblázatot a típusok neveivel és egy objektum prototípussal. Amikor egy típusnevet kiolvasunk egy fájlból, elhelyezzük a táblázatban, illetve kérünk egy prototípust is az objektumról. Minden típus nevét és prototípusát tartalmaznia kell a táblázatnak a progtam indításakor!
Ha egy olyan típust olvasunk be egy fájlból, ami egy még be nem töltött modulban van deklarálva, akkor a NameToObj betölti a modult. Ha típustáblázatot használunk prototípusokkal, akkor a betöltött modult be kell építeni a rendszerbe, ha ezt a rendszer támogatja.

A típusnevek tömörített tárolása

A típusnevek sok helyet foglalhatnak el egy fájlban, ezért tömörített formában táruljuk õket. Ezt a következõ módon implementálhatjuk: A típusnév elsõ elõfordulásánál a teljes nevet beírjuk a táblázat végére. A következõ elõfordulásoknál már csak a táblázat megfelelõ indexét írjuk be a teljes név helyett. Az olvasás fordított sorrendben történik. Az OS.Rider osztály a karaktarláncokat tömörített formában olvassa be és írja ki a WriteString és a ReadString metódusok segítségével. A táblázat, ami a karakterláncokat indexekké konvertálja és viszont a Rider osztály egy adatmezõje. A Rider objektumot az InitRider metódussal inicializáljuk az I/O kezdetekor.
  
  TYPE
    String = ARRAY  32 OF CHAR;
    Rider = RECORD
    ...
    tab: ARRAY maxnames OF String; (* tab[0] = "" a NIL-nek*)
    end: INTEGER; (* tab[0..end-1] -ig van kitöltve *)
  END;

  PROCEDURE InitRider (VAR r: Rider);
  BEGIN
  r.tab[0]:="";
  r.end:=1;
  END InitRider;

  PROCEDURE (VAR r: Rider) WriteString (VAR s: ARRAY OF CHAR);
    VAR I: INTEGER;
  BEGIN
    I:=0;
    LOOP (* s keresese r.tab-ban *)
  IF i = r.end THEN 
      r.Write(CHR(i));
      i:=-1;
      REPEAT INC(i); r.Write(s[i]) UNTIL s[i] = 0x;
      COPY (s,r.tab[r.end]);
      INC(r.end); EXIT
  ELSIF s = r.tab[i] THEN r.Write(CHR(i)); EXIT
  ELSE INC(i);
  END;
    END; 
  END WriteString;

  PROCEDURE (VAR r: Rider) ReadString (VAR s: ARRAY OF CHAR);
    VAR i: INTEGER;
    ch: CHAR;
  BEGIN
    r.Read(ch);
    IF ORD(ch) = r.end THEN (* teljes szöveg következik *)
   i:=-1;
   REPEAT INC(i); r.Write(s[i]) UNTIL s[i] = 0x;
   COPY (s,r.tab[r.end]); 
   INC(r.end);
    ELSE COPY (r.tab[ORD(ch)],s);
    END;
  END ReadString;
A 7. ábra egy szöveget mutat típusnév tömörítéssel és anélkül.
  Teljes Szöveg:
  CircleDesc...EllipseDesc...EllipseDesc...CircleDesc...EllipseDesc...

  Tömörítet szöveg:
  1CircleDesc...2EllipseDesc...2...1...2...

  7. Ábra: Szöveg típusnév tömörítéssel és anélkül