Objektově orientované programování

Doc. Dr. Vladimír Homola, Ph.D.

Anotace

Článek si klade za cíl uvést čtenáře do problematiky objektového programování. Ve své první kapitole tak činí pokud možno obecně, dokonce bez ohledu na programování. Počínaje druhou kapitolou však jde o ryze programátorské aspekty objektové problematiky a zde už není možno pominout programovací jazyk. Každý má totiž různé syntaktické vyjádření více méně stejně implementovaného objektového mechanismu. Autor tohoto článku očekává, že čtenářskou obec budou tvořit odborníci profesí nikoliv programátorskou, ale kteří přesto budou mít snahu o drobná programátorská dílka ve svém vědním oboru. Protože tak budou činit v Česku, bude to z 99% v prostředí Windows, drobného to dílka kolegy Gatese. A v tomto prostředí zvolí z 99% jazyk Basic, protože ho zmíněný Gates nacpal kam se dalo. Po pravdě, má úspěch díky zdařilému velmi silnému syntaktickému rozšíření, a přitom dokázal zůstat jazykem pro důchodce a ženy v domácnosti. Hlavním důvodem je však to, že pouze pro jazyk Basic je k disposici nejen překladač, ale také interpret spustitelný z jakékoliv aplikace. A toho dnes využívá snad každá aplikace, která si přeje být komerčně úspěšná. To vše a další důvody vedly k tomu, že i v tomto článku byl jako demonstrační jazyk zvolen Basic, a to z Microsoft Visual Studia .NET a následujících. V omezeném rozsahu pak informace z článku lze použít i v Basicu z Visual Studia 6 - tj. v tom, který podporuje interpret spustitelný z aplikací typu Word, Excel, Access, AutoCad, Corel, Surfer a další, a který bývá označován jako VBA (Visual Basic for Application). Obsah článku by podle mínění autora mohl posloužit přímo k použití ve vlastních aplikacích, rozhodně však není referenční příručkou a nepopisuje všechny možnosti a varianty konstrukcí a příkazů, které lze použít v objektově orientovaném programování.

1. Pojem "Objekt"

Objektová orientace nějaké činnosti je metoda plánování a vykonávání činnosti spočívající v co největší abstrakci a zevšeobecnění předmětů činnosti a jejich chování na jedné straně a vazeb mezi těmito předměty na straně druhé. Při takto orientované činnosti dochází ke strukturování jednotlivých kroků jejího návrhu i provedení, přičemž jsou zdůrazněny obecné vnější znaky a potlačeny vnitřní detaily. Snahou přitom je definovat, popsat nějakou vlastnost nějakého předmětu činnosti na jednom jediném místě - a to i v případě, že jde o logicky stejnou vlastnost dvou různých předmětů. Totéž pak platí o chování předmětů činnosti.
 

Objektová orientace výroby psacích potřeb by tedy podle toho spočívala v co největší abstrakci a zevšeobecnění předmětů činnosti, tedy např. tužky. Zdůrazněnými vnějšími znaky mohou být cena, výrobní doba a tvrdost. Zdůrazněným chováním pak může být činnost před použitím. Potlačenými detailními znaky mohou být Datum a kdo tužku vyrobil.

Taková abstrakce předmětu nějaké činnosti, která s sebou nese popis svých vlastností i svého chování, se v popisované metodě nazývá objekt (angl. object). Stav, kdy vlastnosti i chování, kterými se objekt projevuje navenek (aniž by zvenčí zpřístupňoval detaily), jsou integrální součástí objektu, se označuje termínem zapouzdření (angl. encapsulation).
 

Zobecnění předmětu činnosti (zde tužky) jako abstraktní objekt, který zapouzdřuje (všechny) charakteristiky a chování, ale vystavuje navenek jen (některé) - ty, které pro jeho použití považuje návrhář tohoto objektu za důležité.

Každý objekt je tedy větším či menším zevšeobecněním hmotného či nehmotného prvku reálného či imaginárního světa. Má žádnou, jednu nebo více charakteristik, tzv. vlastností (angl. property - j.č., properties - mn.č.). Chování objektu je pak především dáno aktuálními hodnotami těchto vlastností. Dále může objekt poskytovat žádnou, jednu nebo více metod (angl. method) jakožto dalšího nástroje pro řízení jeho chování. Každá z metod je v podstatě nějaká akce, která pracuje s hodnotami vlastností objektu - a to nejen s těmi dosažitelnými zevně.

Vlastnostmi objektu Tužka jsou Cena, Doba výroby a Tvrdost. Metodou je činnost, akce - Před použitím.
 

Výroba psacích potřeb nespočívá jen v tužkách - vyrábějí se také pera. Zdůrazněnými vnějšími znaky u per mohou být cena, výrobní doba a objem inkoustu. Zdůrazněným chováním pak může být činnost před použitím. Potlačenými detailními znaky mohou být Délka a také síla hrotu.


 

Zobecněním dalšího předmětu výroby, zde pera, je abstraktní objekt "Pero", který navenek vystavuje vlastnosti obecně jiné než objekt "Tužka".

1.1. Dědičnost a mnohotvárnost

Významným mechanismem, který je dán požadavkem na jedinečné umístění popisu vlastnosti nebo chování, je dědičnost (angl. inheritance). Jestliže totiž mají dva různé objekty stejnou vlastnost, pak si jsou - nejméně touto jednou vlastností - podobné a logicky lze usuzovat, že by mohly být nějak příbuzné, že vznikly z nějakého jejich společného předchůdce (angl. ancestor) a tuto vlastnost zdědily. V tom případě však lze v úvaze pokračovat a usoudit, že zdědily nejen tuto vlastnost, ale všechny vlastnosti (i chování) onoho společného předchůdce, které předchůdce vystavuje navenek. Ten objekt, který zdědil vlastnost, je následník (angl. descendant) svého předchůdce.

Při práci s objekty je žádoucí maximální abstrakce. Proto jak vlastnosti, tak chování objektů by měly být v návrhu abstraktními pojmy, konkretizované až při realizaci. Uvažujme objekt "Výrobek". Ten má jistě nějaké chování při "Použití", protože k čemu by byl výrobek, který nejde použít. Uvažujme dále objekty "Tužka" a "Pero". Oba jsou výrobky a jsou tedy následníky objektu výrobek. Mají tedy také nějaké chování při "Použití". Zdědily "Použití", ale náplň "Použití" je pro každý z těchto výrobků jiná pero nejde ořezat a tužka nejde naplnit. "Použití" objektu "Výrobek" je generalizovaným chováním, přičemž pro "Použití" jeho potomků je charakteristická mnohotvárnost (angl. z řečtiny polymorphism) - každý se může použít jinak. Řečeno jinými slovy, každý následník může přepsat metody svého předchůdce.
 

Předchůdce

Následníci

Zděděno

 

Doba (vlastnost)
Cena (vlastnost)
Před použitím (metoda)

1.2. Hierarchický strom

Výše uvedený princip dědičnosti lze rozvíjet do hloubky. Nechť je objekt A předchůdcem objektu B - tedy objekt B je následníkem objektu A. Nic nebrání tomu, aby objekt B byl sám předchůdcem nějakého objektu C atd. Objekt A však nemusí mít jediného následníka B a ani objekt B nemusí mít jediného následníka C. Pak existuje jediný objekt, který nemá předchůdce - objekt A - a existuje jeden nebo více objektů, které nemají následníka. Taková soustava objektů lze vyjádřit jako hierarchický strom s kořenem A (angl. nejčastěji používaný termín je family tree, českému použití lépe vyhovuje v daném kontextu ne rodinný, ale zde použitý hierarchický).

Objekt B i objekt C jsou oba následníky objektu A - oba zdědily od A jeho vlastnosti (ovšem C navíc i od B). Ve zřejmém smyslu se pak použije termín bezprostřední (angl. immediate) následník, analogicky bezprostřední předchůdce. O bezprostředním předchůdci se také říká, že je bází (angl. base) svého bezprostředního následníka.

Při výrobě psacích potřeb není nutno zůstat v objektovém modelu na objektech "Tužka" a "Pero". Objekt "Tužka" - sám následník objektu "Výrobek" - může být předchůdcem objektů "Dřevěná", "Pentilka" a "Verzatilka" a objekt "Dřevěná" může být předchůdcem objektů "S gumou" a "Bez gumy" atd. podle následujícího schématu:
 

 

Daleko nejznámějším hierarchickým stromem je zřejmě "Strom života" - taxonomická klasifikace objektů přírody podle charakteristických znaků. Jednotlivé úrovně předchůdcovství a následnictví jsou zde označovány konkrétními termíny (..., Rod, Druh, ...), přičemž objekt na úrovni Druh je následníkem objektu na úrovni Rod. Uveďme pro ilustraci část tohoto stromu odpovídající kdysi pobuřujícímu výroku "Člověk pochází z opice". Je zde dobře vidět základní logika tvorby objektů; je-li jedinec opicí (tj. má všechny její vlastnosti a metody), potom ho lze přidělit k některému z následníků "Ploskonosí" a "Úzkonosí" - ti zdědily všechny vlastnosti a metody podřádu "Opice" (kdyby některou neměly, nejsou to opice). Na druhé straně "Ploskonosí" mají některé vlastnosti zohledňované až při začleňování do infrařádu, které nemají "Úzkonosí" a naopak - kdyby neměly nic rozdílného nebo navíc, nemá smysl kategorizace na úrovni infrařádu.
 

 

1.3. Třída a instance

Objektově orientovaná činnost pracuje s objekty ve dvou zcela odlišných úrovních, které se velmi často zaměňují.

Logicky první úroveň je plánování činnosti - jen totální neprofesionál ji začne vykonávat aniž by si ji naplánoval. Plánuje-li se výroba, zavede se jako abstraktní kategorie "Výrobek". Ten má jakousi abstraktní "Cenu", "Dobu výroby" a "Použití". I abstraktní kategorie "Tužka" a "Pero" mají cenu, dobu výroby a použití, jsou tedy potomci kategorie "Výrobek". Ovšem "Tužka" má navíc "Tvrdost" a "Pero" navíc "Objem". Tyto abstraktní kategorie bývají označovány jako třída (angl. class) objektu.

Další úrovní objektově orientované činnosti je vlastní vykonání. Začne-li se vyrábět, začnou se do světa chrlit miliony tužek a per. Každá konkrétní tužka bude odpovídat abstraktnímu vzoru - třídě - "Tužka"; každá konkrétní tužka je tedy instancí (angl. instance) objektu třídy "Tužka".
 

Třída objektu

Instance objektu dané třídy

V objektově orientovaném světě (a zvláště v objektově orientovaném programování) se pak velmi často používá jen samotný pojem objekt (angl. object). Je to v případech, kdy lze z kontextu odvodit, zda jde o třídu objektu nebo instanci objektu. Řekne-li se "definice objektu", jde zřejmě o třídu, řekne-li se "objekt Pentilka", jde zřejmě o instanci. Přesto se doporučuje tyto dva pojmy explicitně rozlišovat.

1.4. Konstruktor a destruktor

Shrnuto, pak např. každý konkrétní člověk je instancí objektu třídy "Homo", některý pak i "Sapiens". Nejdůležitějšími okamžiky v životě takové instance je okamžik, kdy na svět přijde nový člověk a kdy tentýž ze světa odchází. Jsou to tak důležité okamžiky, že je logicky musí mít každý objekt.

