Az Euphoria Programozási nyelv (v2.3)

Objektum-orientált programozás

Objektum orientált programozás

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.

 

Osztályok - A sympel lehetõségei

A 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

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:

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

Az adattag értékét a 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:

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:

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

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:

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:

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

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:

   function Method(sequence param)
     return parentMethod(param)
   end function