A Modula3 programozási nyelv

Objektum-orientált programozás

Osztályok

A nyelv OBJECT típusa leginkább az újabb PASCAL nyelvekéhez hasonlít (pl. Turbo Pascal 5.5-től). Egy ilyen objektum típus felel meg egy osztálynak. Az OBJECT kulcsszó előtt megadhatunk egy darab őst, utána pedig rendre az adatmezőket, a metódusokat, végül az ősosztályból felüldefiniált műveleteket. Minden metódus alapértelmezésben a NIL virtuális függvény, ezt definiáljuk felül. Az első paraméter, ha nem is írjuk ki explicit, mindig az objektum maga (self). Konstruktor nincsen, mint mondjuk a C++-ban, erről a programozónak kell gondoskodni.

Object típus szintaxisa:
TYPE leszármazott = ős OBJECT adatmezők [METHODS műveletek] [OVERRIDES az ősosztályból felüldefiniált műveletek] END

Object a Super altípusa, vagyis az Object altípusa egy előredefiniált, úgynevezett ROOT típusnak, mely az összes osztálynak a gyökere. Object megörökli Super-től az összes attribútumot és metódust, ez azt jelenti, hogy az Oject összes példányának tartalmazott mezőjének és metódusának ugyanaz a neve mint a Super-ben. Tekintsük meg az absztrakt Jarmu osztályt:

Jarmu = OBJECT position: RECORD: REAL END; speed: REAL; load: REAL; METHODS newPos(x,y:REAL); setSpeed(mph: REAL); loadFreight(kg:REAL); unloadFreight(kg:REAL); END;

Kiterjeszthetjük a Jarmu osztályt egy absztrakt Auto osztályra, mely plusszban tárolni fogja az utasok létszámát:

Auto = Jarmu OBJECT passengers: [0..9]:=0; METHODS getIn(number:[1..9]); getOut(number:[1..9]); END;
Az metódusokat eljárásokkal implementáljuk, s ezek neveit a következőképpen adjuk meg:
... METHODS getIn(number:[1..9]):=GetIn; ...
Itt a GetIn eljárás implementálja a getIn metódust, de itt meg kell jegyezni, hogy ha legalább egy absztrakt metódusa van az osztálynak, akkor még absztrakt, s nem lehet példányosítani, de ha mindegyik metódusa meg van valósítva, utána már lehet példányosítani.
Ha valaki nem elégedett az Auto Object egyes szolgáltatásaival, akkor felülírhatja azokat:
SpecialisAuto = Auto OBJECT METHODS setSpeed:=SetCruiseControl; END;
Ez a kódsor kreál egy másik alosztályt, amely ugyan olyan mint az Auto osztály, csak a setSpeed metódusban különböznek, de viszont a szignatúrában megegyeznek.
Az Objektum típusok egységbe zárása

Először is tanácsos tisztában lenni az egységbe zárt adattípussal

Szabályok az egységbe zárt adattípus tervezéséhez Egységbe zárt objektum létrehozása és inicializálása (bemutatása egy példán keresztül)
INTERFACE PiggyObj; (* Malacpersely *) TYPE T <: Public; (* rejtett altípus *) Public = OBJECT METHODS init():T; deposit(cash:CARDINAL); (* pénz elhelyezése a malacperselyben *) smash():CARDINAL; (* pénz kivétele, a persely összetörése *) END; END PiggyObj. *** MODULE PiggyObj; REVEAL T=Public BRANDED OBJECT contents:INTEGER; OVERRIDES init:=Init; deposit:=Deposit; smash:=Smash; END; PROCEDURE Init(t:T):T= BEGIN t.contents:=0; RETURN t END Init; PROCEDURE Deposit(t:T; amount:CARDINAL)= BEGIN <* ASSERT t.contents >= 0 *> (* elöfeltétel: nem törött a persely *) INC(t.contents, amount); END Deposit; PROCEDURE Smash(t:T):CARDINAL= VAR s:CARDINAL:=t.contents; BEGIN t.contents:=-1; (* persely összetörve *) RETURN s (* visszatér az eredeti tartalommal *) END Smash; BEGIN END PiggyObj. *** MODULE PiggyBank EXPORTS Main; (* Malacpersely felhasználása *) IMPORT PiggyObj; FROM SIO IMPORT PutText,PutInt,GetInt,Nl;  TYPE PiggyBanks=ARRAY [0..1] OF PiggyObj.T; VAR sty:PiggyBanks; amount,index:INTEGER; active:=NUMBER(PiggyBanks); (* működö perselyek száma *) BEGIN PutText("Malacpersely:\n"& "Pozitív összeg halmozódik, negatív hatására összetörik a persely.\n"& "Páratlan összegek az 1-es, párosak a 0-s perselybe kerülnek.\n");  FOR s:=FIRST(sty) TO LAST(sty) DO sty[s]:=NEW(PiggyObj.T).init(); (* objektum létrehozása és init. *) END; WHILE active>0 DO amount:=GetInt(); index:=ABS(amount) MOD NUMBER(sty); IF amount>=0 THEN IF sty[index]#NIL THEN sty[index].deposit(amount) END ELSE PutText("A persely kiürített tartalma:"); PutInt(index,1); PutInt(sty[index].smash(),6); Nl(); sty[index]:=NIL; (* objektum törlése *) DEC(active); (* aktív perselyek száma csökken *) END; END; END PiggyBank.

