Dr. Vermes Mátyás1
 első változat: 2001. február 
utolsó átdolgozás: 2006. január
A CCC olyan új2 objektum rendszert kapott, ami felülről 100%-ban kompatibilis a Clipper 5.x-beli objektumokkal. A Clipper négy fixen beépített osztállyal rendelkezett: error, get, tbcolumn, tbrowse. Természetesen a CCC-ben is megvannak ezek a régi osztályok, ám ami fontosabb, a CCC-ben a programozó tetszés szerint definiálhat új osztályokat, és ezek többszörös öröklődéssel örökölhetnek egymástól. A CCC az objektumorientált nyelvek minden fontos jellemzőjével rendelkezik, mégis az objektumok használatának módja azonos a régi Clippernél megszokottal. Ha egy program a négy régi objektum használatára szorítkozik, akkor az a régi Clipperrel is működni fog.3
A CCC-ben nem szívesen vezetünk be új szintaktikát, ezért sokáig osztályok készítéséhez sem volt speciális szintaktika, hanem függvényhívási API szolgált a célra. Megjegyzem, hogy a Pythonban és a Jávában is van ilyen runtime osztálydefiniálási API, legfeljebb a kezdők nem azzal találkoznak először. Újabban az osztályokat a class utasítással definiáljuk, amihez kicsit kevesebbet kell írni. A két módszer felhasználói szempontból egyenértékű. A régebbi függvényhívási API sem vesztette el a jelentőségét, ui. lehetővé teszi olyan osztályok létrehozását, amik felépítése csak futásidőben válik ismertté. Az összefüggések könnyebb megértése érdekében a függvényhívási API-val kezdem az ismertetést.
Egy új osztály programozásának elkezdésekor viszonylag sokat kellene írni. Hogy ettől mentesüljünk, célszerű elhozni a $CCCDIR/ccctutor/xclass directoryból a template.prg filét, aminek tartalma:
static clid_template:=templateRegister()
static function templateRegister()
local clid:=classRegister("template",{objectClass()})
    classMethod(clid,"initialize",{|this|templateIni(this)})
    classAttrib(clid,"cargo")
    return clid
function templateClass()
    return clid_template
function templateNew()
local clid:=templateClass()
    return objectNew(clid):initialize()
function templateIni(this)
    objectIni(this)
    return this
A kódot olvasva megállapíthatjuk, hogy 
a templateClass  vagy templateNew függvény első
hívásakor megtörténik az osztály regisztrálása. A későbbi hívások
alkalmával pedig templateClass mindig visszaadja a 
statikusan tárolt osztályazonosítót.
A classRegister függvény első argumentuma tartalmazza az új osztály nevét, a második argumentum egy array, amiben az új osztály szülő osztályait kell felsorolni. A legegyszerűbb esetben az új osztály az objectClass-tól (minden osztály közös ősétől) származik.
A classMethod függvény egy metódust ad a clid-vel azonosított osztályhoz. A metódus nevét a második paraméterben adjuk át, jelen esetben a név ,,initialize''. A metódus végrehajtása a harmadik argumentumban átadott kódblokk kiértékelésével történik. A metódusblokkok első paramétere mindig maga az objektum, amit általában ,,this'' névvel illetünk, de itt ez nem kulcsszó, mint a C++-ban, vagy a Jávában.
A classAttrib függvény egy attribútumot ad a clid-vel azonosított osztályhoz. Az attribútumok egyszerűbbek a metódusoknál, nem tartozik hozzájuk kódblokk, csak egy adat tárolására alkalmasak.
Természetesen előfordulhat, hogy a classAttrib, vagy classMethod olyan attribútumot, vagy metódust ad az osztályhoz, amit az egyébként örökölne valamelyik ősétől. Ez nem hiba, hanem éppenséggel ez az objektumorientált programozás egyik kulcsa: az ősosztályban lévő elemeket a leszármazott felüldefiniálhatja. A leszármazottban újra definiált metódus eltakarja és helyettesíti az ősosztály azonos nevű metódusát.
Egy text editorral könnyen ki tudjuk cserélni a ,,template'' szót az új osztály nevére. Mindjárt rögzítsük az első névkonvenciót, ahogy egy új osztályhoz tartozó három alapfüggvényt elnevezni illik:
Ez volt az eredeti koncepció, az újabb programokban ettől már gyakran eltérünk: Nyilván nincs elvi akadálya, hogy az osztálynévNew helyett valami más függvény állítsa elő az objektumot. A GTK-ban pl. sok olyan osztályt találunk, aminek különféle paraméterezéssel több konstruktor függvénye is van. A CCCGTK csatolóban célszerű szigorúan követni a GTK-beli neveket. A metódus-cast bevezetése óta az ősosztályokat azok initialize metódusával is tudjuk inicializálni, ezért az osztálynévIni függvény elhagyható.
Néhány új kulcsszó (class, attrib, method, new) árán mérsékelhető az osztályok kódolásához szükséges írásmunka. A class utasítást a fordító visszavezeti a függvényhívási API-ra, tehát az előbb elmondottak jó része most is érvényes.
class derived(base1,base2,...) new:symbol
    attrib a1
    attrib a2
    method m1 codeblock
    method m2 
    method m3
    ...
