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