Činnosti vykonávané při vzniku instance objektu se nazývají konstruktor (angl. constructor). Činnosti vykonávané při zániku instance objektu se nazývají destruktor (angl. destructor). Protože v obou případech jde o činnosti, akce prováděné s objektem, jsou realizovány jako metody objektu a tyto dvě metody má každý objekt, aniž by se musely nějak explicitně definovat. Počátečním obsahem je prázdná činnost - neprovádějí nic  U konstruktoru a destruktoru je však specifická situace co se týče dědění: jestliže autor objektové rodiny explicitně definuje činnost konstruktoru a (nebo) destruktoru pro nějaký objekt nějaké úrovně hierarchického stromu, pak musí explicitně definovat činnost konstruktoru a (nebo) destruktoru pro všechny jeho následníky. Přesnější popis tohoto požadavku pro oblast objektového programování je podán níže.

2. Objekty při programování

Programování jakožto činnost, při které vzniká zápis textu programu řídícího činnost výpočetního systému, je bezesporu oblastí, ve které se objektová orientace projeví nejzřetelněji. Důvodem je už jen to, že se při programování používá nějakého programovacího jazyka, tedy formalizovaného prostředku pro vyjádření pracovních postupů při zpracování dat. Pomocí programovacího jazyka se také princip objektově orientovaných činností vysvětluje nejlépe.

Pokusme se objektovou problematiku vysvětlit na programovém zajištění chodu naší obrovské firmy. Má spoustu oddělení (asi dvě), kmitají tam zástupy pracovníků (asi šest) a makají a makají a měsíčně prodáme zboží za tisíce korun (asi tři).

Jsme sice kapitalisti, ale s lidskou tváří - a proto je pro nás středobodem každý jednotlivý zaměstnanec:

To je jeden z nich. Honza Nováků, bezva kluk. Je u nás už od ledna ' 15. Má skoro dva metry, za večer udělá nejmíň metr piv. Dáváme mu osm a půl, vždyť platí asi na pět potomků celkem šest tisíc. No a takových máme ...

 

Je sice pěkné, že Honza je vazoun a že udělá metr piv, ale z hlediska firmy nás to tak moc nezajímá; hlavně že ráno přijde, a to střízlivý. Ovšem to, že je velmi plodný začíná být pro firmu velmi důležité v momentě, kdy Honzovi stanoví exekuce na výživné a ty je povinna odvést firma z jeho platu dřív, než mu dá zbytek. Firmu tedy zajímá hlavně jak se jmenuje, kdy nastoupil, kolik mu platíme a jaké jsou zákonné srážky. To jsou údaje, které obecně sledujeme pro všechny zaměstnance bez ohledu na jejich zařazení, stav a další okolnosti. A na tomto modelu postavme další výklad.

2.1. Datové struktury

Historicky nejdříve se programovacími jazyky zpracovávala data po jednotlivých hodnotách jednotlivých typů, nanejvýš se hodnoty sdružovaly do polí hodnot. Konstanty a proměnné byly tedy např. typu "Textový řetězec" s hodnotou "Jan Novák" nebo "Celé číslo" s hodnotou 8500. Deklarovaly se proměnné např.

Dim Jmeno As String
Dim Plat As Long
Dim Nastup As Date
Dim Srazky As Long

V problematice datových struktur a objektů zvlášť je důležité mít na paměti základní fakt vyšších programovacích jazyků: identifikátory proměnných jsou symboly zastupující adresu místa v operační paměti počítače, kde jsou uloženy hodnoty zpracovávaných dat. Schematicky to je možno pro uvedený příklad jednoduchých proměnných znázornit např. takto:
 

Proměnná:   Jmeno Plat Nastup   Srazky
Paměť:  
Adresa:   2 252 2 256 2 260 2 264 2 268 2 272 2 276 2 280 2 284

Postupem času vznikla nutnost sdružovat hodnoty různých typů, které jakýmsi způsobem logicky "patří k sobě". Například data typu "Textový řetězec" s hodnotou "Jan Novák", "Celé číslo" s hodnotou 8 500, "Datum" s hodnotou 1/1/2005 a celé číslo 6 000 logicky patří k sobě, protože to jsou data jednoho konkrétního zaměstnance popisující jeho jméno, plat, datum nástupu a srážky. Programovací jazyky na takovou nutnost reagovaly možností používat nejen "strojové" typy dat, ale také "uživatelské" typy dat - datové struktury. Popis takového uživatelského datového typu - datové struktury - může být např.

Type Zamestnanec
   Jmeno As String
   Plat As Long
   Nastup As Date
   Srazky As Long
End Type

 

Po uvedení popisu typu je možno zavést proměnnou tohoto typu a pracovat s ní:

Dim Honza As Zamestnanec
Dim Jirka As Zamestnanec
...
With Honza
   .Jmeno = "Jan Novák"
   .Plat = 8500
   .Nastup = #1/1/2005#
   .Srazky = 6000
End With
...
Jirka.Plat = 9200

Zaměstnanec Honza je pak v paměti uložen takto:
 

Proměnná:   Honza                
   
Datové pole::   Jmeno Plat Nastup Srazky
Paměť:                    
Adresa:   2 252 2 256 2 260 2 264 2 268 2 272 2 276 2 280 2 284

S proměnnými uživatelských typů (= uživatelských datových struktur) lze provádět většinu toho co s jednoduchými proměnnými. Zvláště mohou být použity jako parametry programových jednotek, výsledky funkcí a prvky polí.

Čas však plynul dál a programátorská veřejnost začala pociťovat absenci některých mechanismů při práci s uživatelskými typy. Uveďme zde alespoň dvě situace:

  1. V programu jsou zpracováni zaměstnanci. Ale také jejich šéf. Ten má stejně jako každý jiný zaměstnanec jméno, plat a datum nástupu, ale liší se od nich: má příplatek za šéfování. Datová struktura pro šéfa lze samozřejmě vytvořit analogicky. Mechanickým postupem to jde takto: zkopíruje se struktura Zamestnanec, přejmenuje na Chef a přidá datové pole Priplatek. Jenže pro výpis, zápis, čtení a další akce se šéfem se musí vytvořit analogické podprogramy jako pro zaměstnance, ale co je horší: u zaměstnanců bude najednou zapotřebí sledovat ještě navíc oddělení, kde pracují. Tak se do typu Zamestnanec vloží datové pole Oddeleni - ale nezapomene se totéž udělat i pro šéfa? Kdo na to má pořád myslet? Nejde tohle nějak zajistit?

  2. V programu jsou použity proměnné Honza, Jirka a další, všechny typu Zamestnanec. Všechny je zapotřebí čas od času vypsat, zapsat, načíst - provést stejnou akci s různými lidmi. Klasický programátorský postup spočívá v sestavení samostatných programových jednotek (procedur, funkcí), které jako parametr mají zaměstnance. Podle toho, s jakým aktuálním parametrem = zaměstnancem se volají, takový je výsledek jejich činnosti. Pravda, je to osobní záležitostí programátora, ale jak už to jsou lidé roztržití až bohémští, vždy u nich hrozí nebezpečí, že použijí nesprávný podprogram se správným zaměstnancem nebo naopak správný podprogram pustí na nesprávného zaměstnance. Proč nemít možnost umístit dovnitř struktury dat také akce které se s daty mají provést?

A to je přesně okamžik, kdy se zrodil objekt.

2.2. Definice objektové třídy

Po formální stránce to vypadá na první pohled v nejjednodušším případě jako trochu obměněná definice struktury:

Class Zamestnanec
   Public Jmeno As String
   Public Plat As Long
   Public Nastup As Date
   Public Srazky As Long
End Class

První viditelnou změnou je výměna klíčového slova Structure (datová struktura) za Class (objektová třída), druhou změnou je přidaný specifikátor Public - jinak má zápis (zatím) rysy struktury. Skutečně v pojetí Microsoft NET (Pozor! To nemusí platit v jiných programových prostředích, např. v Pascalu a už vůbec ne v jiných systémech) mají struktury a objekty mnoho podobného a ovšem také mnoho rozdílného.

Podobnosti a odlišnosti jsou souhrnně vyjmenovány v odstavci níže. Pro další výklad vytkněme snad tu nejpodstatnější odlišnost:

Proměnné strukturového typu jsou data, proměnné objektové třídy jsou odkazy (adresy, reference).

Přesně to znamená toto:

Uvedený text navozuje hned dvě otázky: 1. Jak nebo kdo vloží do proměnné objektové třídy příslušnou adresu a 2. proč se zatím v textu vyskytuje objekt jen v podobě přídavného jména a nikoliv podstatného? Odpověď na obě otázky podá společně následující odstavec.

2.3. Třída a instance

V objektovém prostředí je ústředním pojmem objekt. Při práci v objektovém prostředí je však nutno s pojmy pracovat exaktně a tedy odstranit dvojznačnost právě pojmu reprezentovaného podstatným jménem objekt. Mluvnicky a na první pohled i formálně jsou správná slovní spojení Objekt Zamestnanec a také Objekt Honza. Je zde však podstatná odlišnost: zatímco Zamestnanec je identifikátor typu dat, je Honza identifikátorem proměnné tohoto typu. Proto samotný pojem Objekt by neměl bez dalšího upřesnění být používán samostatně a proto také jsou naznačené dvě kategorie odlišeny terminologicky.

Objektová třída je jakýsi vzor, šablona (zde Zamestnanec), podle které se vytváří jednotlivé konkrétní výskyty, kopie, instance (zde Honza). Nepoužívá se tedy spojení Objekt Zamestnanec, ale Objektová třída Zamestnanec, ne Objekt Honza, ale Instance Honza (objektové třídy Zamestnanec).
 

Třída Instance Instance

 

V programu se objektová třída zavádí popisem, definicí, deklarací objektové třídy - např. jak shora uvedeno

Class Zamestnanec
   Public Jmeno As String
   Public Plat As Long
   Public Nastup As Date
   Public Srazky As Long
End Class

Proměnné objektového typu jsou deklarovány jako každé jiné proměnné v daném programovacím jazyce, zde tedy (viz Anotaci shora) v Basicu .NET - např.

Dim Honza As Zamestnanec

Honza je zde identifikátorem proměnné objektové třídy. Precizně vzato, ještě není instancí. Je proměnnou, která bude obsahovat adresu paměti, kde se budou rozprostírat data v množství a pořadí daném definicí dané objektové třídy. Instancí začne být v okamžiku, kdy někdo paměť pro data vyhradí a adresu této paměti vloží do proměnné Honza. Tím někým, kdo vyhrazuje a přiděluje oblasti paměti, je operační systém.

V systémech Microsoftu se uvedeným způsobem přiděluje paměť z dynamického zásobníku paměti spravované a přidělované operačním systémem (angl. heap) na vyžádání aplikace. Aplikace napsaná v jazyku Basic vyžádá místo v této části paměti pro proměnnou Honza přiřazovacím příkazem za použití klíčového slova New:

Honza = New Zamestnanec

Teprve nyní začne instance zaměstnance s přístupem přes identifikátor Honza skutečně existovat a je možno začít pracovat s jeho datovými poli, např.

Honza.Jmeno = "Jan Novák"

Situace v paměti při deklarování Honzy jako objektu třídy Zaměstnanec je pak následující:
 

Proměnná:   Honza     Paměť přidělená v oblasti zvané Heap:
         
      Dat.pole: Jmeno         Plat Nastup   Srazky
Obsah paměti:   29 252 →————+ ...                  
Adresa:   1234 + —————→ 29 252 29 256 29 260 29 264 29 268 29 272 29 276 29 280 29 284

Během práce aplikace však může dojít k situaci, že Honza už není zapotřebí a je možno jemu přidělenou paměť (které nikdy není dost) uvolnit. Uvolnění paměti provádí opět operační systém, v Basicu se o to systém požádá pomocí klíčového slova Nothing (doslova Nic):

Honza = Nothing

Operační systém paměť uvolní, do proměnné Honza se vloží indikace nepřidělené paměti a s daty Honzy není možno nadále pracovat, protože prostě už neexistují. Podrobně ve vztahu k objektům je uvedený mechanismus popsán v kapitole "Ze života objektu" níže; zde byly uvedeny zatím jen základní informace.

2.4. Objekty a schopnost dědit

Tento odstavec opakuje vývoj od struktury až k objektové třídě a navazuje na otázku uvedenou pod písmenem a. z odstavce 2.1 (viz).

Na první pohled se programátorům zdá objekt jako něco velmi podobného datové struktuře - uživatelskému datovému typu, který je jakousi obálkou udržující pospolu pod jedním jménem několik logicky svázaných datových elementů - viz shora uvedený uživatelský typ Zamestnanec. Zaměstnanec je zde typem, strukturou, záznamem či jak se tomu v nejrůznějších programovacích jazycích říká. Je to jakási šablona pro překladač, který podle ní bude generovat jednotlivé konkrétní proměnné (instance) typu Zaměstnanec.

S typem Zaměstnanec lze pracovat na dvou úrovních. Je-li zapotřebí pracovat s platem nebo jménem zaměstnance přímo, jsou dostupné jako datová pole datové struktury Zaměstnanec, viz shora např. Jirka.Plat. Na druhé straně lze o Jirkovi uvažovat jako o datovém celku nového zaměstnance firmy, který je do ní zařazován jako celek, ne datové pole po poli: Firma.Add Jirka.

Uvažujme nyní problém nastíněný v bodě a. odstavce 2.1. Ve firmě jsou kromě běžných zaměstnanců také jejich vedoucí, kteří se od nich liší jen tím, že mají příplatek za vedení. Máme takové:

Klasický programátor tedy vytvoří uživatelský typ (datovou strukturu)

Type Chef
   Jmeno As String
   Plat As Long
   Nastup As Date
   Srazky As Long
   Priplatek As Long
End Type

Případně chytřejší programátor využije existence typu Zaměstnanec a Vedoucího vytvoří takto:

Type Chef
   Osobní As Zamestnanec
   Priplatek As Long
End Type

Dim Honza As Zamestnanec
Dim Pavla As Chef
...

To samozřejmě funguje a programátoři to dělají celá léta. Zvláště druhá definice vedoucího je však trochu nepříjemná v tom, že k příplatku vedoucí Pavly se lze dostat "normálně" (Pavla.Priplatek), ale k jejímu jménu už složitěji (Pavla.Osobni.Jmeno).

Tato metoda má ale také větší nedostatek: nenutí programátora uvažovat o podstatě manipulace s daty ve svém vlastním programu. Měl by si přitom klást otázku: Jak se vedoucí liší od běžného zaměstnance? A odpovědět si: vedoucí je zaměstnanec, který má příplatek za vedení. A přesně v tom to je - vedoucí je zaměstnanec ... !

Implicitní v definici zaměstnance jsou jeho osobní data. Objektově orientované programování to bere v úvahu jako specielní vztah osobních datových položek. Protože ale všichni vedoucí musí také mít osobní data, je Vedoucí logickým pokračovatelem Zaměstnance. Vedoucí obsahuje všechno to co Zaměstnanec a přidává něco, co činí Vedoucího Vedoucím. Tento proces, při kterém jeden typ dat zdědí charakteristiky jiného typu, se nazývá dědičnost. Dědic je pak nazývaný následník a typ, ze kterého následník zdědil, se nazývá předchůdce.

Běžné datové typy nemohou dědit. Objektově orientované jazyky však rozšiřují datový typ na novou kategorii datových struktur, které jsou oproti datovým typům daleko silnější při použití, na objektové typy. Definice (popis dat a chování) přitom určuje třídu objektů a každá konkrétní objektová proměnná je pak instancí objektu dané třídy. Třída objektového typu pak může být definována jako kompletní, samostatný typ (Zaměstnanec)  nebo může být definována jako následník existující třídy (Chef). Uvedený příklad může být zapsán např. takto:

Class Zamestnanec
   Public Jmeno As String
   Public Plat As Long
   Public Nastup As Date
   Public Srazky As Long
End Class

Class Chef
   Inherits Zamestnanec
   Public Priplatek As Long
End Class

Dim Honza As Zamestnanec, Pavla As Chef
...

Zde je tedy Zamestnanec předchůdcem Chefa, Chef následníkem Zamestnance. Tento proces může pokračovat déle: lze definovat následníka Chefa a následníka následníka Chefa atd. Velká část práce na designu objektově orientované aplikace spočívá právě v budování objektové hierarchie vyjadřující hierarchický strom objektů aplikace.

Všechny objektové typy eventuelně dědící vlastnosti Zaměstnance jsou následníci objektového typu Zaměstnanec, ale v uvedeném příkladu je objektový typ Chef bezprostřední následník. Objektový typ může mít libovolný počet následníků, ale jediná úroveň je bezprostřední - srov. adresářový strom operačního systému. Analogicky pro konkrétní objektový typ existuje jediný bezprostřední předchůdce.

Objekty mají úzký vztah k datovým strukturám, jak ukazují příklady definice. Nejvýraznější změnou je na první pohled změna klíčového slova Structure na Class. Objekty jsou však principielně něco zcela jiného než strukturové typy, struktury; existuje řada rozdílů - některé na první pohled sotva patrné, jak bude ukázáno později. Například datová pole Jméno a Plat ze Zaměstnance nejsou explicitně popsány v Chefovi, ale přesto je Chef má - získal je na základě dědičnosti. Lze tedy mluvit jménu Chefa (Pavla.Jmeno) stejně jako se mluví o jménu zaměstnance (Honza.Jmeno).

Přístup k datovým polím objektu je přitom formálně stejný jako přístup k datovým polím strukturového typu - pomocí tečky, před kterou je určení konkrétní instance objektu a za ní jméno datového pole. Stejně tak lze stejně použít příkaz With. Místo pěti příkazů

   Pavla.Jmeno = "Pavla Nováčková"
   Pavla.Plat = 10500
   Pavla.Nastup = #1/1/2005#
   Pavla.Srazky = 0
   Pavla.Priplatek = 1500

lze psát přehledněji a pro změny vhodněji

With Pavla
   .Jmeno = "Pavla Nováčková"
   .Plat = 10500
   .Nastup = #1/1/2005#
   .Srazky = 0
   .Priplatek = 1500
End With

 

2.5. Objekty a metody

Tento odstavec opět opakuje některé aspekty vývoje od struktury až k objektové třídě a navazuje na otázku uvedenou pod písmenem b. z odstavce 2.1 (viz).

Přestože k jednotlivým objektovým datovým polím lze přistupovat přímo (Pavla.Plat =10500), nedoporučuje se to. Principy objektově orientovaného programování logicky požadují, aby datová pole objektu byla ponechána samostatně tak dalece, jak je to jen možné. Toto omezení se může zdát na začátku svévolné a přísné; má však dobré důvody dané budováním samostatného, zvenčí zapouzdřeného celku, kterým objekt má být. Hned však vyvstane otázka: a jak tedy mají být datová pole objektu dostupná? Jak je nastavovat a jak je číst?

Odpověď dá další principiální odlišnost objektu od strukturového typu, struktury: objekt může mít metody použitelné především pro přístup k datům.

Metoda je principielně podprogram nebo funkce deklarovaná uvnitř objektu a úzce s tímto objektem svázaná. Je to jeden z nejvýraznějších atributů objektově orientovaného programování, jehož použití přináší výrazné zisky spočívající v přehlednosti, jednoduchosti a v konečné fázi také menší chybovosti při sestavování aplikací.

Uvažujme proces zpracování informací o zaměstnanci. Nejprve klasické použití pomocí uživatelského typu (struktury):

Structure Zamestnanec
   Jmeno As String
   Plat As Long
   Nastup As Date
   Srazky As Long
End Structure

Většina programátorů použije příkaz With:

Dim Honza As Zamestnanec
...
With Honza
   .Jmeno = "Jan Novák"
   .Plat = 8500
   .Nastup = #1/1/2005#
   .Srazky = 6000
End With
 

To sice funguje, ale kód je velmi úzce svázaný s jedním konkrétním zaměstnancem, Honzou. Je-li zapotřebí takto zpracovat více zaměstnanců (= více instancí stejného typu), je k tomu zapotřebí více příkazů With provádějících přesně stejnou činnost, jen s různými daty nad jinou proměnnou. Proto je dalším přirozeným krokem sestavení samostatného podprogramu, který zobecňuje příkaz With při zpracování jakékoliv instance typu Zaměstnanec - ta je zadána jako parametr:

Sub NastavZamestnance (X As Zamestnanec, J As String, P as Long, N as Date, S As Long)
   With X
      .Jmeno = J
      .Plat = P
      .Nastup = N
      .Srazky = S
   End With
End Sub

Použití takového samostatného podprogramu je např. pro Honzu toto:

NastavZamestnance (Honza, "Jan Novák", 8500, #1/1/2005#, 6000)

To svou práci odvede - avšak jestli pociťujete, že je v tom něco nesystematického, cítíte správně. Je to pocit spočívající v tomto: dobrá tedy, vymysleli jsme podprogram obsluhující specielně typ Zaměstnanec. Proč potom musíme stále výslovně uvádět, který typ záznamu a která instance tohoto typu vystupují v podprogramu NastavZaměstnance? Přece by měla být nějaká cesta jak připojit k sobě datový typ a kód, který obsluhuje data typu do souvislého celku.

V objektově orientovaném programování ta cesta je - jsou jí metody. Metoda je podprogram nebo funkce tak pevně svázaná s daným objektovým typem, že si ji lze představit obklopenou skrytým příkazem With zpřístupňujícím data instanci tohoto typu zevnitř metody. Je to dáno tím, že metoda je deklarována, popsána uvnitř popisu samotného objektového typu.

Prakticky se rozšíření třídy Zaměstnanec o metodu Nastav provede takto:

Class Zamestnanec

   Public Jmeno As String
   Public Plat As Long
   Public Nastup As Date
   Public Srazky As Long

   Sub Nastav (J As String, P as Long, N as Date, S As Long)
      Jmeno = J
      Plat = P
      Nastup = N
      Srazky = S
   End Sub

End Class

Každá instance objektu třídy Zaměstnanec tedy s sebou nese nejen svá vlastní data (Jméno, Plat atd.), ale také definice akcí, které se s těmito daty provádí - zde např. nastavení dat na nějakou konkrétní hodnotu. Přístup k metodě je pak stejný jako shora uvedený přístup k datovým polím - uvede se konkrétní instance, tečka a identifikátor metody. Protože metoda sama je podprogram nebo funkce, následuje seznam aktuálních parametrů. Nastavení dat Honzovi pak je zcela průzračné:

Dim Honza As Zamestnanec
...
Honza.Nastav ("Jan Novák", 8500, #1/1/2005#, 6000)
...

 

2.6. Včasná a pozdní vazba, typ Object

Text shora vždy uváděl na příkladech objektové proměnné konkrétní třídy, např. Zamestnanec. Proměnnou objektového typu však můžeme především deklarovat jako proměnnou (obecného) typu, pro který je v programovacím jazyku Basic zaveden identifikátor Object:

Dim Honza As Object

V programovacím jazyku Basic je však proměnná (jakéhokoliv) objektového typu, tedy i obecného Object nikoliv nositelem hodnoty (resp. hodnot) dat, ale nositelem adresy v paměti, kde jsou data umístěna. Je tedy tím, co je v jiných programovacích jazycích nazýváno ukazatelem, pointerem atd. Oblast paměti je v dynamickém zásobníku paměti spravované a přidělované operačním systémem (angl. heap) na vyžádání aplikace - viz odstavec níže. Aplikace však v žádosti musí sdělit, kolik že paměti vlastně chce. Aplikace napsaná v jazyku Basic vyžádá místo v této části paměti pro proměnnou ABC přiřazovacím příkazem za použití klíčového slova New a v něm už identifikátor objektové třídy uveden být musí právě proto, že nese informaci o velikosti paměti:

Honza = New Zamestnanec

Teprve po přidělení paměti (tj. po přiřazení adresy do proměnné - zde Honza) de facto instance objektu dané třídy vznikne a je možno ji začít používat. Má to však jednu nevýhodu: při psaní textu programu editor resp. překladač jazyka neví, že Honza bude třídy zde např. Zaměstnanec - vždyť by to mohl být třeba i veliký Chef!. Pro něj to je obecný objekt a proto nemůže pro Honzu nabídnout např. datové pole Jméno či Plat. Přiřazení Honza.Jmeno = "Novák" se jistě provede v pořádku, ale datové pole Jméno objektu Honza bude identifikováno až při běhu programu - nikoliv při překladu. Takový způsob identifikace datových polí se nazývá pozdní vazba (angl. late binding).

Je však jen málo situací, kdy programátor píšící program neví, jakého konkrétního objektového typu bude daná proměnná. Nejčastěji proto použije určení typu přímo v příkaze Dim. Proměnná objektového typu Zaměstnanec udržující informace o Honzovi se deklaruje příkazem Dim, jak už bylo shora použito

Dim Honza As Zamestnanec

Ovšem stále platí, že proměnná Honza - jelikož je objektového typu - je nositelem ne dat Honzy přímo, ale adresy, kde budou data Honzy v paměti skutečně umístěna. Než se začne s daty Honzy pracovat, musí se požádat o přidělení paměti přiřazovacím příkazem s klíčovým slovem New:

Honza = New Zamestnanec

Teprve nyní začne instance zaměstnance s přístupem přes identifikátor Honza skutečně existovat. Přiřazení Honza.Jmeno = "Novák" se jistě provede v pořádku, ale navíc bude datové pole Jméno identifikováno již překladačem a nemusí se tedy identifikací zdržovat běžící program. Takovému způsobu identifikace datových polí se říká včasná vazba (angl. early binding).

Na první pohled se zdá, že obecný typ Object ne zbytečný a přináší spíše komplikace. Ve skutečnosti existuje řada případů, kdy se využije velmi elegantně jako silný nástroj při práci s hierarchickými stromy implementujícím polymorfismus, zejména pak za použití kolekcí.

2.7. Struktury a třídy

Tento odstavec se týká výhradně prostředí .NET (a návazných) firmy Microsoft. Vypadá to, že si u nich ulehčili práci a když už implementovali objekty se vším všudy, tak úplně stejně - pouhou dereferencí o jeden stupeň - upravili i struktury. Protože však struktury jsou data a objekty adresy, nemohli to udělat úplně totožné. Navíc asi chtěli zachovat co největší kompatibilitu s předchozími verzemi jazyků, zvláště pro Basic. Proto tento odstavec popírá některá shora uvedená tvrzení o strukturových typech, zvláště omezení struktur.

2.7.1. Podobnost

Datové struktury a objektové třídy jsou si podobny v následujících aspektech:

2.7.2. Odlišnost

Datové struktury a objektové třídy se liší v následujících aspektech:

3. Ze života objektu

3.1. Konstruktor

Jak bylo zmíněno v 1.4, činnosti vykonávané při vzniku instance se nazývají konstruktor. Základní činnost provádí operační systém s každou instancí objektu každé třídy - přidělí paměť a adresu jejího počátku přiřadí do identifikátoru objektové proměnné. Je však logické, že i programátor - tvůrce objektové třídy - by měl mít možnost doplnit činnosti vykonávané při vzniku instance o specifické akce podle povahy objektu, např. inicializovat datová pole, načíst hodnoty z registru apod. - to všechno ještě předtím, než se instance objektu poprvé použije.

Možnost definovat své vlastní činnosti při vzniku instance objektové třídy je zajištěna tím, že se jako jedna z metod zapíše metoda s pevně stanoveným identifikátorem New. Protože metoda je podprogramem a podprogram může mít parametry, i metoda New může mít parametry. Celý mechanismus konstruktoru explicitně vytvořeného např. pro Zaměstnance vysvětlí příklad:

Class Zamestnanec

   Public Jmeno As String
   Public Plat As Long
   Public Nastup As Date
   Public Srazky As Long

   Sub New (J As String, P as Long, N as Date, S As Long)
      Jmeno = J
      Plat = P
      Nastup = N
      Srazky = S
   End Sub

End Class

Konstruktor třídy Zaměstnanec tedy inicializuje datová pole zaměstnance počátečními hodnotami rovnými aktuálním parametrům metody New. Použití konstruktoru je pak následující:

Dim Honza As Zamestnanec
...
Honza = New Zamestnanec ("Jan Novák", 8500, #1/1/2005#, 6000)

V jazyku Basic.NET existuje další možnost použití konstruktoru, a to přímo v příkaze Dim:

Dim Honza As New Zamestnanec ("Jan Novák", 8500, #1/1/2005#, 6000)

Konstruktor New se logicky volá jedinkrát za "dobu života" instance a to v okamžiku, kdy je instance objektu dané třídy vytvářena. Nelze ho volat explicitně opakovaně. Je však zajištěno, že kód napsaný autorem objektu v podprogramu New se vykoná ještě před jakýmkoliv jiným kódem dané třídy. Jestliže autor třídy podprogram New do definice třídy vůbec nevloží, pak je implicitně vytvořen při běhu programu s prázdným obsahem (tj. provedou se jen alokační a inicializační akce dané systémem, ale nic dalšího).

3.2. Objekty MyBase a Me

Zákaz explicitního volání konstruktoru New má jednu výjimku - naopak v jednom případě se konstruktor explicitně volat musí. Uvažujme příklad uvedený shora, kdy byla definována třída Chef jako následník třídy Zaměstnanec:

Class Chef
   Inherits Zamestnanec
   Public Priplatek As Long
End Class

Jestliže se nyní deklaruje vedoucí Pavla typu Chef

Dim Pavla As Chef

pak instance objektu třídy Chef odkazovaná identifikátorem Pavla nezačne existovat, dokud se neprovede příkaz

Pavla = New Chef

Pavla jako Chef ovšem zdědil všechny vlastnosti i metody od Zaměstnance a proto v okamžiku vytváření nové instance Chefa (tj. v okamžiku volání Chefova konstruktoru) se nejprve volá konstruktor jeho předchůdce, tj. Zaměstnance. Jenomže má-li tento konstruktor parametry, jak by se vědělo, s jakými aktuálními hodnotami parametrů se má onen "nadřízený" konstruktor volat?

Proto je přijato toto pravidlo: Autor objektové třídy nemusí explicitně konstruktor v nějaké třídě napsat. Jakmile však tak učiní a vytvoří konstruktor s parametry, pak musí také a) explicitně napsat konstruktor New všech následníků této třídy a b) jako první příkaz následnického konstruktoru volat konstruktor svého předchůdce.

Pozn.: toto pravidlo neplatí pro konstruktory bez parametrů ve třídě předchůdce - tam následnická třída konstruktor mít nemusí.

Za a) splní autor třídy jednoduše: prostě kamkoliv mezi jiné metody napíše metodu New. Ovšem při plnění požadavku b) nastane problém: Jestliže uvnitř podprogramu New se volá podprogram New, jde o klasické rekurzivní volání, kdy podprogram volá sám sebe. Jak tedy rozlišit své vlastní identifikátory a identifikátory svého předchůdce?

Pro toto rozlišení zavádí programovací jazyky dva vyhrazené identifikátory - jeden pro předchůdce a jeden sám pro sebe. Jazyk Basic.NET a násl. je má konkrétně zavedeny takto:

Nyní jsou k disposici všechny syntaktické nástroje a je možno uvést příklad definice třídy Chef v případě, že bázová třída Zaměstnanec explicitně obsahuje konstruktor New jak uvedeno shora:

 

Class Chef

   Inherits Zamestnanec
   Public Priplatek As Long

   Sub New (J As String, P as Long, N as Date, S As Long, R as Long)
      MyBase.New (J, P, N, S)
      Priplatek = R
   End Sub

End Class

Vytvoření instance Chefa pak provede příkaz

