+---+ +---+ +---+ | | | | | | +---+ +---+ +---+ | Nazdárek kašpárek všichni ti, co zas až tak úplně vůbec nevíte co to je OOP a nebo jak vlastně OOP zvládnout v tom vašem malém milém Turbo nebo Borland Pascalu. Já sám se s OOP potýkám teprve nedlouho, ale vím, jak těžké bylo začít se potýkat, protože jsem neměl zbytečné peníze na drahé knihy, a tak mým jediným zdrojem informací byly pascalské helpy a jeden známý, který si říká Stuff. Nebo si tak alespoň donedávna říkal. Takže jak jste jistě všichni pochopili, tento článek je pro ty z vás, co znají slova jako dědičnost pouze z biologie a teď se snaží napsat svůj první objektový program. Protože samozřejmě nejlepší metoda jak se učit, je někde něco opsat. Takže vám zde dodám množství příkladů. K tomu použiji unitu Objects, která je myslím součástí standartní výbavy Pascalu. Takže jdeme na to. V první řadě musíte zapomenout, že existuje něco, jako globální strukturované programování. V ideálním případě by tedy měl program vypadat takto: Var Aplikace:PAplikace; Begin Aplikace:=New( PClovece,Init ); { Inicializace programu } Aplikace^.Work; { Prace programu } Dispose( Aplikace,Done ); { Zruseni programu } End; A ted přistoupíme k tomu, jak vlastně vypadá takový objekt. OBJECT je datová struktura značně podobná struktuře RECORD. Podobně se dokonce deklarují jednotlivé typy objektů, takzvané třídy. Deklarace třídy TAplikace: Type TAplikace=Object Procedure Work; Constructor Init; Destructor Done; End; Nyní jsme si nadeklarovali třídu TAplikace. Každá třída, kterou si nadeklarujete, má svoje instance. Příklady instancí od třídy TAplikace jsou: Var StatickyAplikace:TAplikace; DynamickyAplikace:^TAplikace Protože ale v objektovém programování nebudete vůbec používat statických objektů, lze dynamiku ( a určitě to tak udělejte, protože to pro pozdější využití bude potřeba ) napsat spíše takhle: Type PAplikace=^TAplikace; TAplikace=Object Procedure Work; Constructor Init; Destructor Done; End; Var Aplikace:PAplikace; Lze tedy říci, že třída objektů je ve strukturovaném programování vlastně typ a instance je vlastně proměnná. A nyní jaké položky může třída obsahovat. Může obsahovat všechny datové typy, které používá strukturovaný pascal a navíc metody. Metoda je vlastně funkce nebo procedůra, kterou vlastní nějaký objekt a je schopná operovat pouze s proměnnými vlastněnými tímto objektem. Navíc každý objekt musí obsahovat speciální metody konstruktor a destruktor, které správně nastaví všechny proměnné objektu a provedou nutné kroky inicializace. Například TScreen bude objekt obsluhující obrazovku v grafickém režimu 320*200*256: Type PScreen=^TScreen; TScreen=Object Procedure PutPixel( X,Y:Word;Color:Byte ); Function GetPixel( X,Y:Word ):Byte; Consrtuctor Init; Destructor Done; End; Procedure TScreen.PutPixel( X,Y:Word;Color:Byte ); Begin Mem[A000:X+Y*320]:=Byte; End; Procedure TScreen.GetPixel( X,Y:Word ):Byte; Begin GetPixel:=Mem[A000:X+Y*320]; End; Constructor Init; Begin Asm mov ax,13h int 10h End; End; Destructor Done; Begin Asm mov ax,3h int 10h End; End; Zde sice objekt nevlastnil žádné proměnné, ale konstruktor i destruktor byly nutné pro nastavení obrazovkového módu. Obě metody ( PutPixel a GetPixel ) snad není nutno vysvětlovat. Každá třída může mít vlastnosti společné s jinou třídou objektů. Existují dva takovéto vztahy, jedním z nich je dědičnost a druhý vlastnictví. Dědičnost znamená, že nějaká třída je potomkem třídy jiné, je datově kompatibilní a má všechny vlastnosti svého předchůdce. Příklad dědičnosti může být takovýto: TStavba=Object X,Y:Byte; { souřadnice stavby } Procedure Draw; { metoda na vykreslení na obrazovku } Procedure SetXY( _X,_Y:Byte ); Constructor Init( _X,_Y:Byte ); { nastaví proměnné } Destructor Done; End; TDum=Object( TStavba ) { dům je potomkem stavby } Bytu:Byte; { bude mít navíc ještě proměnou vyjadřující nějakou vlastnost, která objektu TStavba chybí } Constructor Init( _X,_Y,_Bytu:Byte ); { nastaví proměnné } End; Vzhledem k rozdílné datové struktuře pak konstruktory vypadají takto: Constructor TStavba.Init( _X,_Y:Byte )ů Begin X:=_X; Y:=_Y; End; Constructor TDum.Init( _X,_Y,_Byytu:Byte ); Begin Inherited Init( _X,_Y ); Domu:=_Domu; End; A navíc budeme schopni provést například takovéto přiřazení: Var Dum:PDum; Begin Dum:=New( PDum,Init( 10,20,30 ); Dum^.SetXY( 30,20 ); { přiřazení přímo instanci } PDum( Dum )^.SetXY( 30,20 ); { přiřazení přetypovaním stejným typem } PStavba( Dum )^.SetXY( 30,20 ); { přiřazení přetypovaním typem předchůdce } End; Z těchto přiřazení je hlavně zřejmá datová kompatibilita obou tříd objektů, která se bude hodit hlavně u abstraktních metod. Syntaxe zápisu metod není nikterak složitá. Jak jste si jistě již všimli v příkladech na objekty, které jsem zde již napsal, v metodě se používají proměnné daného objektu přímo, jako by to byly lokální proměnné dané metody. Zvláštním případem je pak identifikátor SELF, který vlastně zprostředkovává samu instanci objektu. Využití tohoto identifikátoru je patrné hlavně pokud přejdeme k druhému vztahu objektů, a to je vlastnění se. Objekt vlastní jiný objekt jednoduše právě tehdy, když vlastní ukazatel na něj. Příklad: Objekt TVeverka je potomkem nějakého obecného objektu zvířátko a bude vlastnit objekt TZuby. TVeverka=Object( TZviratko ) Zuby:PZuby; Procedure Kousni; { metoda, která použije vlastněný objekt } End; Potom, pokud bude chtít přistoupit na nějakou metodu třídy TZuby, je schopen zavolat jejich metodu. Příklad takovéhoto zavolání je metoda Kousni: Procedure TVeverka.Kousni; Begin Zuby^.KSobe; Zuby^.OdSebe; End; Vlastnění se je jediná možnost, jak v pascalu provést určité zvláštnosti v dědičnosti. Typickým příkladem je to, že má objekt dědit od dvou tříd současně. V paskalu to naní možné udělat přímo. Představte si objekt THasubót, který by měl některé vlastnosti dědit od třídy TLoď (plave přece na vodě ) a některé od třídy TDům, protože je obytný. Strom dědičnosti by pak vypadal asi takto: TObject / \ TStavba TLod / / TDum / \ / \ / \ / THausbot To samozřejmě paskal nedovolí, a proto se to řeší tak, že objekt THausbot bude potomkem objektu TLod, který bude vlastnit TDum, tedy vlastně hausbót je loď, která má na sobě dům. V programu to bude vypadat takto: THausbot=Object( TLod ) Dum:PDum; End; Další situace, kterou je nutno řešit, je, pokud se dva objekty vlastní navzájem. Představte si například hru člověče nezlob se,kde objekt TPolíčko vlastní ( ukazuje na ) objekt třídy TFigurka, který na políčku stojí a každá figurka taktéž vlastní ( ukazuje na ) jedno políčko, na kterém stojí. Potom právě využijeme operátoru SELF, protože s přesunem figurky je nutné změnit oba pointery. Tedy třídy vypadají takto: TFigurka=Object( THraciObjekt ) Policko:PPolicko; Procedure PosunseNaPolicko( _Policko:PPolicko ); End; TPolicko=Object( THraciObjekt ) Figurka:PFigurka; Procedure AkceptujFigurku( _Figurka:PFigurka ); End; Procedure TFigurka.PosunseNaPolicko( _Policko:PPolicko ); Begin Policko:=_Policko; Policko^.AkceptujFigurku( @SELF ); { poskytne políčku pointer na sebe sama } End; Procedure TPolicko.AkceptujFigurku( _Figurka:PFigurka ); Begin Figurka:=_PFigurka; End; Když už tak perfektně ovládáme dědičnost,je důležité, abychom se naučili ji maximálně využívat. K tomu nám pomůžou metody nazvané virtuální. Virtuální metoda je metoda, která má stejný interface, ale různý kód pro různé potomky. Interface není přímo stejný, ale opět pouze datově kompatibilní ( tzn může mít více, ale nikoliv méně parametrů ). Příklad virtuální metody může být například metoda, která v rámci nejakého jednoduchého simulátoru řekněme zoologické zahrady zobrazuje jednotlivá zvířata ( pro jednoduchost předpokládám třeba textový režim ). TVeverka a TTygr jsou potomky TZvířete: TZvire=Object X,Y:Word; Zivoty:Real; Procedure NakresliSe; Virtual; End; TVeverka=Object( TZvire ) Zivoty:Real; Procedure NakresliSe; Virtual; End; TTygr=Object( TZvire ) Zivoty:Real; Procedure NakresliSe; Virtual; End; Procedure TZvire.NakresliSe; Begin GotoXY( X,Y ); End; Procedure TVeverka.NakresliSe; Begin Inherited NakresliSe ; Write( 'v' ); End; Procedure TTygr.NekresliSe; Begin Inherited NakresliSe ; Write( 'T' ); End; Z tohoto příkladu ( i když není zrovna nejlepší ) jsou myslím si docela dobře vidět vlastnosti dědění u virtuálních metod, hlavně možnost použít identifikátoru Inherited pro zavolání již napsaného kusu kódu. Zde bych chtěl upozornit na jednu věc. Předpokládejme, že konstruktory TZvíře.Init a TVeverka.Init mají rozdílný počet paramterů. Pak vypadá použití identifikátoru Inherited takto: Constructor TZvire.Init( _X,_Y:Word ); Begin X:=_X; Y:=_Y; End; Constructor TVeverka.Init( _X,_Y:Word;_Zivoty:Real ); Begin Inherited Init( _X,_Y ); Zivoty:=_Zivoty; End; Zde už je úspora trošku více znát a je i vidět způsob volání již existující zděděné části. Speciálním případem virtuální metody je metoda abstraktní. Její využití je důležité tam, kde v době deklarace předka ještě neznáte typ následníka. Typický případ je porovnávání podle obecné položky. Představte si, že máte třídu TEntita, která vlastní pole nějakých objektů TPoložka. A potom máte třídu TSeznam, která je spojovým seznamem TPoložek. A vy chcete TPoložky v TSeznamu seřadit podle n-té TPoložky. A každá TPoložka je jiného typu. Jak to vyřešíme? Docela jednoduše - abstraktní metodou. Třída TPoložka bude mít virtuální metodu JsiVětšíNež, která bude boolovská funkce. Ta bude definována jako abstraktní ( je povinná pro všechny následníky, ale její kód závisí na datové struktuře ). Pascal nezná přímo klíčové slovo Abstract, ale lze abstraktní metody napsat. Ukážeme si tedy abstraktní metodu pro potomky třídy TPoložka: TPolozka=Object Data:String; { data jsou ulozena ve stringu, nemusi byt ale nutne string } Function JsiVetsiNez( _Kdo:PPolozka ):Boolean; Virtual; End; Function TPolozka.JsiVetsiNez( _Kdo:PPolozka ):Boolean; Begin End; { nic neprovadi ( vlastne implicitne vraci true ) } TJmeno=Object( TPolozka ) Function JsiVetsiNez( _Kdo:PPolozka ):Boolean; Virtual; End; TVyska=Object( TPolozka ) Function JsiVetsiNez( _Kdo:PPolozka ):Boolean; Virtual; End; Function TJmeno.JsiVetsiNez( _Kdo:PPolozka ):Boolean; Begin If Data>_Kdo^.Data { porovnani stringu, u jmena postacuje } Then JsiVetsiNez:=True Else JsiVetsiNez:=False; End; Function TVyska.JsiVetsiNez( _Kdo:PPolozka ):Boolean; Var Tmp1,Tmp2:Real; Code:Integer; Begin Val( Data,Tmp1,Code ); Val( _Kdo^.Data,Tmp2,Code ); If Tmp1>Tmp2 Then JsiVetsiNez:=True Else JsiVetsiNez:=False; End; Myslím si, že tento příklad je dostatečně názorný. Pokud pak chcete takovýto seznam setřídit například podle čtvrté datové položky, pustíte na něj jednoduše bubble sort tímto způsobem ( předpokládám, že metoda Prohoď patří TSeznamu a že není problém ji napsat ). Takto pak vypadá celý problém: Tseznam=Object Entita:PCollection; { jsem liny, pouzivam pascalsky spojovy seznam } Procedure SetridSe( PodleCeho:Byte ); { nadefinujeme si } Procedure Prohod( _Koho,_Skym:PEntita ); { predpokladam, ze neni problem napsat prohozeni dvou pointeru } End; TEntita=Object Polozka:Array[ 1..10 ]Of PPolozka; { vsimnete si obecne definice typu } End; Jak vypadá položka jsme si již nadefinovali včetně několika potomků. Metody potom vypadají takto: Procedure TSeznam.SetridSe( PodleCeho:Byte ); Var I:Byte; Begin { jak vypada bubble sort snad vsichni vite, takze zde vypisi pouze vnitrek toho while cyklu } For I:=1 To Entita^.Count-1 Do { od 1 do poctu polozek-1 } If Entita^.At( I )^.Polozka[ PodleCeho ]^.JeVetsiNez( Entita^.At( I+1 )^.Polozka[ PodleCeho ] ) Then Prohod( Entita^.At( I ),Entita^.At( I+1 )); { myslim si, ze genialita je natolik jasna, ze netreba objasnovat, ale prece pro jistotu: i-ta entita se podle sveho typu porovna s i+1-ni a sama vrati pomoci funkce JeVetsiNez splneni nebo taky nesplneni podminky pro bouble sort } End; Myslím si, že tento vyčerpávající pokus ukázat vám funkci metod, které se jmenují abstraktní a virtuální k něčemu byl a že jste jakž takž pochopili, o co v OOP jde. Takže na závěr ještě pár zásad pro úspěšné používání. Vždy si udělejte OBJEKTOVOU ANALÝZU a nestyďte si nakreslit si na papír strom dědičnosti a pacičky, jak se objekty vlastně budou vlastnit. Čím více tříd objektů, tím lépe se vám bude pracovat. Využívejte možnosti paskalu rozdělit si program do několika unit a v každé se starejte o něco jiného. Pokud se něco tváří jen trochu obecně, udělejto to obecné a teprve potomky konkretizujte. A navíc se nebojte udělat si více hladin dědičnosti. Hodně štěstí v OOP vám přeje Marky Parky výheň