A class definíció függvények helyén állhat.
A class a következő class-ig vagy function-ig tart,
nincs külön lezáró endclass.
A  baseclass-okat zárójelek között felsorolva kell megadni.
Mindig van legalább egy baseclass.
Az osztálydefiníció  névtérbe helyezhető:
A class definíciót tartalmazó modulban lehet namespace
utasítás, a derived, base1,... 
nevek minősítve lehetnek. 
A class definícióból automatikusan keletkezik a
derivedClass függvény, 
ami a szokásos módon az osztályazonosítót adja.
A new:symbol toldalék opcionális. Ha hiányzik, akkor a default derivedNew nevű konstruktor készül, amivel így jutunk új objektum példányhoz:
    obj:=derivedNew(p1,p1,...)
Ha van new toldalék, de a symbol tagja üres 
(tehát ilyen alakú new:), 
akkor egyáltalán nem keletkezik konstruktor függvény. 
Teljes new toldalék esetén a symbol-ban megadott 
névvel képzett derivedSymbol konstruktort kapjuk.
A konstruktor létrehozza a megfelelő osztályú objektumot, 
és meghívja rá az initialize metódust.
Ha a class definícióban nincs initialize metódus, 
akkor egy öröklött initialize hívódik meg, ilyen mindig van, 
mert a gyökér object osztálynak van inicializálója.
A konstruktor minden paramétert továbbad az inicializálónak.
A class törzsében csak attribútum és metódus deklarációk állhatnak. Megengedett, hogy a class törzse üres legyen.
Az attrib teljesen egyszerű: Lesz egy új (vagy felüldefiniált) attribútum az osztályban a megadott névvel.
A method kulcsszó és metódusnév után egy tetszőleges kódblokk írható, ebben az esetben a metódus implementációja maga a kódblokk. A metódus deklarációnak van egy alternatív, egyszerűsített formája: Ha a deklaráció csak a nevet tartalmazza, az olyan, mintha kiírtuk volna a következő (optimalizáltan forduló) kódblokkot: {|*|derived.m2(*)}. Tehát ilyenkor a metódust úgy kell implementálni, hogy megírjuk a derived.m2 közönséges függvényt. Mint látjuk, alapesetben a metódusok az osztálynévből származó névtérbe kerülnek (automatikus prefixelés).
Visszatérve az előző template osztályra, így lehet azt definiálni class utasítással:
class template(object)
    method initialize
    attrib cargo
function template.initialize(this)
    this:(object)initialize
    return this
