„Hagyományos” típuskonstrukciós eszközök a nyelvben nincsenek, ezekre a standard könyvtárakban levő absztrakt osztályok nyújtanak lehetőséget.
Satherben felsorolási típus közvetlenül nem készíthető, ami szerintem egy hiányossága a nyelvnek. Ellenben létezik hozzá egy furcsa kerülőút: a ‘const’ kulcsszóval típus nélkül elnevezett változók sorozata rendre 0-tól egyesével növekvően inicializálódnak (mint a C-beli enum értékek). Például:
A tömb szerkezetet nem típuskonstrukcióval kaphatunk, hanem az ARRAY{T} paraméteres osztállyal. A tömbelemek indexelése 0-tól kezdődik. Érdekesség, hogy nincs fix mérete a tömbnek – egyszerűen azért, mert a fix méretű tömbökre külön elnevezést használ: n-esek (tuples). Tömböknek is adható kezdeti érték a ‘|’ jel segítségével. Nézzünk néhány példát:
Adhatunk meg minimális elemszámot készítéskor inicializálás nélkül is:
Ezen kívül vannak referencia tömbök (AREF{T}) és immutable típusú referencia tömbök is (AVAL{T}).
Tömbök egyik tipikus használata a parancssori argumentumok kezelése. Ehhez csak annyit kell tennünk, hogy az alkalmazás main rutinjának adunk egy ARRAY{STR} típusú paramétert.
Ezen program lefordítása után egy lehetséges futtatás az alábbi kimenetet eredményezheti:
Sather nyelvben is van egy ún. Container osztály a standard program könyvtárban (hasonlóan az Eiffelhez, de itt ez nincs további 3 kategóriára osztva), amelyből nagyon sokféle adatszerkezet (tároló) származik (pl.: verem, sor, lista, zsák, halmaz, prioritásos sor, hash-tábla, stb.)
Ahogy már korábban említettük (ld. 3 fejezet), a Satherben minden egyed (változó) egy objektum, amelyet típusa ír le. Még az egyszerű beépített típusok is osztályok, azonban az ilyen típusú objektumok statikusan jönnek létre, míg az össze többit dinamikusan kell létrehoznunk explicit konstruktor hívások segítségével.
Satherben kétféle típus létezik: konkrét és absztrakt. A konkrét típusokat osztálynak (‘class’) nevezi, míg az absztrakt típusokat egyszerűen típusnak (‘type’ = ’abstract class’). Minden típus nevét csupa nagybetűvel kell írni, az absztrakt osztályok nevét pedig mindig ‘$’ jellel kell kezdeni.
Ebben a fejezetben az osztályokat mutatjuk be részletesebben.
Osztályokat a ‘class’ kulcsszóval hozhatunk létre. Nézzünk erre egy egyszerű példát:
Egy osztályelemet példányosítani a ‘create’ eljárással lehet (mint C++-ban a new), de Satherben nincs default konstruktor, tehát ha elhagyjuk, akkor az osztály nem példányosítható. A ‘create’-ben a ‘new’ kifejezés szintén új példány létrehozására szolgál, ez foglal területet az objektum számára és visszaadja üresen (mindent void-ra állít). Vegyük észre, hogy mivel nincs paramétere, egyedül a visszatérési értéke határozza meg a típusát. A Sather szemléletnek megfelelően a ‘new’-nak implicit függvénynek kellene lennie, de akkor a szignatúra szerint az overload nem eldönthető. Ez a Sather ‘nagyon erős’ típusosságában már elképzelhető lenne, mivel a visszatérési érték is része a szignatúrának, de csak annyiban, hogy van-e. Ezzel kényszeríteni akarja a programozót a visszatérési érték felhasználására. Azonban a visszatérési érték típusa alapján nem különböztethetünk meg két eljárást. Ha viszont ez egy nyelvi trükk, akkor nem igazán illik bele a nyelv szemléletébe.
Tehát példányosítsuk a fenti osztályt:
Talán szebb lenne, ha nem közönséges függvényhívásnak nézne ki a készítés, ezért definiáltak egy ezzel ekvivalens kifejezést is a ‘#’ segítségével:
Egy változóról a ‘void( )’ függvénnyel lekérdezhető, hogy hivatkozik-e valamilyen objektumra vagy még nem:
Megjegyzés: ez a teszt nem használható beépített típusokra (pontosabban a változtathatatlan), ugyanis a nulla (0, 0.0, false és NULL) értékre igazat ad vissza. (Minden más változó referencia típusú, tehát a void() függvény hívása ekvivalens a C-ből ismert (a == NULL) kifejezéssel).
Egy osztály a következő összetevőkből áll:
Objektumok tagjait a szokásos pontozással (‘.’) érhetjük el.
Megjegyzés:
Egy osztályon belül minden publikus, hacsak nem mondunk valami mást. Bármit deklarálhatunk sajátnak (private), ekkor csak az osztályba tartozók látják. Ezen kívül egy attribútum lehet csak olvasható (readonly), ekkor kívülről az értékét csak lekérdezni lehet. A láthatósági korlátozás osztály szintű és nem objektum (egyed) szintű, azaz azonos típusú elemek látják egymás ‘szennyesét’ is.
Ezen kívül lehet egy osztály elemeinek osztott (shared) attribútuma, ami tulajdonképpen osztály szintű attribútum. Gyakorlatilag ezek korlátozottan elérhető (csak adott osztály elemei által) globális változók. Ezek láthatóságát ugyanúgy korlátozhatjuk a ‘private’ és a ‘readonly’ kulcsszavakkal.
Konstans attribútumok is minden egyed számára láthatók, de nem módosíthatók. Konstans attribútum csak inicializáláskor vehet fel értéket, tetszőleges konstans kifejezést.
Láthatósági mezők behatárolása a ‘::’ jelöléssel lehetséges.
Bár a memóriakezelésről szemétgyűjtő rendszer gondosodik és nincs szükség felszabadításra,van rá lehetőségünk, hogy explicit módon megszüntessünk objektumokat (destroy kulcsszóval).
Immutable objektumokról már a 3.4. fejezetben beszéltünk. Most tekintsük át, hogy hogyan hozhatunk létre saját immutable osztályokat.
Immutable osztályok definiálásának és használatának módja a legtöbb tekintetben megegyezik a referencia osztályokéval: attribútumokból és ezeken operáló függvényekből állnak. Példaként tekintsük meg a Sather könyvtárban szereplő CPX (komplex szám) immutable osztály egy leegyszerűsített megvalósítását. A kulcspont az, hogy megfigyeljük azt a módszert, ahogyan a create rutinban beállítjuk az attribútumokat.
Ahogy már korábban is láttuk, a CPX osztály használható például az alábbi módon:
A referencia osztályokkal ellentétben az immutable osztályok példányait nem explicit módon a new kulcsszóval hozzuk létre. Egy immutable változóhoz a deklarációtól kezdve mindig van érték hozzárendelve! A fenti példában tehát a create eljárásban a res változót egyszerűen csak deklarálnunk kell. Egy immutable objektum kezdeti értéke úgy van meghatározva, hogy minden adattagja void legyen, és ez lesz az osztály void értéke. Fontos, hogy itt a void érték egészen mást jelent, mint referencia osztályok esetén: nem azt jelenti, hogy az objektum nem létezik, hanem azt, hogy egy speciális értéke van: minden attribútuma void, amely referencia objektumok esetén a NULL pointer, egészek esetén 0, valósak esetén 0.0, logikai értékek esetén pedig false (hamis). (Vagyis egy CPX típusú változó kezdeti értéke egy olyan objektum, amelynek mindkét adattagja 0.0.) Tehát egy void értékű immutable objektum attribútumaira mindig hivatkozhatunk, míg egy void referencia objektum adattagjaira való hivatkozás mindig fatális hibát eredményez.
Az immutable objektumok tehát nem tudják megváltoztatni az értéküket, akkor viszont mit jelent egy attribútumnak való értékadás? Természetesen azt, hogy az objektum lemásolódik, és a megfelelő attribútum értéke módosul. Vagyis a fenti CPX osztály attr real:FLT adattagjának van egy implicit beállító művelete az alábbi szignatúrával:
amely az eredeti CPX objektum egy másolatát adja a real adattag módosításával (a create metódusban ezt használtuk). Hasonlítsuk össze ezt egy referencia osztállyal, amelyben ezen művelet szignatúrája az alábbi lenne:
Fontos továbbá, hogy egy immutable osztály nem tartalmazhat olyan attribútumot, amelynek típusában közvetve vagy közvetlenül szerepel újra ez a típus (pl. referencia osztályoknál egy láncolt lista elemeinek típusa ilyen, ugyanis tartalmaz hivatkozást a lista más elemeire), ugyanis az ebben az esetben nem csak egy referenciát jelentene, hanem lezáratlan, végtelen méretű adatszerkezetet eredményezne.
Satherben lehetőség van az implementációtól való elvonatkoztatásra is. Erre a célra vezették be az absztrakt típus fogalmát, amely valójában egy típusosztályt ír le. Ilyen típusú objektum nem készíthető, ez csupán osztályok felületének (interface) definíciójára alkalmazható. Ez a forma elrejti mind a belső reprezentációt, mind a tárolandó típust. Absztrakt osztályok neve a könnyebb olvashatóság kedvéért mindig ‘$’ karakterrel kezdődik.
Használatukkal klasszikus polimorfizushoz jutunk. Tegyük fel például, hogy van két verem osztályunk (egész értékű elemekkel), az egyik tömbbel, míg a másik listával valósítjuk meg. Ha a megvalósítást láthatatlanná akarjuk tenni, készíthetünk egy absztrakt típust, mely az implementációt elrejti a felhasználó elől. Ez Sather nyelven így nézhet ki:
Ezt az interface-t megvalósító két osztály:
Ez után egy számológépben láthatatlan, hogy melyiket használjuk:
(Természetesen még szebb lenne, ha általános verem típusunk lenne. Az ehhez szükséges generikus eszközöket később mutatjuk be.)
Fontos, hogy az absztrakt osztályok csak rutinokból és iterátorokból állhatnak. (Pl. jelen esetben a pop: INT nem egy attribútumot jelent, hanem egy argumentum nélküli rutint!)
E módszer tipikus használata a standard könyvtári alaptípusok megvalósítása: a $STR absztrakt típus a szöveges megjelenítést támogató típusok halmaza. Minden alaptípus ilyen, tehát a $STR altípusai.
Hivatkozás SAME-re csak visszatérési értékként és kimenő paraméterként lehet. Írhatunk ‘create’ eljárást is, bár ezt nem lehet meghívni.
Altípus készítése a ‘>’ jellel lehetséges:
Ahogy már korábban is említettük, minden típus az $OB típus altípusa. Fontos, hogy altípust csak absztrakt típusból készíthetünk! És van többszörös öröklődés is.
Egy alosztálynak meg kell felelnie a szülő osztály előírásainak. Ezért el kell tudni dönteni, hogy az alosztály szignatúrái megegyeznek-e a szülő osztályéval. Garantálni kell, hogy ha az alosztály szignatúrái megfelelők, akkor jó fog működni.
Sather a kontravariáns megfeleltetés eszközével végzi az összehasonlítást.
A Sather nyelv egy érdekessége, hogy más nyelvektől eltérően lehetőség van létező típusok fölé rendelni típust. A szupertípusban csak minden altípusában szereplő eljárásokat definiálhatunk, azaz nem bővítheti az interface-t. Például: