4. T!ídy a objekty Pojetí pojmu objekt se v r!zn"ch programovacích jazycích li#í. V tomto textu budeme vycházet z p$edstavy, kterou zavedl SmallTalk a která je dodnes v objektov"ch jazycích více %i mén& dodr'ována. Objekt je ucelen" souhrn dat (abstraktní datová struktura), kter" je charakterizován t$emi základními vlastnostmi: zapouzd$ením, polymorfismem a d&di%ností. T&mito pojmy se budeme zab"vat v následujících %ástech, nyní uvedeme pouze základní definice. Ka'd" objekt obsahuje krom& sv"ch dat i kód, kter" s t&mito daty pracuje. Tomuto kódu se $íká metody. Metoda je zvlá#tní druh funkce, která se spustí, kdykoliv chce u'ivatel vykonat s objektem n&jakou akci. Ka'd" objekt m!'e obsahovat více metod, podle toho, jaké akce s ním lze provád&t. Spou#t&ní metod se d&je pomocí mechanismu zasílání zpráv. Ka'dá metoda objektu má své jméno; pokud chceme n&jakou metodu zavolat, po#leme objektu zprávu tého' jména. Pokud tedy za#leme objektu zprávu A, systém mezi jeho metodami nalezne metodu A a spustí ji. Této metod& se $íká obsluha zprávy A, procesu zavolání obsluhy dané zprávy se $íká její obslou!ení. Zpráva zasílaná objektu m!'e obsahovat argumenty podobn&, jako m!'e argumenty obsahovat volání funkce. Obsluha zprávy (metoda) má pak tyto argumenty k dispozici (podobn& jako funkce). Obsluha zprávy také m!'e vracet hodnotu jako sv!j v"sledek. Poznámka 4.1. V Common Lispu je na rozdíl od této u'#í definice zvykem naz"vat objektem jakákoli data. V p$ípad&, 'e by mohlo dojít k nedorozum&ní, je tedy n&kdy t$eba blí'e specifikovat, v jakém smyslu zrovna o objektech hovo$íme. Proto budeme n&kdy pou'ívat zp$es(ující termíny, jako objekt Common Lispu (lispov" objekt), objekt ve smyslu objektového programování a podobn&. Poznámka 4.2. Ka'dé zaslání zprávy, které zm&ní vnit$ní stav objektu, znamená vedlej#í efekt. V samém srdci objektového programování tak stojí princip vedlej#ího efektu, kter" je v p$ímém rozporu se zásadami funkcionálního programování. 4.1. T!ídy Data v objektech jsou (podobn& jako u struktur v jazyce C) rozd&lena do jednotliv"ch pojmenovan"ch polo'ek. V Common Lispu se t&mto polo'kám $íká sloty. Ka'd" slot objektu má své (v rámci objektu) jedine%né jméno. T#ída je, ve své jednodu##í podob&, popis objektu. Obsahuje jednak seznam názv! slot! objektu a jednak definici v#ech jeho metod. P$i b&hu programu slou'í t$ída jako p$edloha k vytvá$ení nov"ch objekt!. Objekt, jeho' popisem je daná t$ída, se naz"váp#ímou instancí této t$ídy. Poznámka 4.3. Uvedenou definici t$ídy v následujících kapitolách je#t& roz#í$íme. Mimo jiné definujeme pojem instance t$ídy, kter" je obecn&j#í ne' pojem p$ímé instance. Pokud v následujícím textu pou'ijeme pojem instance, vztahuje se to k tomuto obecn&j#ímu pojmu (a tedy i k pojmu p$ímé instance). Průvodce studiem Zopakujme si, v %em jsou shodné a v %em se mohou li#it dv& p$ímé instance té'e t$ídy: 1. Dv& p$ímé instance té'e t$ídy obsahují stejnou sadu slot!, tedy mají stejn" po%et slot! stejn"ch názv!. Hodnoty t&chto slot! v#ak mohou b"t r!zné. 2. Dv& p$ímé instance té'e t$ídy obsahují stejné metody.
T$ída je datov" typ. Ve staticky typovan"ch programovacích jazycích lze t$ídy pou'ívat ke specifikaci typ! prom&nn"ch, v dynamicky typovan"ch programovacích jazycích pak lze na t$ídy pou'ít nástroje k dynamickému zji#)ování typu dat (v Common Lispu nap$íklad funkci typep). Tyto mo'nosti jsou ov#em k dispozici i ve staticky typovan"ch jazycích (konstrukce is ), do kter"ch tak objektové programování vná#í prvky dynamického typování. 4.2. T!ídy a instance v Common Lispu Podívejme se, jak jsou obecné pojmy z p$edchozích podkapitol realizovány v Common Lispu. Nové t$ídy se definují pomocí makra defclass, které specifikuje seznam slot! t$ídy, a pomocí makra defmethod, které slou'í k definici metod instancí t$ídy. Nové objekty se vytvá$ejí pomocí funkce make-instance. Ke %tení hodnoty slotu objektu slou'í funkce s názvem slot-value, kter" lze v kombinaci s v"razem setf pou'ít i k nastavování hodnot slot!. Zprávy se v Common Lispu objekt!m zasílají pomocí stejné syntaxe, jakou se v tomto jazyce volají funkce. Zjednodu#ená syntax makra defclass je následující: (defclass name () slots) Definice t#ídy: makro defclass, 1. verze name: symbol (nevyhodnocuje se) slots: seznam symbol! (nevyhodnocuje se) Symbol name je název nov& definované t$ídy, symboly ze seznamu slots jsou názvy slot! této t$ídy. Poznámka 4.4. Prázdn" seznam za symbolem name je sou%ástí zjednodu#ené syntaxe. V dal- #ích kapitolách, a' se dozvíme více o t$ídách, uká'eme, co lze pou'ít místo n&j. P!íklad 4.5. Definice t$ídy point, její' instance by obsahovaly dva sloty s názvy x a y by vypadala takto: (defclass point () (x y)) Definovali-li jsme novou t$ídu (zatím pouze t$ídu bez metod, k jejich' definici se dostaneme vzáp&tí), m&li bychom se nau%it vytvá$et její p$ímé instance. V Common Lispu k tomu pou'íváme funkci make-instance, její' zjednodu#ená syntax je tato: Vytvá#ení instancí: funkce make-instance (make-instance class-name) class-name: symbol Funkcemake-instance vytvo$í a vrátí novou p$ímou instanci t$ídy, její' jméno najde ve svém prvním parametru. V#echny sloty nov& vytvo$eného objektu jsou neinicializované a ka'd" pokus získat jejich hodnotu skon%í chybou (pozd&ji si $ekneme, jak se získávají hodnoty slot! a pak to budeme moci vyzkou#et). P!íklad 4.6. Pokud jsme definovali t$ídu point tak, jak je uvedeno v p$edchozím p$íklad&, m!'eme nyní vyzkou#et vytvo$ení její instance:
CL-USER 2 > (make-instance 'point) #<POINT 200DC0D7> Průvodce studiem Pokud není v tento moment %tená$i jasné, pro% jsme ve v"razu (make-instance 'point) symbol point kvotovali, m&l by si uv&domit, 'e make-instance je funkce a zopakovat si základy vyhodnocovacího procesu v Common Lispu. V"sledek volání není p$íli# %iteln", v prost$edí LispWorks si jej ale m!'eme prohlédnout v inspektoru. Pokud v Listeneru klikneme na tla%ítko s mikroskopem ( ), objeví se okno obsahující údaje o posledním v"sledku, jak je vid&t na Obrázku 4. Obrázek 4: Neinicializovaná instance t$ídy point v inspektoru Text #<unbound slot> u názv! jednotliv"ch slot! znamená, 'e sloty jsou neinicializované. M!'eme jim ale pomocí prost$edí LispWorks zkusit nastavit hodnotu. Kliknemeli na n&kter" ze zobrazen"ch slot! prav"m tla%ítkem, m!'eme si v objeviv#í se nabídce vybrat volbu Slots->Set tak, jak je znázorn&no na Obrázku 5 a novou hodnotu slotu nastavit. K programovému %tení hodnot slot! slou'í funkce slot-value, k jejich nastavování symbol slot-value v kombinaci s makrem setf. Syntax je následující: (slot-value object slot-name) $tení a nastavování slot%: slot-value object: objekt slot-name: symbol P!íklad 4.7 (práce s funkcí slot-value). Vyzkou#ejme si práci s funkcí slot-value. Nejprve vytvo$me instanci ji' definované t$ídy point a ulo'me ji do prom&nné pt: CL-USER 2 > (setf pt (make-instance 'point)) #<POINT 216C0213>
Obrázek 5: Nastavování hodnoty slotu v inspektoru Průvodce studiem Pou'ití prom&nné, kterou jsme d$íve nedefinovali (jako v tomto p$ípad& prom&nné pt), je povoleno pouze k experimentálním ú%el!m v p$íkazovém $ádku. Na jin"ch místech je v"vojové prost$edí nepovoluje. Ka'dou prom&nnou je t$eba bu* definovat jako lexikální (nap$íklad pomocí speciálního operátoru let), nebo jako dynamickou (makrem defvar). Nyní zkusme získat hodnotu slotu x nov& vytvo$ené instance: CL-USER 3 > (slot-value pt 'x) Error: The slot X is unbound in the object #<POINT 216C0213> (an instance of class #<STANDARD-CLASS POINT 200972AB>). 1 (continue) Try reading slot X again. 2 Specify a value to use this time for slot X. 3 Specify a value to set slot X to. 4 (abort) Return to level 0. 5 Return to top loop level 0. Type :b for backtrace, :c <option number> to proceed, or :? for other options Vidíme, 'e do#lo k chyb&; slot x není v nov& vytvo$eném objektu inicializován. Z chy-
bového stavu se dostaneme napsáním :a a zkusíme hodnotu slotu nejprve nastavit: CL-USER 4 : 1 > :a CL-USER 5 > (setf (slot-value pt 'x) 10) 10 Nyní ji' funkce slot-value chybu nevyvolá a vrátí nastavenou hodnotu: CL-USER 6 > (slot-value pt 'x) 10 Nové hodnoty slot! lze také ov&$it pomocí inspektoru. Získejme nejprve obsah prom&nné pt: CL-USER 7 > pt #<POINT 216C0213> a stiskn&me tla%ítko s mikroskopem. V"sledek vidíme na Obrázku 6. Obrázek 6: Instance t$ídy point po zm&n& hodnoty slotu x Zb"vá vysv&tlit, jak se v Common Lispu definují metody objekt!. Jak jsme ji' $ekli, v#echny p$ímé instance jedné t$ídy mají stejnou sadu metod. Metody jsou zvlá#tní druh funkce, proto se definují podobn&. V Common Lispu je k definici metod p$ipraveno makro defmethod. Jeho syntaxe (ve zjednodu#ené podob&, jak ji uvádíme na tomto míst&), je stejná jako u makra defun s tou v"jimkou, 'e u prvního parametru je t$eba specifikovat jeho t$ídu. Tím se metoda definuje pro v#echny instance této t$ídy. Definice metod: makro defmethod (defmethod message ((object class) arg1 arg2...) expr1 expr2... ) message: symbol object: symbol class: symbol argi: symbol expri: v"raz
Symbol class ur%uje t$ídu, pro její' instance metodu definujeme, symbol message sou%asn& název nové metody i název zprávy, kterou tato metoda obsluhuje. V"razy expr1, expr2 atd. tvo$í t&lo metody, které stejn& jako t&lo funkce definuje kód, kter" se provádí, kdy' je metoda spu#t&na. Symbol object je b&hem vykonávání t&la metody navázán na objekt, jemu' byla zpráva poslána, symboly arg1, arg2, atd. na dal#í argumenty, se kter"mi byla zpráva zaslána. Jak ji' bylo $e%eno, syntax zasílání zprávy je stejná jako syntax volání funkce: (message object arg1 arg2...) Syntax zasílání zpráv message: symbol object: v"raz argi: v"raz Symbol message musí b"t názvem zprávy, kterou lze zaslat objektu vzniklému vyhodnocením v"razu object. Zpráva je objektu zaslána s argumenty, vznikl"mi vyhodnocením v"raz! arg1, arg2 atd. Stejn& jako u volání funkce jsou v"razy object, arg1, arg2 atd. vyhodnoceny postupn& zleva doprava. P!íklad 4.8. +ekn&me, 'e pot$ebujeme zji#)ovat polární sou$adnice bod!. Správn" objektov" p$ístup $e#ení této úlohy je následující: definovat nové zprávy, které budeme bod!m k získání t&chto informací zasílat. Definujme tedy nové metody pro t$ídu point: metodu r, která bude vracet vzdálenost bodu od po%átku (první slo'ku jeho polárních sou$adnic), a metodu phi, která bude vracet odchylku spojnice bodu a po%átku od osy x (tedy druhou slo'ku polárních sou- $adnic bodu) Metoda r po%ítá vzdálenost bodu od po%átku pomocí Pythagorovy v&ty: (defmethod r ((point point)) (let ((x (slot-value point 'x)) (y (slot-value point 'y))) (sqrt (+ (* x x) (* y y))))) Průvodce studiem Pokud nechápete, co znamená (point point) v této definici, podívejte se znovu na syntax makra defmethod. Zjistíte, 'e první polo'kou tohoto seznamu je symbol, na n&j' bude p$i vykonávání t&la metody navázán p$íjemce zprávy, zatímco druhou polo'kou je název t$ídy, pro její' instance metodu definujeme.,e jsou ob& tyto hodnoty stejné, nevadí, v Common Lispu mohou b"t názvy prom&nn"ch a t$íd stejné. Po zaslání zprávy r bodu bychom tedy m&li obdr'et jeho vzdálenost od po%átku. Vytvo$me si na zkou#ku instanci t$ídy point a nastavme jí hodnoty slot! x a y na 3 a 4: CL-USER 8 > (setf pt (make-instance 'point)) #<POINT 200BC6A3> CL-USER 9 > (setf (slot-value pt 'x) 3 (slot-value pt 'y) 4) 4
Vytvo$en" objekt reprezentuje geometrick" bod, kter" je znázorn&n na Obrázku 7. 5 4 pt 3 2 1 0 0 1 2 3 4 5 Obrázek 7: Bod o sou$adnicích (3, 4) Nyní zkusme získat vzdálenost tohoto bodu od po%átku zasláním zprávy r na#í instanci (p$ipome(me, 'e zprávy se objekt!m zasílají stejnou syntaxí jakou se volají funkce, tedy v"raz (r pt) znamená zaslání zprávy r objektu pt): CL-USER 10 > (r pt) 5.0 Tento v"sledek by m&l b"t správn&, jeliko' 3 2 +4 2 =5. Podobn& definujme metodu phi (pochopení vy'aduje trochu matematick"ch znalostí): (defmethod phi ((point point)) (let ((x (slot-value point 'x)) (y (slot-value point 'y))) (cond ((> x 0) (atan (/ y x))) ((< x 0) (+ pi (atan (/ y x)))) (t (* (signum y) (/ pi 2)))))) Dal#í zkou#ka: CL-USER 11 > (phi pt) 0.9272952 Tangens tohoto úhlu by m&l b"t roven 4/3 (viz Obrázek 7): CL-USER 12 > (tan *) 1.3333333 Pro úplnost je#t& definujme metody pro nastavení polárních sou$adnic bodu. Narozdíl od p$edchozích budou tyto metody vy'adovat zadání argument!. Vzhledem k tomu, 'e ka'dá z nich m&ní ob& kartézské sou$adnice bodu sou%asn&, bude u'ite%né napsat nejprve metodu pro sou%asné nastavení obou polárních sou$adnic. (defmethod set-r-phi ((point point) r phi) (setf (slot-value point 'x) (* r (cos phi)) (slot-value point 'y) (* r (sin phi))) point)
Metody set-r a set-phi tuto metodu vyu'ijí (p$esn&ji $e%eno, zprávu set-r-phi zasílají): (defmethod set-r ((point point) value) (set-r-phi point value (phi point))) (defmethod set-phi ((point point) value) (set-r-phi point (r point) value)) Poznámka 4.9. Metody set-r-phi, set-r a set-phi vracejí v'dy jako v"sledek parametr point. Tento p$ístup budeme volit ve v#ech metodách, které m&ní stav objektu: v'dy budeme jako v"sledek vracet m&n&n" objekt. D!vodem je, aby #lo objektu m&nit více hodnot v jednom v"razu: (set-r (set-phi pt pi) 1) Nyní m!'eme instancím t$ídy point posílat zprávy set-r-phi, set-r a set-phi a-m&nit tak jejich polární sou$adnice. Vyzkou#ejme to tak, 'e na#emu bodu pt po#leme zprávu set-phi s argumentem 0. Tím bychom m&li zachovat jeho vzdálenost od po- %átku, ale odchylka od osy x by m&la b"t nulová. Zaslání zprávy set-phi s argumentem 0: CL-USER 13 > (set-phi pt 0) #<POINT 200BC6A3> Test polohy transformovaného bodu: CL-USER 14 > (slot-value pt 'x) 5.0 CL-USER 15 > (slot-value pt 'y) 0.0 V"sledek je tedy podle o%ekávání (nová poloha bodu je na druhém konci modrého oblouku na Obrázku 7). 4.3. Inicializace slot" Uka'me si je#t& jednu mo'nost makra defclass. V p$edchozích odstavcích jsme si v#imli, 'e kdy' vytvo$íme novou instanci t$ídy, jsou v#echny její sloty neinicializované a p$i pokusu o získání jejich hodnoty p$ed jejím nastavením dojde k chyb&. To se n&kdy nemusí hodit. Proto makro defclass stanovuje mo'nost, jak specifikovat po%áte%ní hodnotu slot! nov& vytvá$ené instance. V obecn&j#í podob& makra defclass je jeho syntax následující: (defclass name () slots) Makro defclass, 2. verze name: symbol (nevyhodnocuje se) slots: seznam (nevyhodnocuje se)
Prvky seznamu slots mohou b"t bu* symboly, nebo seznamy. Je-li prvkem tohoto seznamu symbol, je jeho v"znam takov", jak ji' bylo $e%eno, tedy specifikuje název slotu instancí t$ídy, kter" není p$i vzniku nové instance inicializován. Je-li prvkem tohoto seznamu seznam, musí b"t jeho tvar následující: (slot-name :initform expr) slot-name: symbol expr: v"raz V tomto p$ípad& specifikuje symbol slot-name název definovaného slotu. V"raz expr je vyhodnocen poka'dé p$i vytvá$ení nové instance t$ídy a jeho hodnota je do p$íslu#ného slotu instance ulo'ena. P!íklad 4.10. Upravme definici t$ídy point tak, aby byly sloty x a y nov"ch instancí inicializovány na hodnotu 0: (defclass point () ((x :initform 0) (y :initform 0))) Jak m!'eme snadno zkusit, sloty nov"ch instancí jsou nyní inicializovány: CL-USER 1 > (setf pt (make-instance 'point)) #<POINT 20095117> CL-USER 2 > (list (slot-value pt 'x) (slot-value pt 'y)) (0 0) P!íklad 4.11. Nyní definujeme dal#í t$ídu, její' instance budou reprezentovat geometrické útvary. Bude to t$ída circle. Jak známo, geometrie ka'dého kruhu je ur%ena jeho po%átkem a polom&rem. Proto budou mít instance této t$ídy dva sloty. Slot center, kter" bude obsahovat instanci t$ídy point a slot radius, kter" bude obsahovat %íslo. Ka'd" z t&chto slot! bude p$i vytvo$ení nové instance automaticky inicializován. (defclass circle () ((center :initform (make-instance 'point)) (radius :initform 1))) Te* ji' necháme na %tená$i, aby si sám zkusil vytvo$it novou instanci této t$ídy a prohlédl její sloty. P!íklad 4.12. Pokud po#leme zprávu objektu, kter" pro ni nemá definovánu metodu (obsluhu této zprávy), dojde k chyb&. M!'eme si to ukázat tak, 'e po#leme zprávu phi instanci t$ídy circle: CL-USER 3 > (phi (make-instance 'circle)) Error: No applicable methods for #<STANDARD-GENERIC-FUNCTION PHI 21694CFA> with args (#<CIRCLE 216C3CF3>) 1 (continue) Call #<STANDARD-GENERIC-FUNCTION PHI 21694CFA> again
2 (abort) Return to level 0. 3 Return to top loop level 0. Type :b for backtrace, :c <option number> to proceed, or :? for other options V tomto hlá#ení o chyb& je t$eba v#imnou si hlavn& textu No applicable methods, kter" znamená, 'e jsme posílali zprávu objektu, kter" pro ni nemá definovanou obsluhu (metodu). Vzhledem k tomu, 'e syntax zasílání zpráv je v Common Lispu stejná jako syntax volání funkce %i aplikace jiného operátoru, nemohou se zprávy jmenovat stejn& jako funkce, makra, nebo speciální operátory. Proto následující definice vyvolá chybu (set je funkce Common Lispu): CL-USER 5 > (defmethod set ((point point) coord value) (cond ((eql coord 'x) (set-x point value)) ((eql coord 'y) (set-y point value)))) Error: SET is defined as an ordinary function #<Function SET 202D54A2> 1 (continue) Discard existing definition and create generic function 2 (abort) Return to level 0. 3 Return to top loop level 0. Type :b for backtrace, :c <option number> to proceed, or :? for other options Pokud bychom se pokusili poslat objektu zprávu, pro ni' jsme nedefinovali metodu pro 'ádnou t$ídu, Common Lisp v!bec nepochopí, 'e se sna'íme poslat zprávu, a bude volání interpretovat jako pou'ití neexistujícího operátoru: CL-USER 7 > (pho (make-instance 'circle)) Error: Undefined operator PHO in form (PHO (MAKE-INSTANCE (QUOTE CIRCLE))). 1 (continue) Try invoking PHO again. 2 Return some values from the form (PHO (MAKE-INSTANCE (QUOTE CIRCLE))). 3 Try invoking something other than PHO with the same arguments. 4 Set the symbol-function of PHO to another function. 5 Set the macro-function of PHO to another function. 6 (abort) Return to level 0. 7 Return to top loop level 0. Type :b for backtrace, :c <option number> to proceed, or :? for other options