+---+ +---+ +---+
                              | | | | | | +---+
                              +---+ +---+ |

        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ň