Az egységbe zárt adattípushoz képest annyi a különbség, hogy a létrehozó műveletet a kliensre hagyjuk, s az implementációs modulban a típust deklaráljuk a REVEAL kulcsszóval, amúgy minden teljesen hasonló csak nem típust, hanem OBJECT-et definiálunk.

Objektumok

Objektum létrehozására rendelkezésünkre áll a más nyelvekben is megszokott NEW parancs.

Object típusok deklarációja

A Modula-3 objektumai az object típusok(osztályok) példányai, melyek mezőkből és metódusokból állnak

Objektumok létrehozása

Minden objektumnak van előredefiniált NEW konstruktor függvénye, viszont példányosításkor mi is megadhatjuk az objektumok default értékeit:
cat:= NEW(Auto,passengers:=1);
A meghívott NEW függvény létrehozott egy Auto objektumot beállítva a passengers mező értékét.

A Modula-3 egyik specialitása, hogy létrehozhatunk úgynevezett névtelen osztályokat, mint a Java-ban, egy nagyon egyszerű szintaxissal:

myCar:=NEW(Auto OBJECT OVERRIDES setSpeed:= SetCruiseControl);
Az objektumokra vonatkozó altípus szabályok

Az objektumokra az alábbi típusrelációk érvényesek:

ROOT <: REFANY UNTRACED ROOT <: ADDRESS NULL <: T OBJECT ... END <: T <: ROOT

Minden traced objektum -melyek felszabadítását a szemétgyűjtő végzi- a ROOT altípusa. A NULL minden objektum típusnak altípusa. Minden untraced objektum az UNTRACED ROOT altípusa. Ezek tárterületét a DISPOSE, unsafe paranccsal lehet felszabadítani.

Öröklődés

Az alul található kód a Malacpersely(PiggyObj) kiterjesztése. CoinBank csak az előre meghatározott érméket fogadja el, még pedig úgy, hogy egyrészt felüldefiniáljuk a deposit metódust, s hozzáveszünk még egy mezőt. Az áj deposit metódus implementálásánál használjuk a PiggyObj deposit metódusát, azaz meghívtuk a superclass metódusát, s ezt nevezzük supercall-nak. A supercall végső soron egy eljáráshívás: s ily módon megkerültük a dinamikus kötést és közvetlenül hívtuk meg azt az eljárást amit a superclass implementált.

INTERFACE CoinBank; IMPORT PiggyObj; CONST Valid = SET OF [1..20] {1,2,5,10,20}; (*érvényes érmék*) TYPE T <: Public; (* rejtett altípus *) Public = PiggyObj.T OBJECT METHODS (*deposit csak az érvényes érméket fogadja el*) missed():CARDINAL; (*érvénytelen érmék összege*) END; END CoinBank. *** MODULE CoinBank; REVEAL T=Public BRANDED OBJECT invalidAmount:CARDINAL:=0; OVERRIDES deposit:=Deposit; missed:=Missed; END; PROCEDURE Deposit(bank:T; sum:CARDINAL)= BEGIN IF sum IN Valid THEN PiggyObj.T.deposit(bank, sum) ELSE INC(bank.invalidAmount, sum); END; END Deposit; PROCEDURE Missed(bank:T):CARDINAL= VAR s:CARDINAL:=t.contents; BEGIN RETURN bank.invalidAmount END Missed; BEGIN END CoinBank.

Polimorfizmus, dinamikus kötés

A többalakúság miatt szükség van objektumok típusának meghatározására. Erre a következő nyelvi eszközök állnak rendelkezésre:

A NARROW függvénnyel biztosíthatjuk a fordítót, hogy egy leszármazott objektum műveletei egy, az őstípusával rendelkező referenciából is elérhetőek legyenek. Helytelen használata esetén, például ha rossz dinamikus típust adtunk meg, futási hiba lép fel. Az ISTYPE függvény értéke pontosan akkor igaz, ha T típus tartalmazza x-et. Így könnyen ellenőrizhető egy objektum típusa. ISTYPE(X:Referencia;T:ReferenciaTípus):BOOLEAN; A TYPECASE egy hasonló, de nagyobb kifejezőerejű eszköz. Használatával esetszétválasztást készíthetünk egy kifejezés típusára nézve. Szintaxisa:
TYPECASE kifejezés OF típus_1 (segédváltozó_1) programrészlet_1 típus_n (segédváltozó_n) programrészlet_n ELSE programrészlet_0 END

A megfelelő ág segédváltozója felveszi a kifejezés értékét. Ha több ág is megfelel, a legelső választódik ki. Ezért figyelni kell a sorrendre. Ha nincs megfelelő ág és ELSE ág sincs, a program futási hibával leáll.

A TYPECASE programstruktúra kiértékelése az alábbiak szerint zajlik:

MODULE BankPoly EXPORTS MAIN; IPORT PigyObj, CoinBank; FROM SIO IMPORT PutText, PutInt, GetInt, NI; PROCEDURE Deposit(p: PiggyObj; amount: CARDINAL)= BEGIN p.deposit(amount); (*a helyes metódus választódik ki automatikusan*) IF ISTYPE(p, CoinBank.T) (*teszt, vajon p CoinBank.T típusú*) AND NOT amount IN CoinBank.Valid THEN PutText("érvénytelen érme a coin bank-nak"); PutInt(amount); NI(); END; END Deposit; PROCEDURE Withdraw(p: PiggyObj.t)= VAR t: TEXT; BEGIN TYPECASE p OF (*teszt, vajon p dinamikus típus-e*) |CoinBank.T(c) => t:="coin"; PutText("érvénytelen kísérletek száma = "); PutInt(c.missed()); (*c jelöli p-t*) PutText(" "); |PiggyObj.T => T:="piggy"; END; PutText(t & "bank tartalma = "); PutInt(p.smash()); (*smash nem volt felülírva*) NI(); END Withdraw; VAR coin: CoinBank.T:= NEW(CoinBank.T).init(); piggy: PiggyObj.T:= NEW(PiggyObj.T).init(); amount: INTEGER; BEGIN REPEAT amount:= GetInt(); IF amount >=0 THEN IF amount < 6 THEN Deposit(coin, amount) ELSE Deposit(piggy, amount) END; ELSE Withdraw(piggy); Withdraw(coin); END; UNTIL amount < 0; END BankPoly;

Interfészek

A Modula-3 fordítási egységei a modulok és interfészek.

Az interfész deklarációk csoportja. Az interfészen belüli deklarációkra ugyanazok a szabályok vonatkoznak, mint egy blokkban, kivéve, hogy minden kezdeti értéknek konstansnak kell lennie, és az eljárásdeklarációknak csak a szignatúrát szabad megadni, a törzset nem.

Egy modul exportál egy interfészt, hogy törzset rendeljen az interfészben deklarált eljárásoknak. Egy modul vagy interfész importál egy interfészt, hogy láthatóvá tegye az abban deklarált entitásokat.

Egy program modulok és interfészek összessége, és tartalmaz minden olyan interfészt, melyet moduljai vagy interfészei importálnak, vagy exportálnak, és melyben minden eljárás, interfész és modul legfeljebb egyszer van definiálva.

Egy interfészt a következőképpen definiálhatunk:

        INTERFACE <azonosító>;
                <import utasítások>;
                <deklarációk>;
        END <azonosító>;

Ahol a deklarált és importált entitások halmazának diszjunktnak kell lennie. Fordítási hibát kapunk akkor is, ha két interfész kölcsönösen függ egymástól.

"Unsafe" műveletek

Bevezetésüket az indokolta, hogy a nyelv alkalmas legyen rendszerprogramok írására, amihez elengedhetetlen az erőforrások alacsony szintű kezelhetőségének biztosítása. Körültekintő használatukkal a program hatékonysága növelhető, ugyanakkor figyelmetlen használatuk könnyen okozhatja a program összeomlását (unchecked run-time-error). Biztonságos (csak safe műveleteket használó) programban hibás választ vagy checked run-time errort kaphatunk, de a memória nem keveredik össze. Egy interfész unsafe, ha az őt exportáló modulok bármelyikében használunk unsafe műveletet, vagy ha az UNSAFE kulcsszót az INTERFACE elé írjuk. Egy interfész biztonságos (safe), ha az őt exportáló összes modul az.