A this:(object)initialize kifejezésben új szintaktikai elemet látunk, az ún. metódus castot. Hatására a this objektum dinamikus típusától függetlenül az object osztály initialize metódusa hívódik meg. Ezzel elkerülhető, hogy az ősosztály inicializáló függvényét közvetlenül meg kelljen nevezni. A korábbi konvenciónak megfelelő ini függvény tehát felesleges. Az initialize metódusok kötelező visszatérési értéke a this.
Alapesetben az obj:method kifejezésben mindig az objektum valódi osztályának (Jáva terminológiával dinamikus típusának) megfelelő metódus hívódik meg. Néha azonban másra van szükség, ilyenkor alkalmazható a metódus cast, aminek három esete van:
obj:(otherclass)methodAz otherclass-beli metódus hívódik meg. Tipikus esetben otherclass obj osztályának az őse.
obj:(parent@class)methodA parent osztály metódusa hívódik meg, feltéve, hogy parent közvetlen szülője class-nak, ellenkező esetben runtime error keletkezik. Az előző esethez hasonlóan használható, de még biztonsági ellenőrzést is tartalmaz, amivel nehezen felderíthető híbák előzhetők meg.
obj:(super@class)methodA class osztály ősosztályának metódusa hívódik meg (jelen esetben super egy kulcsszó). Ez a forma lehetővé teszi, hogy az ősosztály megnevezése nélkül hivatkozhassunk az ottani metódusra.
Ne alkalmazzunk metódus-castot attribútumokra! Bár formailag jónak látszik a program, átltalában értelmetlen eredményt kapunk.
Az objektumorientált programozás erejét mutatja, hogy már az előző egyszerű (template) osztály is említésre méltó tudással rendelkezik, ugyanis egy csomó dolgot örököl az object-től. Így elsősorban ki tudja listázni, hogy milyen metódusai és attribútumai vannak, ki tudja listázni az attribútumainak az értékét, meg tudja mondani az osztályának és a szülő osztályainak a nevét, és ezzel a képességgel minden osztály rendelkezik. Ezen általános metódusokat vesszük most sorra.
Felsorolunk néhány további objektumokkal kapcsolatos függvényt, amik azonban nem metódusai az object osztálynak:
Gyakorlásképpen fordítsuk le tamplate.prg-t, és linkeljük az alábbi főprogrammal:
function main()
    templateNew():liststruct
    return NIL
Ha a kész programot lefuttatjuk, akkor a következő eredményt kapjuk:
         1 ancestors                M object
         2 asarray                  M object
         3 attrnames                M object
         4 attrvals                 M object
         5 baseid                   M object
         6 classname                M object
         7 isderivedfrom            M object
         8 length                   M object
         9 list                     M object
        10 liststruct               M object
        11 methnames                M object
        12 struct                   M object
        13 cargo                    A template
        14 initialize               M template
Az alábbi példa a CCC könyvtárból származik, egy karakteres chekbox osztályt implementál. A checkbox nagyon hasonló egy get objektumhoz, csak éppen nem írni lehet bele, hanem a szóköz billentyű nyomogatásával bejelölhetjük (X), vagy törölhetjük. A checkbox osztály egyetlen új metódust definiál, toggle-t, ami az állapotváltást végzi, néhány metódust felüldefiniál, minden mást örököl a get osztálytól.
static clid_checkbox:=checkboxRegister()
static function checkboxRegister() 
local clid:=classRegister("checkbox",{getClass()})
    classMethod(clid,"initialize",{|this,r,c,b,v|checkboxIni(this,r,c,b,v)})
    classMethod(clid,"toggle",{|this|toggle(this)})
    classMethod(clid,"insert",{|this|toggle(this)})
    classMethod(clid,"overstrike",{|this|toggle(this)})
    classMethod(clid,"delete",{|this|setfalse(this)})
    classMethod(clid,"backspace",{|this|setfalse(this)})
    classMethod(clid,"display",{|this|display(this,"[X]")})
    return clid
function checkboxClass() 
    return clid_checkbox
function checkboxNew(r,c,b,v) 
local clid:=checkboxClass()
    return objectNew(clid):initialize(r,c,b,v)
