A nyelv nem támogatja az objektum orientált programozást.
Az Euphoria használói részérõl azonban felmerült az igény, hogy bizonyos feladatokat mégis objektumorientált ezközökkel kellene megközelíteni. Nyelvi támogatás hiányában több ügyes és kevésbé ügyes megoldás született a probléma megoldására.
A megoldások két csoportba sorolhatóak: programkönyvtárak, és elõfordítók. Az interpreter nem szabad szoftver, így annak a javítása szóba sem jöhet sajnos.
A programkönyvtárak gyengesége, hogy magához a nyelvhez nem nyúlnak hozzá, így használatuk nehézkes, kifejezõ szintaxis hiányában a nyelvtõl idegen, és sokat is kell gépelni. A legteljesebbnek a könyvtárak közül a sympel csomagot találtam, ami egy Java/C++/Objective-C szerû objektummodellt kínál a programozóknak.
Az elõfordítók szebb megoldást adhatnának a nyelv OO-vá tételére, de a legnagyobb projekt amit találtam -az EEP mint Euphoria PreProcesszor- még csak gyerekcipõben jár, ezért kénytelen vagyok a könyvtárakról írni.
sympel
lehetõségeiA library freeware, ezért bárki használhatja módosíthatja. Külön honlapja nincsen, de a changelog-ból úgy látszik elég régen fejlesztgeti egy lelkes programozó az ObjectEuphoria és a STOOP-1, STOOP-2, STOOP-3 alapján.
A könyvtár osztály alapú, és egyszeres öröklõdést támogat. Interface-ekre nincsen szükség, mert az üzenetek küldése dinamikus, így akármelyik osztálypéldánynak akármilyen üzenetet elküldhetek. Ha az adott osztálypéldány nem ismeri a kapott üzenetet, akkor futási hiba lép fel.
Mind a metódusok mind az adattagok láthatósága szabályozott.
Három védettségi szint van PRIVATE
,
PROTECTED
és PUBLIC
.
Ezeknek a szinteknek a mûködése olyan mint C++-ban: a
PUBLIC
mindenhonnal, a PROTECTED
a leszármazott, a PRIVATE
csak ugyanazon osztály
metódusaiból elérhetõ.
A C++-os friend
-nek nincs megfelelõje, hasonló mûködés
elérésére sincs mód.
Osztályszintû metódusok definiálására egyenlõre nincs lehetõség. Emulálásuk történhet oly módon, hogy minden definiált osztályt külön, az osztállyal eggyezõ nevû modulban valósítunk meg. Ez egyébként sem rossz ötlet. Az osztály moduljában a statikus mûveleteket, illetve adattagokat a normál változóként és függvényként definiáljuk/implementáljuk. A modulok használata miatt más modulbeli szimbólumokkal névütközés nem lehet, sõt, ha akarjuk a globális adattagokat definiálhatjuk modulláthatónak, így megvédhetjük õket a nem mûveleten keresztüli értékadásoktól.
Persze ezzel elveszítettük a PROTECTED
láthatósági
szintet a statikus mûveleteknél, továbbá a statikus mûveletek nem
öröklõdnek, de ez talán nem akkora baj.
Öröklés során az örökölt metódusokat felüldefiniálhatjuk, és metóduson belül elérhetjük az örökölt metódust is.
Nem lehet túlterhelni a metódusneveket, a duplikált metódus vagy adattag név futási hibához vezet.
Objektumok definiálása
A következõ hívások objektumok definiálásához használatosak.
defineClass() | egy osztályt deklarál | |
doneClass() | befejezi egy osztály deklarációját | |
addAttribute() | adattagot ad egy osztályhoz | |
addMethod() | metódust ad egy osztályhoz | |
addConstructor() | konstruktort ad egy osztályhoz |
Osztályt deklarálni a defineClass(int, str1, str2)
függvény segítségével lehet. Az osztály definiálása több
lépésben történik és egészen a következõ doneClass
hívásig tart. A két hívás között van lehetõségünk adattagok és
metódusok hozzáadására.
Paraméterek:
int
értéke lehet BASE
vagy
DERIVED
, és azt jelzi hogy a deklarált osztály
bázisosztály, vagy származik valakitõlint
értéke BASE
volt akkor str1
értéke az õsosztály neve,
egyébként bármi lehetstr2
a definiált osztály nevePélda:
defineClass (BASE, "", "Point") -- deklarál egy Point nevû
-- osztályt õs nélkül
defineClass (DERIVED, "Vehicle", "Car") -- delkarálja a Car
-- osztályt,
-- mint speciális Vehicle-t
Az éppen definiált osztály definiálását a
doneClass()
hívással tudom lezárni. Ezután már sem
mûveletet, sem attribútumot nem adhatunk az osztályunkhoz.
Példa:
defineClass (BASE, "", "Point")
-- mûveletek és attribútumok hozzáadása
doneClass ()
Még nem adtunk az osztályhoz adattagot. Erre a feladatra az
addMember(str, int)
hívás alkamas. Mindenképpen
meg kell elõznie egy defineClass()
hívásnak, amit
még nem zártunk le doneClass()
-szal.
Paraméterek:
str
a hozzáadott adattag neve. Ha nem egyedi,
akkor futási idejû hibával leáll a futtatókörnyezet.int
az adattag hozzáférési szintje. Lehet
PUBLIC
, PRIVATE
és
PROTECTED
.getMember
és a
setMember
függvény segítségével kérdezhetjük
le/állíthatjuk be.
A PUBLIC
, PRIVATE
és
PROTECTED
jelzõk jelentése a megfelel C++-ban szokásosnak:
a PUBLIC
osztályúakat bárhonnan el lehet érni, a
PRIVATE
-okat csak a következõ
doneClass
-ig, a PROTECTED
-eket pedig
a leszármazott osztályokban.
Példa:
defineClass (BASE, "", "Point")
addMember ("name", PUBLIC)
addMember ("x", PROTECTED)
addMember ("y", PROTECTED)
addMember ("name", PUBLIC) -- Hiba, ilyen nevû adattag már van!
doneClass ()
addMember ("colour", PUBLIC) -- Hiba, addMember doneClass után!
Metódust osztályhoz adni az addMethod (str, int1,
int2)
hívással lehet. Ennek a hívásnak is egy
defineClass
és egy doneClass
között
van a helye.
Paraméterek:
str
a metódus neve, ha nem egyedi akkor
futási idejû hibával áll le az interpreter. int1
a metódust implementáló Euphoria
függvény azonosítójaint2
PUBLIC
,
PRIVATE
vagy PROTECTED
Minden osztálymetódus függvény kell hogy legyen, eljárás nem
jó. Egy eljárás vagy függvény azonosítóját a neve alapján a
routine_id
standard Euphoria függvény segítségével
tudjuk megállapítani. Az elsõ paraméterben megadott névnek nem
kell hogy köze legyen az implementáló metódus tényleges
nevéhez, de segít ha ugyanazt használjuk mindkettõre. A
hozzáférés szabályozó PUBLIC
,
PRIVATE
és PROTECTED
jelentését az
addMember
leírásánál megtalálod.
Példa:
defineClass (BASE, "", "Printer")
function Print (sequence st)
puts (1, "Printer.Print(): \n")
puts (1, st)
return NO_RETURN
end function
addMethod ("Print", routine_id ("Print"), PUBLIC)
doneClass ()
A függvénydefiníciónak nem kell
a defineClass
és a doneClass
között
lenni.
A könyvtár megkülönbözteti a konstruktort a sima metódusoktól,
ezért külön függvényt biztosít a definiálásukhoz, a
addConstructor(str, int1, int2)
függvényt. A
függvény paraméterezése megeggyezik az addMethod
paraméterezésével. A konstruktorfüggvények mindig
NO_RETURN
-nel térjenek vissza.
Konstruktora csak egy lehet minden osztálynak, megadása opcionális.
Új objektum létrehozása, használata
A következõ függvényekrõl lesz szó:
newObject() | új példányt hoz létre a megadott osztályból | |
destroyClass() | töröl egy megadott objektumot(!) | |
getMember() | lekérdez adatot egy adattagból | |
setMember() | beállít egy adattagot | |
methodCall() | meghívja egy objektum egy mûveletét |
Közös bennük, hogy elsõsorban az elõzõ szekcióban összerakott objektumok felhasználói alkalmazzák õket. Persze az objektumok mûveleteinek implementálásánál ugyancsak használjuk õket, dehát ilyenkor az objektum kódja saját kliense is, ezért kategorizálhatjuk ezeket a mûveleteket "kliens mûveleteknek".
int = newObject (str, seq)
Létrehozza egy osztály egy új példányát, meghívja az objektum konstruktorát (ha volt neki), és visszatér egy azonosító számmal, ami a létrehozott példányt azonosítja.
Paraméterek:
str
az osztály neve, aminek példányát létre
kívánjuk hozni.seq
az objektum konstruktorának
paramétereiint
a létrehozott objektum azonosítójaPélda:
-- Létrehozunk egy UnitSphere példányt. UnitSphere-nek nincs konstruktora
integer objId
objId = newObject ("UnitSphere", {})
-- Létrehozunk egy Sphere példányt. Sphere-nek van konstruktora, és
-- paraméterül kapja a 0, 0, és 1 számokat
integer objId
objId = newObject ("Sphere", {0, 0, 1})
x = getMember (int1, str1)
Kiolvassa a egy objektum egy adott adattagját, és értékét visszaadja.
Paraméterek:
int1
a lekérdezett objektum objektumazonosítója, az
objektumot létrehozó newObject()
hívás adta vissza.str1
a lékérdezett attribútum neve, ha nincs ilyen
attribútom a program futásidejû hibával leáll.x
tetszõleges típus (atom vagy szekvencia), ebbe kerül
az attribútum tárolt értéke
Példa:
integer objectId
objectId = newObject ("Point", {"Origo", 0, 0})
sequence n
integer a
n = getMember (objectId, "name")
a = getMember (objectId, "x")
A getMember()
-rel lekérdezhetõ adattagok értékét a setMember()
hívással állíthatjuk be.
setMember (int1, str1, x1)
Beállítja egy objektum egy adattagját a megadott értékre.
Paraméterek:
int1
az objektum aminek adattagját állítani akarjukstr1
az állítandó adattag neve, ha nincs ilyen adattag, akkor a hívás futásidejû hibát eredményezx1
tetszõleges típusú érték, ez lesz a str1
nevû adattag értéke
Példa:
setMember (objectId, "name", "Waypoint")
setMember (objectId, "x", 1)
setMember (objectId, "y", 0)
setMember (objectId, "w", 0)
x = callMethod (int1, str, seq)
Meghívja egy objektum egy metódusát adott paraméterekkel, és visszatér a mûvelet eredményével.
Paraméterek:
int1
az objektum azonosítója, aminek a mûveletét hívni szeretnénkstr
a hívott mûvelet neve, ahogy azt az addMethod()
függvénnyel hozzáadáskor megadtukseq
a hívott mûvelet paraméterei egy szekvenciábanx
tetszõleges érték, amit a mûvelet hívása visszaadottA mûveletek, mint azt a létrehozásukról szóló részben említettük mindíg függvények, és ezért van visszatérési értékük. Mivel a visszatérési értéket Euphoriában nem hagyhatjuk figyelmen kívül, ezért ha a mûveletünk igazából eljárás, akkor egy "cumi" (dummy) értéket adtunk vissza. Ezt az értéket pedig el kell helyeznünk valahová, pl. önmagába. Ezt mutatja az elsõ példa.
Példa:
NO_RETURN = callMethod (objectId, "Display", {})
n = callMethod (objectId, "getName", {})
Egy metódust felüldefiniáló függvény hozzáfér az öröklött
implementációhoz a parentMethodi()
függvényen keresztül.
Egyebek
Ebben a részben a sympel
máshonnan kimaradt függvényeirõl írok: a this()
-rõl és a parentMethod()
-ról.
Ezek a függvények csak és kizárólag az objektumok mûveleteinek implementálásánál hasznosak, talán igazából a 2.6.2-ben lenne a helyük.
int = this()
Az éppen futó metódusokban visszaadja hogy mely objektum mûveleteként fut éppen a metódus.
Paraméterek:
int
az objektum azonosító, aminek a metódusa éppen fut
Példa:
function method(sequence Param)
setMember(this(), "Name", Param[1])
return NO_RETURN
end function
x = parentMethod(seq)
Egy metódus törzsében alkalmazva meghívja a szülõ osztály azonos nevû mûveletét. Az implementáló metódusok ennek a függvénynek segítségével használhatják a régi implementációt.
Paraméterek:
seq
a szülõ metódusnak szánt paraméterek szekvenciájax
a szülõ metódus visszatérési értéke
function Method(sequence param)
return parentMethod(param)
end function