Dim Pavla As Chef
...
Pavla = New Chef ("Pavla Nováčková", 10500, #1/1/2005#, 0, 1500)

nebo také

Dim Pavla As New Chef ("Pavla Nováčková", 10500, #1/1/2005#, 0, 1500)

3.3. Nothing

V předchozích odstavcích bylo podrobně vysvětleno, jak objekt vzniká a jak se mu přiděluje paměť - použitím New. Nyní je třeba vysvětlit také to, jak objekt zaniká resp. jak požádat o dealokování = uvolnění přidělené paměti, když se už s objektem pracovat nebude.

V programovacím jazyku Basic to provede přiřazení tvaru např.

Honza = Nothing

Klíčové slovo Nothing je vyhrazeno právě pro opak New - pro sdělení operačnímu systému, že paměť přidělená Honzovi už nebude dále Honzou (!) využívána.

V mnoha jednodušších aplikacích by zde výklad mohl skončit: New alokuje místo pro objekt, Nothing dealokuje, uvolní místo pro objekt a tečka a začátečník nemusí číst dál.

V praxi však může dojít ke složitějším situacím a vykřičník v předchozí větě je namístě: uvažme totiž, že program vypadá takto:

Dim Honza As Zamestnanec, Lakyrnik As Zamestnanec
...
Honza = New Zamestnanec ("Jan Novák", 8500, #1/1/2005#, 6000)
Lakyrnik = Honza

Máme tedy dva objekty třídy Zaměstnanec": Honzu a Lakýrníka. Prvním přiřazovacím příkazem vyžádáme místo pro Honzu, konstruktorem New mu rovnou přiřadíme data. Honza tedy bude obsahovat adresu v paměti. Druhým přiřazovacím příkazem logicky říkáme, že do funkce lakýrníka jsme obsadili Honzu. Lakýrník tedy bude obsahovat stejnou adresu jako Honza. Zpracujeme Honzu a už ho nebudeme potřebovat. To sdělíme operačnímu systému výše uvedeným příkazem

Honza = Nothing

Jenže pořád potřebujeme lakýrníka a proto paměť alokovaná Honzovi se ihned po tomto příkazu nesmí dealokovat, musí být přístupná přes Lakýrníka. Teprve po vydání příkazu

Lakyrnik = Nothing

se paměť skutečně uvolní - samozřejmě za předpokladu, že program mezitím nepřiřadil prozměnu Lakýrníka třeba do zaměstnance Bratr.

Je zřejmé, že problematika přidělování paměti je značně složitější a je třeba ji vysvětlit podrobněji - viz následující odstavec.

3.4. Správa paměti, Garbage Collector

V odstavci Konstruktor i předchozím Instance proměnných objektového typu bylo zmíněno přidělování paměti operačním systémem. Pro objektově orientované programování to je jeden z klíčových momentů pro plné využití možností, které objekty poskytují - a konec konců i pro pochopení činnosti samotného prostředí operačního systému, zde systému firmy Microsoft.

Především je nutno mít alespoň schematický přehled o rozdělení operační paměti. Níže uvedené schéma uvádí příklad toho, co je často nazýváno mapou operační paměti, a to pro současně spuštěné dvě aplikace:
 

Operační paměť:  
Využití:   Hwd, rezid.OS Moduly OS Aplik. 1 Aplik. 2 Heap
Obsazeno:            
Adresa abs:   0 128 000 199 000 341 000 391 000
Adresa rel:   0 128 000 0 0 0

Každá z částí paměti přidělené aplikaci lze dále zjemnit:
 

Paměť aplikace:  
Využití:   Kód aplikace Konstanty Max.zás. Akt. zásobník
Obsazeno:        
Adresa rel:   0 30 000 32 000 36 000

Část paměti přidělené aplikaci a označené jako Kód aplikace zabírají strojové instrukce přeloženého programu. Část označenou jako Konstanty zabírají v programu deklarované konstanty. Část označenou jako Akt. zásobník zabírají jednak mezivýsledky vyhodnocení výrazů, jednak lokální proměnné aktuálního řetězení volání podprogramů. Část označená jako Max. zásobník je zatím volná část zásobníku, která bude využita při maximálním zřetězení volání programů (zásobník se využívá shora dolu, první proměnná je na nejvyšší adrese).

Znovu je třena upozornit, že jde o velké zjednodušení, ve skutečnosti je využívání paměti jednak trochu složitější, jednak závisí od konstrukce překladače (je ovšem fakt, že jazyky překládané v prostředí Microsoftu toto schéma více méně dodržují).

Při objektovém programování však jde především o tu oblast paměti mimo paměť aplikace, označené jako Heap (překládá se často jako Hromada). Mějme tedy zaměstnance označeného identifikátorem Honza. V programu jde o proměnnou třídy Zaměstnanec a ta v paměti zabírá jen místo pro adresu, kde později budou umístěna data Honzy. Proměnná Honza je adresou, ukazatelem a je proto umístěna v zásobníku. Pro 32.bitové aplikace to jsou pouhé 4 byty.

Dim Honza As Zamestnanec

Nechť nyní program vyžádá místo pro Honzu

Honza = New Zamestnanec ("Jan Novák", 8500, #1/1/2005#, 6000)

a vyžádá to (strojovými) instrukcemi zajišťujícími volání systému; ty vygeneruje překladač a jedním z parametrů volání systému je velikost paměti, kterou překladač zná: je to velikost datových polí dané třídy. Nyní se stane toto: Operační systém zjistí, kde v oblasti paměti označené Heap má volné místo o požadované velikosti; adresu počátku této paměti vloží do proměnné Honza a zapamatuje si, že proměnná Honza tuto paměť používá.

Pozn.: Nemá-li systém oblast paměti o požadované velikosti, dojde k chybě předané aplikaci. Faktem je, že v prostředí Microsoftu s virtuální pamětí mapovanou např. do diskového prostoru k tomu dochází zřídka.

A život a čas a program běží dál a systém přiděluje další a další části paměti v oblasti Heap ať už aplikaci s Honzou nebo jiným spuštěným aplikacím. Pak nadejde čas, kdy už Honzu není zapotřebí:

Honza = Nothing

(Nothing - viz předchozí odstavec). Operační systém tedy uvolní paměť v oblasti Heap. Jenomže před Honzou už byla nějaká paměť přidělena, po Honzovi určitě také (byť jiným aplikacím) a v oblasti Heap tedy zůstane po Honzovi díra. Stane-li se to víckrát pro různé objekty různých aplikací, paměť v oblasti Heap pak vypadá jako ementál: obsazená místa střídají díry.

Nevyužité místo v paměti by samo o sobě nevadilo. Problém nastane, když nějaká aplikace bude chtít více místa (třeba o jediný bajtík), než je největší díra - a přitom součet všech děr by na to krásně stačil, kdyby šlo Heap tak nějak setřepat, jako když se defragmentuje disk ...

Právě především k tomu účelu implementují operační systémy mechanismus známý jako Garbage Collector (doslova sběrač odpadků). Jako český ekvivalent tohoto pojmu by mohl vyhovět termín Modul uvolňování paměti. Ten periodicky nebo na vyžádání ověřuje, zda jsou alokovaná místa paměti Heap ještě odkazována nějakými proměnnými a pokud ne, ukončí život instance tohoto objektu a uvolní paměť případně jiné systémové zdroje. Tento systém bývá označován jako uvolňování paměti sledováním odkazů. V případě požadavku na přidělení paměti větší než největší souvislá část paměť reorganizuje a změní adresy všech odkazů na ni.

3.5. Destruktor

Jestliže modul uvolňování paměti zjistí, že nějaký objekt již není odkazován žádnou objektovou proměnnou (tedy že alokované místo v paměti už není zapotřebí), automaticky uvolní tuto paměť a jí přiřazené zdroje. Provede to spuštěním metody objektu, která se obecně nazývá destruktor (angl. destructor). Identifikátor této metody je Finalize. Programátor - autor objektové třídy - může napsat svůj vlastní obsah této metody dané třídy.

Protože však v prostředí systému Microsoft označovaného jako NET Framework se uvolňování paměti děje jednak periodicky, jednak pro potřeby třebas i jiných aplikací, je důsledkem to, že v konkrétní spuštěné aplikaci nelze přesně stanovit okamžik, kdy bude metoda Finalize volána. Určitě to však bude někdy po uvolnění posledního odkazu na daný objekt. V Basicu se odkaz uvolní např. přiřazením hodnoty Nothing (viz výše) nebo ukončením oboru platnosti (objektové) proměnné, tedy ukončením programové jednotky apod.

Metoda Finalize se volá automaticky. Jde o chráněnou metodu a pokud ji autor objektové třídy chce vytvořit sám, musí mít následující syntaxi:

Overrides Protected Sub Finalize ()
   ... Vlastní příkazy ...
   MyBase.Finalize
End Sub

V prostředí NET Framework existuje ještě jeden typ destruktoru, který paměť uvolňuje skutečně ihned na požádání. Je realizován metodou Dispose a je analogický destruktorům jiných objektově orientovaných jazyků. V jazyce Basic.NET musí objekt obsahující vlastní popis této metody implementovat rozhraní (tzv. interface) iDisposable a proto má popis objektové třídy  např. následující tvar:

Class Zamestnanec
   Implements iDisposable
   ...
   Public Sub Dispose () Implements iDisposable.Dispose
      ...
      ... Zde se volají metody Dispose instancí následníků
      ...
   End Sub
   ...
End Class

4. Vlastnosti pod kontrolou

4.1. Datová pole třídy

Uvažujme shora několikrát použitou definici třídy:

Class Zamestnanec
   Public Jmeno As String
   Public Nastup As Date
   Public Plat As Long
   Public Srazky As Long
End Class

Specifikátor Public určuje ta data, které instance objektu ("veřejně") vystavuje navenek. Je tedy možno použít přiřazení typu

Dim Honza As Zamestnanec
...
Honza.Plat = 8500

Tento přiřazovací příkaz provede to, co se od něj žádá - ihned, bez jakýchkoliv podmínek a dalších činností šoupne částku 8500 do vlastnosti Plat zaměstnance Honza. Takové chování však může být často nežádoucí až škodlivé. Znamená to totiž, že kdokoliv může přistoupit k objektu Honza, může také nastavit jeho plat na jakoukoliv hodnotu, třebas i na - hrůza - menší než minimální mzda. Navíc taková možnost odporuje logice principu zapouzdření.

Je ještě další důvod, proč ne vždy vyhovuje takový jednoduchý přístup k datovým polím. Přiřazením do jednoho datového pole se totiž mohou stát hodnoty jiných datových polí neplatnými. Např. datum narození a rodné číslo jsou ve známém vztahu. Změní-li se datum narození, stává se rodné číslo neplatným, chybným, veskrze špatným - alespoň do doby, než přiřazením do rodného čísla se vztah k datu narození uvede do souladu. Nebo přiřazením hodnoty do datového pole mohou vzniknout požadavky na dodatečné akce - např. po přiřazení do pole Plat může vzniknout požadavek na informování exekutora, že Honza už má takový plat, že lze něco strhnout.

Takové jsou nevýhody přímého přiřazení do datového pole instance objektu. Obdobně je tomu však i při získávání hodnoty datového pole. Je přeci zřejmé, že např. příkaz

MsgBox Honza.Plat

by neměl mít možnost vydat kdokoliv a vidět kolik Honza bere. Mohlo by se např. před předáním hodnoty platu ověřit, jestli aplikace požadující tuto hodnotu má k tomu právo.

Proto u objektů existuje možnost kromě přímého přístupu k datovým polím definovat tzv. přístupové funkce (angl. accessors).

4.2. Přístupové funkce

Přístupové funkce jsou dvě: Get je volána při vyhodnocení (1000 + Honza.Plat)  a Set při přiřazení (Honza.Plat = 8500). Ukažme nyní, jak bude vypadat definice třídy Zaměstnanec při využití možnosti zapsat přístupové funkce.

Class Zamestnanec

   Public Jmeno As String                     ' Ponecháno s původním přístupem
   Public Nastup As Date                      ' Ponecháno s původním přístupem
   Public Srazky As Long                      ' Ponecháno s původním přístupem

   Private  lokPlat As Long                   ' Místo pro lokální, ne zevně přístupnou hodnotu platu

   Public Property Plat () As Long   ' Deklarace vlastnosti Plat

      Get                            ' Přístupová funkce vracející hodnotu vlastnosti
        ... Nějaký např. ověřovací kód ...
        Plat = lokPlat
      End Get

      Set (ByVal X as Long)          ' Přístupová funkce nastavující hodnotu vlastnosti
        If X > MinMzda Then
           lokPlat = X
        Else
           MsgBox "Málo peněz!"
           lokPlat = 0
        End If
      End Set

   End Property

End Class

Jestliže tedy při této definici třídy je deklarován Honza

Dim Honza As Zamestnanec

pak přiřazovací příkaz

Honza.Plat = 8500

nepřiřadí Honzovi plat rovnou, ale volá přístupovou funkci Set. Ta nejprve zkontroluje, zda je plat OK. Je-li, přiřadí ho do lokální, zevně přímo nedostupné proměnné lokPlat. Není-li plat OK, vydá se nejprve chybové hlášení a pak se lokální pole pro plat vynuluje.

Analogicky příkaz

Eura = Honza.Plat / 30.23

nepřiřadí přímo do proměnné Eura plat v Eurech, ale nejprve volá přístupovou funkci Get. Ta provede např. ověření práva přístupu a pak předá jako výsledek svého volání hodnotu lokální proměnné lokPlat.

4.3. Datová pole a vlastnosti

Až do této chvíle bylo s pojmy data, datové pole, vlastnost objektu manipulováno velmi volně. S ohledem právě popsané přístupové funkce je však nutno precizovat terminologii.

Jestliže objekt zpřístupňuje své dílčí hodnoty přímo, tedy pomocí Public, jde o datové pole. Jestliže objekt zpřístupňuje své dílčí hodnoty pomocí přístupových funkcí, tedy pomocí Get a Set, jde o vlastnosti. Zda nějakou hodnotu objektu učiní datovým polem nebo vlastností, záleží jen a jen na autorovi objektové třídy. Zpracování datového pole je obecně rychlejší, zpracování vlastnosti umožňuje dodatečné činnosti.

V jazyku Basic je rozlišení datového pole a vlastnosti dáno už přímo způsobem uvedení v definici třídy. Plat v takto definované třídě:

Class Zamestnanec
   ...
   Public Plat As Long
   ...
End Class

je datové pole.

Plat v takto definované třídě:

Class Zamestnanec
   ...
   Public Property Plat As Long
      ...
   End Property
   ...
End Class

je vlastnost.

V objektovém programování se často takové precizní rozlišování neprovádí a mluví se většinou jen o vlastnostech bez ohledu na způsob přístupu. Programátor A (i když ve svém programu vůbec žádnou třídu nedefinuje) totiž používá spousty objektů připravených jiným programátorem B a definovaných v objektových knihovnách. A pak je programátorovi A více méně jedno, zda je plat zaměstnance datové pole nebo vlastnost, hlavně když mu programátor B přesně řekne, v jakém významu má plat použít a jaké následky jeho použití má. V obou případech použije stejné syntaxe, např. Honza.Plat.

Aby se vlastnost (např. Plat) chovala více méně úplně stejně jako datové pole, lze zajistit analogicky následujícímu příkladu:

Class Zamestnanec
   ...
   Private lokPlat As Long
   Public Property Plat As Long
      Get
        Plat = lokPlat
      End Get
      Set (ByVal X as Long)
        lokPlat = X
      End Set
   End Property
   ...
End Class

Zavede se tedy lokální proměnná daného typu (zde označená lokPlat). Přiřazení hodnoty do ní zajistí přístupová funkce Set, získání její hodnoty zajistí přístupová funkce Get. Za zmínku stojí, že - jak je z příkladu vidět - vlastnost se nemusí opírat o žádnou proměnnou, ať už lokální nebo jinou.

5. Kolekce

Vynikající objektovou třídou, která je ve Windowsech používána minimálně jako bázová třída skoro všude, je Kolekce (angl. Collection). Obdobný mechanismus resp. objekt mají v různých podobách a názvech realizovány většiny jazyků ve většině systémů. Kolekcí je téměř vše, kolekce jsou téměř všude. Tento článek obsahuje kolekci odstavců, každý odstavec kolekci slov. Každý sešit Excelu obsahuje kolekci listů, každý list kolekci řádků a kolekci sloupců. Každá logická jednotka obsahuje kolekci adresářů, každý adresář kolekci podadresářů a kolekci souborů. Prostě všude samé kolekce, pořád samé Vánoce.

Základem kolekce je kontejner, který přijímá a odstraňuje adresy libovolných elementů, počínaje jednoduchými proměnnými až po - nejpřirozenější - objekty. Tento kontejner jako takový je privátním polem kolekce. Kolekce však nabízí metody pro zařazení prvku do kolekce, odebrání prvku z kolekce a zjištění, kolik prvků v kolekci právě teď je. Co je však nejdůležitější, kolekce je snad jediný objekt, který má dopad na syntaxi jazyka. Jazyk Basic totiž obsahuje příkaz For Each, který umožňuje v cyklu procházet všechny prvky kolekce, aniž by bylo nutno explicitně definovat, o který prvek kolekce se jedná.

Další významnou vlastností kolekce je možnost zařazovat prvky prostě, jeden za druhým, nebo s tzv. klíčem. Klíčem je přitom jedinečná hodnota typu String, která prvek kolekce jednoznačně identifikuje (v kolekci tedy nemohou být dva prvky se stejným klíčem). Klíčem může tedy být (textové) osobní číslo zaměstnance, SPZ automobilu apod. Při přístupu k prvku kolekce pak můžeme použít buď jeho pořadové číslo, nebo jeho klíč.

Ukažme kolekci a práci s ní na tomto modelovém příkladu: Shora byly hojně použity pro demonstraci objektové třídy Zamestnanec a Chef. Základem kolekce je kontejner schopný pojímat Zaměstnance a Chefy. Uvažujme tedy kolekci Firma, která bude pojímat jednotlivé zaměstnance naší firmy a jejich šéfy (a případně cokoliv dalšího).

5.1. Deklarace kolekce

Kolekce jako třída je deklarována v implicitních knihovnách Basicu. Proměnná typu kolekce se deklaruje jako ostatní proměnné příkazem Dim. V modelovém příkladu tedy

Dim Firma As Collection

Protože jde o třídu (Collection) a její instanci (Firma), je nutno před prvním použitím vyžádat od systému paměťový prostor příkazem

Firma = New Collection

Tím začne Firma existovat. Při volání jejího konstruktoru se nastaví počet členů kolekce na nulu a může se začít používat voláním jejich metod.

5.2. Zařazení do kolekce

Další prvek do kolekce zařadí metoda Add kolekce. Ta má první parametr povinný (adresu prvku, který je do kolekce přidáván), a druhý nepovinný (klíč, který prvek kolekce jednoznačně identifikuje). Platí však toto: jestliže první prvek je zařazen do kolekce s klíčem, pak i všechny ostatní prvky musí být zařazeny s klíčem.  Jestliže první prvek je zařazen do kolekce bez klíče, pak i všechny ostatní prvky musí být zařazeny bez klíče.

Mějme tedy kolekci, např. shora uvedenou modelovou kolekci Firma. Mějme dále několik zaměstnanců a jednoho šéfa zavedených takto:

Dim Honza As Zamestnanec
Dim Jirka As Zamestnanec
Dim Pavla As Chef
...
Honza = New Zamestnanec ("Jan Novák", 8500, #1/1/2005#, 6000)
Jirka = New Zamestnanec ("Jiří Zapletal", 9200, #5/5/2004#, 0)
Pavla = New Chef ("Pavla Nováčková", 8900, #9/9/2005#, 0, 1500)

Zařadit tyto pracovníky do firmy lze dvěma způsoby:

Firma.Add Honza
Firma.Add Jirka
Firma.Add Pavla

je zařadí bez klíče. Na druhé straně

Firma.Add Honza, "NOV12"
Firma.Add Jirka, "ZAP10"
Firma.Add Pavla, "NOV62"

je zařadí s klíčem.

Zda použít zařazování s klíčem nebo bez něj záleží pouze a jen na programátorovi aplikace. Jestliže použije zařazování s klíčem, může k prvkům kolekce přistupovat (třeba i střídavě) pomocí pořadového čísla i pomocí klíče. Jestliže použije zařazování bez klíče, může k prvkům kolekce přistupovat jen pomocí pořadového čísla.

Zařazování s klíčem je někdy problematické, a to v tom případě, že je obtížné sestrojit jedinečný textový identifikační řetězec.

5.3. Počet prvků v kolekci

Počet prvků právě teď zařazených do kolekce je udržován ve vlastnosti Count kolekce. Vlastnost je typu 4-bytového celého čísla se znaménkem (tj. Long pro Basic 6, Integer pro Basic NET). Vlastnost Count je read-only, tj. nelze přiřazovacím příkazem přepsat. To je zcela pochopitelné, aktuální počet si obhospodařuje kolekce sama podle toho, jak se voláním metod Add a Remove přidávají nebo odebírají prvky kolekce.

Bezprostředně po inicializaci je tedy např. Firma.Count = 0 (nula). Příklad použití, je-li Pocet deklarována jako celočíselná proměnná:

Pocet = Firma.Count
MsgBox "Ve firmě je " & Pocet & " lidí."

zobrazí hlášení o počtu lidí ve firmě - přesněji o počtu prvků, které právě teď jsou v kolekci Firma.

5.4. Přístup k prvku kolekce

Specifikovaný prvek zařazený do kolekce je výsledkem volání metody Item (výsledkem volání je předání adresy, nikoliv vynětí prvku z kolekce). Parametrem metody je buď celočíselný výraz (pro přístup k prvku podle pořadí) nebo výraz typu String (pro přístup k prvku podle klíče). Použije-li se volání s celočíselným parametrem, nesmí být jeho hodnota menší než 1 a větší než počet prvků kolekce (tedy hodnota vlastnosti Count). Použije-li se volání s parametrem String, musí v kolekci existovat prvek s klíčem rovným hodnotě tohoto parametru (tedy zařazování prvků do kolekce muselo být s klíčem). Nejsou-li tyto podmínky splněny, dojde k chybě.

Příklad:

Dim Pracovnik As Object
Pracovnik = Firma.Item (2)

přiřadí do proměnné Pracovník adresu 2. zařazeného pracovníka - platí-li příkazy dle 5.2, tedy adresu Jirky.

Při použití přístupu k prvku kolekce pomocí pořadového čísla je ale třeba dát pozor na toto: předchozí příklad vrátí Jirku, ale jen za předpokladu, že předtím nebyl z kolekce odebrán Honza! Pokud by se tak stalo, není od toho okamžiku Jirka druhý, ale první. V takových případech vynikne výhoda zařazování do kolekce s klíčem, protože v tomto příkladu

Dim Pracovnik As Object
Pracovnik = Firma.Item ("ZAP10")

je vždy odevzdán Jirka Zapletal bez ohledu na jiné pohyby ve firmě.

Metoda Item je implicitní metodou objektu třídy Collection. To znamená, že neuvede-li se po identifikátoru kolekce tečka a jméno vlastnosti nebo metody, jako by se uvedlo .Item - proto následující dva příkazy provedou totéž:

Pracovnik = Firma ("ZAP10")
Pracovnik = Firma.Item ("ZAP10")

Metoda Item může být volána jednou z celočíselným, jednou z textovým parametrem - s textovým ovšem jen tehdy, když zařazování prvků do kolekce bylo s klíčem.

5.5. Odebrání z kolekce

Specifikovaný prvek zařazený do kolekce z ní vyjme, vypustí metoda Remove (vypustí prvek, tj. adresu, nezruší instanci objektu s touto adresou!). Parametrem metody je buď celočíselný výraz (pro vypuštění prvku podle pořadí) nebo výraz typu String (pro vypuštění prvku podle klíče). Použije-li se volání s celočíselným parametrem, nesmí být jeho hodnota menší než 1 a větší než počet prvků kolekce (tedy hodnota vlastnosti Count). Použije-li se volání s parametrem String, musí v kolekci existovat prvek s klíčem rovným hodnotě tohoto parametru (tedy zařazování prvků do kolekce muselo být s klíčem). Nejsou-li tyto podmínky splněny, dojde k chybě.

Příklad:

Firma.Remove ("ZAP10")

vypustí z kolekce Jirku Zapletala - ovšem jen z kolekce Firma, instance Jirka pořád bude dál existovat.

Metoda Remove může být volána jednou z celočíselným, jednou z textovým parametrem - s textovým ovšem jen tehdy, když zařazování prvků do kolekce bylo s klíčem.

5.6. Procházení všemi prvky kolekce

K provádění skupiny příkazů pro každý prvek kolekce slouží v Basicu příkaz For Each. Má následující obecný tvar:

For Each Prvek In Kolekce
   ...
   příkazy prováděné pro každý Prvek z Kolekce
   ...
Next

Pro zobrazení jmen všech pracovníků zařazených do firmy lze tedy použít následující:

Dim Pracovnik As Object
For Each Pracovnik In Firma
   MsgBox Pracovnik.Jmeno
Next

Je třeba mít na paměti, že uvnitř cyklu For Each je řídící proměnná cyklu (zde Pracovnik) naplňována řídícím mechanismem cyklu a nelze uvnitř těla cyklu měnit - např. přiřazení Pracovnik = Jirka by vedlo k chybě. Na druhé straně je zde řídící proměnná objektem, tj. adresou, odkazem, a proto ji lze použít k odkazování na metody a vlastnosti prvků kolekce. A proto následující příklad je správný a pro pracovníky velmi příjemný:

Dim Pracovnik As Object
For Each Pracovnik In Firma
   Pracovnik.Plat = Pracovnik.Plat * 1.1
Next

každému pracovníkovi zvýší plat o 10%.

Ještě jeden důsledek má řízení procházení kolekce řídícím mechanismem cyklu: ten používá k přístupu na další prvek kolekce interní metodu MoveNext a nikoliv indexování. Proto se výslovně uvádí, že pořadí dodávání prvků kolekce není v Basicu určeno; je pouze zaručeno, že se takto projdou všechny prvky kolekce.

Zhruba stejnou činnost jako shora uvedený cyklus For Each by provedly následující příkazy:

Dim Prvek As Object
Dim i As Integer
For i = 1 To Kolekce.Count
   Prvek = Kolekce.Item (i)
   ...
   příkazy prováděné pro každý Prvek (i) z Kolekce
   ...
Next

 

6. Zastínění (shadowing), překrývání (overriding) a přetěžování (overloading)

Metody reprezentují akce, které je objekt schopen vykonat. Jsou realizovány programovými jednotkami, ať už podprogramem (Sub) nebo funkcí (Function). Příkladem může být metoda Nastav v odst. 2.4. Zdá se to čisté, jasné, prostě za definice dat objektu (vlastností) uvedu popis programových jednotek (metod) a je to.

Jenomže to je právě omyl. Zdaleka to není tak průzračné a právě zde spočívá podstata polymorfismu, který je jedním ze základních aspektů objektově orientovaného programování. Pro úvod do této problematiky uvažme už shora použité třídy Zaměstnance a Šéfa:

Class Zamestnanec
   Public Jmeno As String
   Public Plat As Long
   Public Nastup As Date
   Public Srazky As Long

   Sub Nastav (J As String, P as Long, N as Date)
   ...
   End Sub

End Class
...
Dim Honza As Zamestnanec

 

Class Chef
   Inherits Zamestnanec
   Public Priplatek As Long
End Class
...
Dim Pavla As Chef

A podstata drobného problému, který je třeba řešit, spočívá v otázce: Pokud má Zaměstnanec zmíněnou metodu Nastav, co když se i Šéfovi vytvoří metoda Nastav (explicitně, ne děděním)? Bude to chyba? A ne-li, jak se to bude chovat?

Je ještě jedna situace, která může mít více řešení. Nějaký vývojář vytvoří rodičovskou třídu (base class, základní třídu). V ní definuje např. nějakou metodu. My ji použijeme a vytvoříme k ní následnickou třídu (derived class, odvozenou třídu). Z nějakého důvodu nám ale nevyhovuje provedení metody rodičovské třídy. Buď bychom potřebovali doplnit metodu o novou implementaci, nebo ji změnit, nebo ji úplně nahradit něčím jiným. Lze to zajistit - a jestliže ano, jak? Ovšem jestliže někdo další bude chtít vzít naši třídu za základní pro tvorbu své odvozené, zdědí naši metodu nebo původní? A nejde zajistit, aby někdo zdědil původní a někdo naši - a jestliže ano, jak?

Existují tři techniky, jak se vypořádat s vytvářením členů se stejnými jmény nebo jmény, které už existují na jiných úrovních. Jsou postupně popsány v následujících odstavcích.

6.1. Přetěžování (overloading)

Zcela bez přemýšlení přijímají lidé to, že [12/1/2005] + 4 = 4 + [12/1/2005] = [16/1/2005]. Obecně, označíme-li N číselnou a D datumovou hodnotu, pak výsledek N + D a D + N je stejný a je to datum o N dní po datumu D. Ovšem je také 1 + 1 = 2. Všichni prostě chápou operační znaménko + jako znaménko operace sčítání, zvětšování jedné hodnoty o velikost jiné. A přitom v uvedených případech jde o tři různé operace! Operace (přesněji algoritmus operace) je dán nejenom operačním znaménkem, ale počtem a typem operandů.

Některé programovací jazyky (dnešní C, dřívější Algol-68 a jiné) mají možnost definovat vlastní algoritmus operace pro dané operační znaménko a daný typ a počet parametrů. Může tedy být třeba sedm různých operací Plus (+), ovšem lišících se typem resp. počtem operandů.

I počet operandů hraje roli, protože máme např. unární mínus (-A) a binární mínus (A - B). Velmi zajímavá by byla práce s ternárními a ještě více operandovými operacemi, ale ty bohužel nejde v běžných programovacích jazycích použít díky lineárnímu zápisu textu programu (dovede to např. Lisp). Proto jsou tyto operace realizovány nikoliv pomocí operátorů, ale pomocí funkcí.

A jsme u jádra problému: jestliže lze použít stejný operátor pro různé typy operandů a jestliže operaci lze substituovat funkcí, nelze také použít stejné jméno funkce pro různé typy parametrů pro realizaci různých algoritmů?

Obecně opět záleží na konkrétním programovacím jazyku. V problematice objektového programování však platí, že metody jsou realizovány podprogramy nebo funkcemi a u nich to lze. Případ, že je v dané třídě definováno více metod se stejným názvem, ale různým počtem a typem parametrů, se nazývá přetěžování (angl. overloading). Přitom nezáleží na jménu parametrů, ale skutečně jen na jejich typu. Uspořádaná množina typů parametrů se nazývá signatura (angl. signature) metody. V dané třídě nesmí existovat dvě metody se stejným jménem a stejnou signaturou, ovšem může existovat několik metod se stejným jménem a různými signaturami - pak jde právě o přetěžování.

Ukažme přetěžování na trochu umělém, přesto však názorném příkladu. Nechť je v naší firmě zvykem, že když se přidává plat, je to vždy o 5%. Je pak zcela přirozené třídě Zamestnanec zavést metodu Pridej takto:

Class Zamestnanec
   Public Jmeno As String
   Public Plat As Long
   Public Nastup As Date
   Public Sub Pridej ()
      Plat = Plat + 5 * Plat / 100
   End Function
End Class

Potom tedy Honzovi způsobí volání Honza.Pridej velikánskou radost, dostane přidání 5%. Dobrá; zvyk přidávat 5% je fajn, každý ví, co může čekat. Ale přesto - Jirka je kvalitní pracovník a zaslouží si přidat 6.5% platu. Jestliže všem přidávám metodou Pridej, pak Jirkovi to zatím nelze. Ovšem zde nastupuje přetěžování. Doplňme třídu o metodu Pridej - ale s jinou signaturou:

Class Zamestnanec
   Public Jmeno As String
   Public Plat As Long
   Public Nastup As Date
   Public Sub Pridej ()
      Plat = Plat + 5 * Plat / 100
   End Function
   Public Sub Pridej (Procenta As Double)
      Plat = Plat + Procenta * Plat / 100
   End Function
End Class

Honzovi pořád způsobí Honza.Pridej zvýšení o 5% stávajícího platu, ale Jirkovi se voláním Jirka.Pridej (6.5) přidá 6.5% stávajícího platu.

Pokračujme však dál: třída Chef je následnická třída, jejím předchůdcem je Zamestnanec a proto Chef zdědí oba způsoby přidání mzdy. Je-li Pavla šéfem, pak Pavla.Pridej jí zvýší plat o 5%, Pavla.Pridej (8) jí zvýší plat o 8% - ale jak se mu zvýší příplatek za šéfování? Opět lze použít přetěžování, tentokrát v následnické třídě:

Class Chef
   Inherits Zamestnanec
   Public Priplatek As Long
   Public Overloads Sub Pridej (ProcPlatu As Double, ProcPriplatku As Double)
      MyBase.Pridej (ProcPlatu)
      Priplatek = Priplatek + ProcPriplatku * Priplatek / 100
   End Function
End Class

Pak volání Pavla.Pridej (8, 6) zvýší Pavle plat o 8 procent a příplatek o 6 procent.

Při použití přetěžování v následnické třídě stojí za pozornost dvě věci. Jednak je to použití metody rodičovské třídy prostřednictvím objektu MyBase. Zde skutečně není důvod v metodě Chefa zvětšovat plat přímo, když už to umí rodičovská třída. Co kdyby se totiž časem něco v systému zvětšování platů zaměstnanců změnilo? Pak by se musela adekvátně změnit část přetížené metody Chefa a to je proti logice objektového přístupu vůbec.

Dále stojí za povšimnutí klíčové slovo Overloads v záhlaví definice metody. Jeho použití je vyžadováno vždy když následnická třída přetěžuje metodu svojí rodičovské třídy. Pokud by se totiž neuvedlo, došlo by ne k přetížení, ale k zastínění (shadowing) se zcela jiným efektem - viz další odstavce.

6.2. Překrývání (overriding)

Každému zaměstnanci je třeba platit mzdu. Pro takového Honzu to je jednoduché, je to jeho plat. Třídu Zaměstnanec tedy doplníme o metodu na zjištění mzdy:

Class Zamestnanec
   Public Jmeno As String
   Public Plat As Long
   Public Nastup As Date
   Public Srazky As Long
   Public Function Mzda () As Long
      Mzda = Plat - Srazky
   End Function
End Class

Mzda pro Honzu je jasná. Výraz Honza.Mzda předá skutečně peníze pro Honzu. Ovšem metodu Mzda zdědí i třída Chef a pak bude problém s Pavlou, která nebude ráda, protože Pavla.Mzda nebude zahrnovat příplatek za vedení.

Mzda pro Chefa se tedy musí počítat jinak. Kdyby se ovšem "jen tak" zapsala třídě následníka (zde Chef) jeho metoda Mzda, nastal by konflikt dvojího identifikátoru v jedné jednotce (zde třídě): jednak by byla metoda Mzda zděděná a jednak metoda Mzda vlastní. Proto je třeba dát nějak na vědomí, že metoda pro následníka musí překrýt (angl. override) výpočet mzdy svého předchůdce. A na vědomí se to dá pomocí dvou klíčových slov: ve třídě předchůdce se sdělí, že metoda je overridable (překrývatelná) metodou následníka a ve třídě následníka se řekne, že metoda overrides (překrývá) metodu svého předchůdce. Prakticky pro třídy Zamestnanec a Chef to vypadá takto:

Class Zamestnanec
   Public Jmeno As String
   Public Plat As Long
   Public Nastup As Date
   Public Overridable Function Mzda () As Long     ' Metoda překrývatelná metodami následníků
      Mzda = Plat - Srazky
   End Function
End Class

 

Class Chef
   Inherits Zamestnanec
   Public Priplatek As Long
   Public Overrides Function Mzda () As Long       ' Metoda překrývající metodu předchůdce
      Mzda = Plat - Srazky + Priplatek
   End Function
End Class

Nyní tedy jak Honza.Mzda, tak Pavla.Mzda odevzdají správnou hodnotu mzdy.

Uvažujme však dále: uvedený příklad je snad nejjednodušší jaký může být. Co když však výpočet mzdy pro zaměstnance je složitější, co když to není jen pouhá kopie platu snížená o srážky? Potom by mzda šéfa nebyla pouhým součtem Platu (zmenšeného o srážky) a Příplatku, ale Mzdy [počítané podle pravidel pro zaměstnance] a Příplatku. Je tedy zapotřebí se v metodě následnické třídy dostat ke (stejnojmenné) metodě třídy předchůdce. To zajišťuje objekt se standardním identifikátorem MyBase (viz také 3.2). Metoda mzda pro Chefa by pak vypadala takto:

Public Overrides Function Mzda () As Long          ' Metoda překrývající metodu předchůdce
      Mzda = MyBase.Mzda + Priplatek
End Function

Takto pojatá metoda následníka nenahrazuje zcela metodu předchůdce, jen ji trochu doplňuje, rozšiřuje. Charakteristické pro překrývané členy je to, že jak překrývaný, tak překrývající jsou stejného typu; u metod to znamená, že mají stejné jméno, stejné parametry a jde-li o funkce, odevzdávají výsledek stejného typu. V mnoha případech však metoda nebo vlastnost předchůdce pracuje v interakci s dalšími svými vlastnostmi. Jestli překrývající metoda následníka nevyvolá překrývanou metodu předchůdce, interakce s dalšími vlastnostmi (tentokrát v instanci následníka!) se neprovede a může dojít k nepředvídatelným situacím. Proto na vývojáři třídy předchůdce leží zodpovědnost za to, zda danou metodu prohlásí za překrývatelnou nebo ne. Jestliže totiž ano, pak musí počítat s tím, že jiný vývojář ve své odvozené třídě metodu překryje zcela a to by nemělo mít za následek havárii chování následnického objektu díky neprovedení něčeho v předchůdcovském objektu.

Zmíněný polymorfismus vynikne zvláště na následujícím příkladu, viz také kapitolu o kolekcích:

Dim Honza As Zamestnanec       ' Konkrétní zaměstnanec
Dim Jirka As Zamestnanec       ' Konkrétní zaměstnanec
Dim Pavla As Chef              ' Konkrétní šéf
Dim Pracovnik As Object        ' Obecný objekt, kterým bude tu zaměstnanec, tu šéf
Dim Firma As Collection        ' Kolekce pracovníků - zaměstnanců i šéfů dohromady (!)
...
Honza = New Zamestnanec ("Jan Novák", 8500, #1/1/2005#, 6000)
Jirka = New Zamestnanec ("Jiří Zapletal", 9200, #5/5/2004#, 0)
Pavla = New Chef ("Pavla Nováčková", 10900, #9/9/2005#, 0, 1500)

Firma = New Collection

Firma.Add Honza, "NOV12"        ' Přidá (adresu, odkaz na) Zaměstnance
Firma.Add Jirka, "ZAP10"        ' Přidá (adresu, odkaz na) Zaměstnance
Firma.Add Pavla, "NOV62"        ' Přidá (adresu, odkaz na) Šéfa

For Each Pracovnik In Firma     ' Cyklus přes všechny objekty - zde zaměstnance a šéfy - zařazené do kolekce
   MsgBox Pracovnik.Mzda        ' Použití metody Mzda objektu, kterým je buď zaměstnanec nebo šéf; oba ji mají, ale každý díky polymorfismu jinou!!!
Next

Na uvedeném příkladu stojí za důkladné rozmyšlení to, že Pracovnik je deklarovaný jako obecný typ Object, tedy nikoliv Zamestnanec nebo Chef. Přesto - díky pozdní vazbě - se správně zjistí příslušná metoda a ta se použije, byť u Pavly jiná než u Honzy a Jirky. Stejně tak by mechanismus pracoval tehdy, když by Pracovnik byl deklarovaný jako Zamestnanec (!). Právě proměnná Pracovnik demonstruje polymorfismus - mnohotvárnost: ona skutečně nabývá mnoha tváří, jednou tvář pracovníka, jindy tvář šéfa - a ve všech případech lze zajistit pomocí překrývání adekvátní chování.

6.3. Zastínění (shadowing)

Předchozí odstavce ukázaly, jak lze ve třídě následníka rozšířit její chování oproti třídě předchůdce přidáním metod. Ukázaly také, jak lze (se svolením autora třídy předchůdce - klíčové slovo overridable) nahradit nebo změnit metody mechanismem překrývání (overriding).

Ovšem co se stane, máme-li  pocit, že potřebujeme změnit nebo nahradit existující metodu předchůdce a autor této předchůdcovské třídy nám k tomu povolení nedá? Když nepoužije v popisu své metody klíčové slovo overridable?

V tom případě nelze překrýt metodu třídy předchůdce metodou třídy následníka. Je to pravděpodobně záměr autora rodičovské třídy. On samozřejmě nevyvíjí svou třídu s předpokladem, že každá následnická třída nahradí jeho metody, proto musíme při tvorbě naší následnické třídy pozorně rozmyslet eventuelní následky ignorování projekčních předpokladů rodičovské třídy. Nicméně někdy se skutečně můžeme rozhodnout, že metodu musíme nahradit a basta - bez ohledu na přání a konstrukci rodičovské třídy.

Existuje ještě jiná situace: nechť v naší aplikaci verze 1.1 rozšiřuje naše následnická třída rodičovskou třídu o nějakou metodu. Jenže potom ve verzi 1.2 autor rodičovské třídy do ní přidá novou metodu, která je v konfliktu s existující naší. To máme přejmenovat naši metodu v následnické třídě a přepsat všechny programy které ji používají? No to ne! Raději se rozhodneme ignorovat novou metodu rodičovské třídy a budeme pokračovat v používání naší existující metody následnické třídy.

V obou uvedených případech používáme zastínění (angl. shadowing). Tento mechanismus umožní dosáhnout našeho cíle - zcela nahradit metodu rodičovské třídy. Zastínění je často v počátcích chápáno jako překrývání - jde ovšem o principielně odlišný mechanismus.

Pro příklad uvažme opět rodičovskou třídu Zamestnanec a následnickou třídu Chef: Budeme požadovat, aby obě třídy měly metodu ZobrazSe, která zobrazí data tu zaměstnance, tu šéfa. Přirozeně tedy zapíšeme:

Class Zamestnanec
   Public Jmeno As String
   Public Plat As Long
   Public Nastup As Date
   Public Srazky As Long
   Public Sub ZobrazSe ()
      MsgBox (Jmeno & Plat & Nastup & Srazky)
   End Function
End Class

Class Chef
   Inherits Zamestnanec
   Public Priplatek As Long
   Public SubZobrazSe ()
      MsgBox (Jmeno & Plat & Nastup & Srazky & Priplatek)
   End Function
End Class

Na první pohled to vypadá korektně. Jsou-li deklarováni

Dim Honza As Zamestnanec       ' Konkrétní zaměstnanec
Dim Pavla As Chef       ' Konkrétní šéf

pak skutečně

Honza.ZobrazSe

zobrazí 4 položky dat Honzy, kdežto

Pavla.ZobrazSe

zobrazí 5 položek dat Pavly. Pokračujme však. Mějme stejně jako v předchozím odstavci Firmu jako kolekci zaměstnanců spolu se šéfy:

Dim Firma As New Collection        ' Kolekce pracovníků - zaměstnanců i šéfů dohromady (!)
...
Firma.Add Honza ...        ' Přidá (adresu, odkaz na) Zaměstnance
Firma.Add Pavla ...        ' Přidá (adresu, odkaz na) Šéfa

Nechme nyní zobrazit data každého člověka ve firmě:

 

Dim Pracovnik As Zamestnanec        ' Obecný pracovník firmy
...
For Each Pracovnik In Firma     ' Cyklus přes všechny objekty - zde zaměstnance a šéfy - zařazené do kolekce
   Pracovnik.ZobrazSe        ' Použití metody ZobrazSe
Next

Oproti očekávání se však pokaždé zobrazí jen 4 položky: Jméno, Plat a Nástup. U šéfa se nezobrazí Příplatek. Důvodem je to, že metoda ZobrazSe třídy Chef zastínila stejnojmennou metodu třídy Zamestnanec a tedy jakoby ze Zamestance "není vidět" - ovšem Zamestnanec metodu ZobrazSe má. Proto tedy proměnná Pracovnik třídy Zamestnanec - i když jde o objektovou proměnnou (tedy odkaz, adresu) a v jeden okamžik ukazuje na objekt třídy Chef - vždy použije metodu předchůdce, nikoliv zastiňující metodu následníka.

Toto je podstatný rozdíl mezi překrýváním a zastíněním. I když deklarace Zaměstnance a Šéfa s metodou ZobrazSe vypadají na první pohled korektně, ve skutečnosti je metoda deklarována jako zastiňující, protože toto chování bylo přijato jako implicitní. Jde tedy o to samé, jako kdyby byla deklarována explicitně takto:

Class Chef
   Inherits Zamestnanec
   Public Priplatek As Long
   Public Shadows SubZobrazSe ()
      MsgBox (Jmeno & Plat & Nastup & Srazky & Priplatek)
   End Function
End Class

Následující tabulka ukazuje, která metoda je volána v závislosti na třídě objektu, na který ukazuje proměnná nějaké objektové třídy:
 

Proměnná deklarována jako třída Odkazuje na objekt třídy Volána je metoda
Předchůdce Předchůdce Předchůdce
Předchůdce Následníka Předchůdce
Následníka Následníka Následníka

Právě druhý řádek odspodu ukazuje, čím se zastínění liší od překrývání - u překrývání je volána metoda následníka.

A ještě jeden rozdíl je mezi překrýváním a zastíněním: zatímco překrýt může pouze metoda metodu se stejnou signaturou, může jakýkoliv zastiňující prvek zastínit jakýkoliv jiný zastiňovaný prvek. Může např. metoda zastínit vlastnost a naopak.

 

Odkazy

V krátkém výčtu zde je podán popis základních pojmů používaných v profesionálním prostředí Microsoft Visual Studia. Jde o výtah z publikace Vick, Paul: The Microsoft Visual Basic Language Specification, Version 8 - 10, Microsoft Corporation 2005 - 2011, nejde tedy o dílo autora tohoto článku.

 

 

Rev. 06 / 2019