function checkboxIni(this,r,c,b,v) 
    getIni(this,r,c,b,v)
    this:colorspec:=setcolor()
    this:varput(.f.)
    return this
static function display(this,c)
local l:=left(c,1)
local r:=right(c,1)
local flg:=if(this:varget,substr(c,2,1)," ")
local clr:=if(this:hasfocus,logcolor(this:colorspec,2),logcolor(this:colorspec,1))
    
    @ this:row,this:col   say l
    @ this:row,this:col+1 say flg color clr
    @ this:row,this:col+2 say r 
    setpos(this:row,this:col+1)
    return NIL
static function toggle(this)
    if( this:varget )
        setfalse(this)
    else
        settrue(this)
    end
    return NIL
static function setfalse(this)
    this:varput( .f. )
    this:display
    return NIL
static function settrue(this)
    this:varput( .t. )
    this:display
    return NIL
Említést érdemel, hogy a fenti program szintaktikailag tiszta Clipper. 
A megértés ellenőrzése kedvéért nézzünk meg egy tipikus sort
a  checkboxIni függvényből:
    this:varput(.f.)
Itt this azt a checkbox objektumot jelenti, amit éppen inicializálunk.
A this nem kulcsszó, választhattunk volna helyette akármi más változónevet is.
A varput metóduson keresztül beállítjuk a checkbox kezdeti értékét .f.-re.
Honnan van azonban a checkboxnak varput metódusa, ha egyszer a class
függvényben nem definiáltunk ilyet? A válasz természetesen, hogy
örökli a get osztálytól.
Jegyezzük meg a második névválasztási konvenciót. Ha egy metódust lokálisan implementálunk, akkor azt célszerű static függvénnyel tenni. Ezzel elérhető, hogy az objektum interfész ne legyen megkerülhető közvetlen függvényhívással. Ha a metódusfüggvény nem helyben van implementálva, vagy más okból nem lehet static, akkor az osztálynévből képzett prefix alkalmazása javasolt. Például a toggle függvény definíciója lehetne ilyen is:
function _checkbox_toggle(this)A névkonvenciók betartása nincs kikényszerítve, a programunk működni fog más névválasztással is. A konvenció mindössze segít elkerülni a zavaros helyzeteket. A névterek bevezetése óta a prefixelésre alkalmazható névtér is.
Érdemes meggondolni a következőt: Tudjuk, hogy a getek editálása Clipperben (és így CCC-ben is) a
    readmodal(getlist)
függvényhívásban történik. A getlist array a régi Clipperben
az editálandó getek listáját tartalmazta. Csakhogy a CCC-ben
a getek között checkboxok is lehetnek, fog-e ez így működni?
Fog, ugyanis a checkbox örökli a get osztályból mindazokat a metódusokat,
amik ahhoz szükségesek, hogy őt a readmodal működtesse,
ezért readmodal nem fogja észrevenni, hogy a getek között 
speciális példányok (checkboxok) is vannak. 
Az objektumorientált programozásban gyakran alkalmazzák ezt a fogást.
Ha az iménti checkbox osztályt az újabb class szintaktika felhasználásával definiálnánk, akkor (rövidítésekkel) a következő eredményre jutnánk:
class checkbox(get)
    method initialize 
    method toggle       {|*|toggle(*)})
    method insert       {|*|toggle(*)})
    method overstrike   {|*|toggle(*)})
    method delete       {|*|setfalse(*)})
    method backspace    {|*|setfalse(*)})
    method display      {|this|display(this,"[X]")})
static function checkbox.initialize(this,r,c,b,v) 
    this:(get)initialize(r,c,b,v)
    this:colorspec:=setcolor()
    this:varput(.f.)
    return this
static function display(this,c)
    ...
static function toggle(this)
    ...
static function setfalse(this)
    ...
static function settrue(this)
    ...
