Smalltalk je velmi konzistentní jazyk. Definuje několik základních pravidel, která jsou bez výjimky dodržována.
- vše je objekt
- objekty komunikují pouze zasíláním zpráv
- každý objekt má svou třídu
- každá třída má svého předka
Díky striktnímu dodržování těchto pravidel si Smalltalk udržuje svou jednoduchost a eleganci. Zmíněná pravidla předurčují celou třídní organizaci Smalltalku a velmi transparentně a jakoby mimochodem přinášejí do tohoto jazyka řadu užitečných vlastností, se kterými se většina ostatních programovacích jazyků vypořádává jen stěží.
V minulém dílu jsme se zmínili o tom, že každý objekt má svou příslušnost ke třídě trvale přiřazenu a nelze ji běžným způsobem změnit. Jedná se o referenci v rámci fyzického uložení objektu v objektové paměti. Je-li objekt reprezentací konkrétních dat, pak třída definuje operace nad těmito daty, přičemž konkrétní data jsou velmi silně zapouzdřena a jsou přístupná pouze pomocí poskytnutých operací.
Data objektu jsou reprezentována jeho instančními proměnnými, což jsou v podstatě reference na další objekty. Instanční proměnné jsou osobním majetkem objektu a pouze on rozhoduje o tom, jak a zda vůbec tato data zveřejní. Instanční proměnné jsou výhradně privátní.
Objety jedné třídy mají možnost společně sdílet data pomocí třídních proměnných. Jedná se o data společná všem instancím dané třídy a mají rovněž privátní charakter.
Nyní již máme základní informace pro to, abychom se pokusili vytvořit si svoji první třídu. Asi vás již moc nepřekvapí, že se nejedná o syntakticky specifickou operaci. Třídy se prostě vytvářejí obyčejným zasíláním zpráv.
Object subclass: #MyClass instanceVariableNames: 'instVar1 instVar2 ' classVariableNames: 'ClassVar1 ClassVar2 ' poolDictionaries: '' category: 'MyClasses'
Zkusíme si nyní podrobně rozepsat, čeho jsme to vlastně docílili.
Třídě Object jsme poslali zprávu pro vytvoření potomka, která má pět parametrů. Prvním parametrem je jméno nové třídy, které je předáváno jako symbol. Jméno třídy musí začínat velkým písmenem a musí být v celém prostředí Squeaku unikátní.
Dalším parametrem je seznam instančních proměnných uvedený v řetězci, kde identifikátory jednotlivých proměnných jsou odděleny mezerami. Jejich jména by měla začínat malým písmenem. Stejně jako u lokálních proměnných se neuvádějí typy.
Následujícím parametrem je řetězcový seznam identifikátorů třídních proměnných, z nichž každá začíná velkým písmenem.
Posledním parametrem je kategorie třídy. Třídy jsou organizovány do tzv. systémových kategorií. Na jejich pojmenování příliš nezáleží a ani sémanticky nehrají žádnou úlohu. Slouží pouze pro lepší orientaci programátorů ve standardních třídách a vlastních projektech. Rovněž usnadňují export a import zdrojových kódů. Třídy obsažené v jedné kategorii spolu nějakým způsobem souvisejí, většinou tvoří určitý logický celek, projekt či jeho část.
Záměrně jsem vynechal parametr definující sdílené slovníky (poolDictionaries). Nechávejte jej prázdný (prázdný řetězec). Sdílené slovníky nejsou příliš významným rysem jazyka Smalltalk a povíme si o nich později.
Jak můžete vidět, Smalltalk používá pouze jednoduchou dědičnost. Nezná ani pojem rozhraní (interface) známý např. z Javy nebo C#. Díky dynamické typové kontrole jsou vícetypovostní koncepty podstatně méně potřebné a Smalltalk je natolik flexibilní, že je lze poměrně snadno doplnit. To pro případ, že by vám skutečně chyběly. Smalltalk nepoužívá vnořené třídy.
Možná zatím marně hledáte něco jako jmenné prostory. Prozatím můžeme zůstat u tvrzení, že Squeak jmenné prostory nemá. Není to sice tak úplně pravda, ovšem prostředky, které Squeak v tomto ohledu nabízí, se používají v praxi jen minimálně. Absence jmenných prostorů krom pár výhod samozřejmě přináší i některé problémy, které se programátoři snaží omezit například specifickými prefixy jmen tříd (B3D pro třídy projektu Balloon3D apod.). Nutno dodat, že vzhledem k odlišnému přístupu výstavby programů se problémy s duplicitou globálních identifikátorů objevují jen minimálně, a jak bylo řečeno, Squeak má prostředky, jak je v případě nouze řešit.
Podobně jako jsou třídy organizovány v systémových kategoriích, jsou i jednotlivé metody v rámci tříd organizovány do kategorií. Kategorie metod mají rovněž pouze informativní charakter a nemají žádný sémantický význam. Jejich pojmenování je zcela v rukou programátora, ovšem je vhodné se držet některých konvencí, aby se ve vašem kódu mohl snadno kdokoliv rychle zorientovat. Často používanými jmény kategorií jsou například:
accessing | přístup k vlastnostem objektů |
instance creation initialize |
metody související s vytvářením a inicializací objektů |
copying | kopírovací konstruktory |
printing | výpisy textové reprezentace objektu do streamu |
fileIn/Out | serializace |
testing | metody typu isSomething apod. |
convrting | konverze objektů (asSomething) |
private | neveřejné metody |
examples | příklady |
Snaha řešit věci co nejjednoduššeji se dotkla i organizace metod. Smalltalk nepoužívá přístupová práva k metodám (public, protected, private apod.). Všechny metody dané třídy včetně metod v kategorii private jsou veřejné (public). Připomeňme, že objekty mezi sebou mohou komunikovat pouze zasíláním zpráv, a tudíž si nelze žádným jiným způsobem zajistit přístup k instančním a třídním proměnným. Data objektu mohou zprostředkovávat pouze jeho metody.
Jedno ze základních pravidel Smalltalku říká, že každá třída má svého předka. Jak je tomu ale u nejobecnějších tříd? Například základní třída třídní hierarchie – Object – musí tím pádem být také potomkem nějaké třídy. Ano, i třída Object má svého předka. Je jím třída ProtoObject, která definuje naprosté minimum vlastností, které musí mít všechny objekty ve Smalltalku (např. metody isNil:,ifNil: apod.). Nicméně podle daných pravidel Smalltalku musí i třída ProtoObject dědit z nějaké třídy. Abychom také někdy skončili, použije se zde zacyklení. Třída ProtoObject dědí sama ze sebe. Znamená to, že je popsána natolik obecně, že dokáže popsat i sebe samu.
Při vytváření vlastních tříd používejte třídu Object a její potomky a nepokoušejte se vytvořit vlastní smyčky v dědičnosti. Standardním způsobem by se vám to nepovedlo, protože jejich vznik je kontrolován a zakázán.
Pro dědění speciálních tříd se používá ještě metoda, jejíž jméno nezačíná na subclass:, ale na variableSubclass:. Vyskytuje se například u odvozování tříd ze třídy Array apod. Pokud je tento speciální druh dědičnosti třeba využít, upraví Squeak vaši definici třídy sám dle potřeby.
Metatřídy
Další ze základních pravidel Smalltalku definuje, že každý objekt má svoji třídu. Protože ve Smalltalku jsou vše objekty, a tudíž i třídy jsou objekty, musí to nutně znamenat, že rovněž třídy mají svoje třídy. Tyto třídy se označují jako metatřídy.
Význam tříd spočívá v tom, že popisují chování objektů (svých instancí). Podobně metatřídy popisují chování tříd. Jak jsme již jednou poznamenali, jsou třídy v podstatě globálně přístupné proměnné (objekty).
Na první pohled může být existence metatříd poměrně matoucí, ale není třeba v nich hledat nic záhadného. Jejich význam snad osvětlí následující příklad:
Mějme třídu Time uchovávající informace o čase pomocí jedné instanční proměnné seconds. Třída Time má mimo jiné metody hours a minutes, které vracejí hodnotu vypočítanou z počtu sekund uloženého v instanční proměnné seconds. Jsou to metody, jejichž výsledek závisí na aktuálních datech konkrétní instance a jsou popsány ve třídě. My ovšem potřebujeme konkrétní instanci nějakým způsobem vytvořit a referenci na ni vložit do proměnné, kterou si pojmenujeme třeba time. Chtěli bychom, aby to vypadalo například nějak takhle.
| time | time new: Time.
Problém je v tom, že se pokoušíme zavolat zprávu neinicializovaného objektu time (má hodnotu nil). Potřebovali bychom mít už alespoň jednu existující inicializovanou instanci. Vyřešíme to tak, že o vytvoření instance požádáme přímo třídu Time.
| time | time := Time new.
Nyní už je vše v pořádku, ovšem kam umístit metodu new, když ve třídě Time být nemůže, protože by popisovala chování instance? Zde využijeme právě metatřídu. Jak konkrétně, to si ukážeme příště.
Třída implementuje zprávy, které budou vykonávat její instance, metatřídy implementují zprávy, které budou vykonávat třídy. Jak jsme si minule řekli, třídu konkrétního objektu zjistíme pomocí metody class, tedy
time class = Time
Podobně se můžeme zeptat na třídu i třídy Time. To, co obdržíme, je právě její metatřída
Time class = Time class
Tedy metatřída třídy Time se jmenuje Time class(pravá strana rovnosti). Výraz pro získání metatřídy a její jméno jsou si rovny. Protože samozřejmě i metatřídy jsou objekty, i ony mají svoji třídu, která má rovněž svoji třídu.
Time class class = Metaclass Time class class class = Metaclass class Time class class class class = Metaclass class
Jak vidíte, narazili jsme na další elegantní smyčku.
Metatřída může implementovat celou řadu metod. Nejčastější jsou konstruktory, což jsou libovolné metody metatřídy vytvářející nějakým způsobem instance. Například metatřída třídy Time má konstruktor now, který vytvoří instanci obsahující aktuální čas.
Time now asString = '3:43:25 pm'
Další příklad konstruktoru můžete vidět v našem starém známém prográmku Hello world:
Speaker woman say: 'hello world'.
Třídě Speaker zašleme zprávu woman, čímž se vytvoří objekt reprezentující hlasovou syntézu ženského hlasu, kterému následně pošleme zprávu say: s požadovanou frází.
Asi vás napadá celá řada nových otázek. Jak a kam se ve Smalltalku píší zdrojové kódy, když třídy se vytvářejí pomocí metody. Jak a kam se píší metody? Jak vlastně metody vypadají? Jak se vytvářejí metatřídy? Jak je to s destruktory nebo třeba kopírovacími konstruktory? Pokud jste se již pokusili si Squeak nainstalovat, možná začínáte některé odpovědi tušit. Pokud ne, budete si na ně muset počkat do dalších dílů.