Az initialize metódust kódblokk nélkül deklaráltuk,
ezért a fordító fog hozzá automatikus kódblokkot rendelni.
Ennek megfelelően az initialize-t implementáló függvényt 
a checkbox névtérbe kell helyezni.
Az ősosztály (get) inicializálásához metódus-castot használtunk,
ezért nincs szükség a getIni függvény közvetlen megnevezésére.
A toggle kódblokkjában a * minden paraméter 
automatikus továbbadását jelenti. Itt bizony már eltértünk
a Clipper szintaktikától, cserébe rövidebb kódot kaptunk.
Egyébként a két megvalósítás egyenértékű.
Az objektumokat használó programnak nem kell számontartania, hogy az objektumműveletek belsőleg attribútumként vagy metódusként vannak-e implementálva. Ez annak következménye, hogy a fordító ugyanazt a kódot fordítja az o:initialize vagy o:initialize() bemenetre. Tehát az üres zárójelpár léte/nemléte nem utal arra, hogy attribútumról vagy metódusról van-e szó. Hasonlóképpen, ugyanaz a kód keletkezik az o:initialize:=x vagy o:initialize(x) bemenetből. A CCC-ben érvényes az alábbi szabály:
Az objektumok használatakor az attribútum kiértékelés és értékadás, valamint a metódus hívás szintaktikája egyenértékű, és minden esetben felcserélhető.Néhány példa egyenértékű attribútum/metódus kiértékelésre:
    obj:attr()      <==>  obj:attr
    obj:meth()      <==>  obj:meth
    obj:attr(x)     <==>  obj:attr:=x
    obj:meth(x)     <==>  obj:meth():=x
    obj:meth(a,b,x) <==>  obj:meth(a,b):=x
Ez biztosítja,
hogy az osztály implementációjában szabadon lehessen attribútumot metódusra,
vagy metódust attribútumra cserélni. A Jávában elterjedt ún. set-get 
metódusoknak ezért kevesebb jelentősége van.
Az interpretált objektum alapú nyelvekben az objektum-metódus párosítás gyakran az asszociatív tömbökön alapul. Az objektum belsőleg egy asszociatív tömb, ami viszont lényegében egy hash tábla. Az objektum-tömb elemeit név szerint lehet elérni. Ha a tömbelem adatot tartalmaz, akkor attribútumról van szó. Ha a hivatkozott tömbelem kódot tartalmaz (pl. Python esetében lambda függvényt), akkor az egy metódus, amit a futtatórendszer automatikusan kiértékel.
A CCC-ben az objektumok az attribútumaikat tartalmazó egyszerű tömbként vannak implementálva. Létezik viszont minden osztályhoz egy hashtábla, amiből név alapján kikereshető
Az attribútum/metódusok hashtáblából való név szerinti keresése meglehetősen hatékony, ám az igazi gyorsulást egy egyelemű cache hozza a konyhára: Minden obj:method alakú kifejezés első kiértékelésekor meghatározzuk obj tényleges típusát, kikeressük az ehhez tartozó metódust (vagy attribútum indexet), és mind a két adatot megjegyezzük. Ha a vezérlés újra ugyanerre a programrészre kerül, akkor először megnézzük, hogy obj típusa változott-e az előző alkalom óta. Tipikus esetben nincs változás, ilyenkor azonnal kéznél van a végrehajtandó metódus. Ha a típus mégis változott, akkor újra keresünk.
Végül megemlítem, hogy a CCC-ben nincsenek asszociatív tömbök, helyette mindig használható a $CCCDIR/ccctutor/hash directoryban implementált hashtable osztály. A CCC objektumrendszere is ezzel működik. Maga az algoritmus mindössze 20-30 Clipper sor (kommentek nélkül).
1ComFirm BT.
2A Clipperhez képest új, de valójában már évek óta használjuk, csak korábban nem volt időm elkészíteni ezt a leírást.
3 Feltéve, hogy osztálydefiniálásra a függvényhívási API-t használjuk, kerüljük a metódus castot és ügyelünk a zárójelezésre.
4Ez a konvenció megfelel a régi Clipperből ismert errorNew, getNew, tbcolumnNew, tbrowseNew függvényneveknek.