Objektově Orientované Programování - V BORLAND PASCALU
V minulém čísle vyšel článek o OOP. Jako dodatek k němu připojuji
toto stručné zasvěcení do syntaxe OOP v Borland Pascalu.
Sám jsem začínal hezky pomaloučku. Krůček po krůčku. První krůček
už máš určitě za sebou, takže se při čtění příštího odstavce nic
nového nedozvíš. Ale postupně bys měl v článku dojít až k místu, kde
narazíš na nějaké nové informace (kdybys syntaxi ovládal, asi bys
tohle nečet... leda že bys hledal bugy :-).. zdravím cpoce, hi dood!).
_____ Object _____
Věděl jsem, že existují nějaké objekty, vypadá to jeko record, ale
místo record se píše object. Tak jsem to zkusil a všechno se chovalo
jako record. Fajn.
_____ Dědění _____
Pak bylo potřeba, aby jeden record zdědil položky jiného. V
nějakém zdrojáku jsem viděl vazbu object(předek), tak jsem ji zkusil
použít...
Type
typ1=object
nějaká_proměnná : byte;
jiná_proměnná : word;
end;
typ2=object(typ1)
další_proměnná : string;
end;
A ono to funguje, v typu2 jsou obsaženy i všechny položky z
typu1. To je teda ta dědičnost, príma.
_____ Metody _____
Další, co asi tak uhodí do oka při prohlížení OOP zdrojáků, jsou
hlavičky procedur a funkcí v definici typu. To jsou metody.
Type
typ1=object
abc : integer;
procedure xyz;
end;
Procedure typ1.xyz;
begin
write(abc);
end.
Třída typ1 má atribut abc a metodu xyz. Všimni si, čím se metoda
xyz liší od obyčejné procedury. Za prvé, před názvem má "typ1.". Za
druhé, uvnitř metody se dá normálně pracovat s atributy, zde abc.
_____ Constructor a destructor? _____
Konstruktor a destruktor jsou skoro úplně obyčejné metody. Jen
místo procedure se u nich píše constructor, resp. destructor. Třída
může mít víc konstruktorů i destruktorů. Většinou ale mívá po jednom
a obvykle se pojmenovávají Init a Done. Jejich náplní by mělo být něco
jako inicializace a zrušení jedné instance. Obvykle to jsou také první
a poslední věc, které ta instance zažije. Všechno píšu tak neurčitě
proto, že máme v použití konstruktorů a destruktorů spoustu
volnosti (na rozdíl od C++, kde se samy zavolají automaticky IHNED po
vzniku instance a AŽ při destrukci instance). V pascalu je můžeme
volat podle libosti třeba osmkrát za sebou nebo ani jednou, ale jak už
jsem psal, obvykle se volají jen jednou a to na začátku a na konci.
Navíc platí, že pokud má třída virtuální metody, nelze je volat dříve
než konstruktor.
_____ Fail _____
Další zvláštností je příkaz Fail, který lze použít pouze uvnitř
konstruktoru. Tenhle odstaveček by měl být až někde u dna stránky,
protože Fail není tak důležitý, ale kvůli New a Dispose bys o něm měl
vědět.
Konstruktor se zvenčí tváří jako funkce, vrací true pokud uspěl,
false pokud neuspěl. Ale zevnitř vypadá jako procedura, nelze
nastavit result. Jediným způsobem jak ohlásit neúspěch je příkaz Fail,
který konstruktor zároveň ukončí (exitne). Pokud konstruktor
proběhne bez failu, skončí úspěchem.
_____ New a Dispose _____
Dosud to vypadalo tak, že konstruktor a destruktor jsou úplně
obyčejné metody. Jedna z jejich odlišností se ale může projevit u
příkazů New a Dispose. Mějme třídu a její instance
Type
typ1=object
abc : integer;
constructor init;
procedure xyz;
destructor done;
end;
Var
a:typ1;
p:^typ1;
Po spuštění programu je a neinicializovaná instance a p
neinicializovaný pointer na instanci. a můžeme jednoduše inicializovat
příkazem a.init;. Pro inicializaci p existuje několik ekvivalentních
variant:
1) new(p); if p<>nil then if not p.init then dispose(p);
2) new(p,init); - new se chová jako procedura
3) p:=new(^typ1,init); - new se chová jako funkce : ^typ1
Máme tedy rozšířený příkaz new. Jeho druhým parametrem ale musí
být konstruktor, žádná jiná metoda. Analogicky existuje rozšířený
dispose, ve kterém lze zadat pouze destruktor.
1) p.done; dispose(p);
2) dispose(p,done);
_____ Inherited, Self _____
Občas se někde objeví slůvka jako self nebo inherited. Vyskytovat
se mohou pouze v metodách. Self je instance se kterou metoda
pracuje.
Předek a potomek mohou mít stejně nazvanou metodu, to je
korektní. Když potomek provede příkaz xyz;, volá se jeho xyz. Zavolat
xyz předka lze příkazem inherited xyz;.
_____ Virtual _____
Pro jistotu lehce zopáknu něco z minulého článku. Máme p pointer
na instanci typu xxx, ale ten ukazuje na instanci typu yyy (kde yyy
je potomek xxx). Když zavoláme nějakou metodu, provede se metoda
třídy xxx nebo yyy? U statických metod xxx, u virtuálních yyy. Neboli
u statických rozhoduje typ pointeru, u virtuálních typ instance.
A teď už syntaxe: pokud za deklarací metody uvedeme virtual;,
bude virtuální, v opačném případě zůstane statická. Pokud má potomek
stejně nazvanou metodu jako předek, musí zachovat její statičnost,
resp. virtuálnost.
_____ Private, public _____
Zapouzdření znamená, že všechno potřebné pro práci třídy bude
zahrnuto v ní. Na povrchu bude vidět jen to co nás zajímá z pohledu
uživatele objektu (metody udělej_tohle, udělej_tamto). Uvnitř asi
budou další důležité metody a atributy, ale ty už uživatele nezajímají
a ani by o nich neměl vědět. Proto lze každému atributu a každé
metodě říct jestli je privátní nebo veřejná. Nejjednodušší vysvětlení
bude příklad:
Type
typ1=object
abc : integer;
private:
efg : string;
hij : byte;
public:
constructor init;
procedure xyz;
private:
destructor done;
end;
To co je za private: je privátní, to za public: je veřejné. Položky
před prvním private nebo public jsou public.
Co přesně ta veřejnost znamená? Že k položce může přistupovat
kdokoliv a kdekoliv. Naproti tomu k private položce lze přistupovat
pouze uvnitř metod dané třídy a uvnitř metod jejích potomků v témže
modulu (unitě). Na jiných místech "není vidět", její použití znamená
syntax error (neznámý identifikátor).
Pozor na private. Inherited xyz nevrací metodu xyz
bezprostředního předka, ale nejbližší public xyz. Když má předek
private xyz a prapředek public xyz, inherited vrací prapředka.
_____ Chyby _____
Našel jsem jednu. Pokud má třída dva konstruktory a z jednoho se
volá druhý, a pokud má potomka, ten se nesprávně inicializuje. Uvedu
příklad.
Type
typ1=object
constructor init1; {inicializace...}
constructor init2; {obsahuje volání init1}
end;
Type
typ2=object(typ1)
constructor init1; {obsahuje volání inherited init1}
constructor init2; {obsahuje volání inherited init2}
end;
New(typ2,init1) vrací správně objekt typu typ2, ale new(typ2,init2)
vrací hybrid tvaru a velikosti typ2 ale s tabulkou virtuálních metod
třídy typ1.
_____ TCollection, TStream _____
K čemu všechna ta teorie.. hrr do praxe. Ohromně (giganticky,
kolosálně) užitečnými se ukazují třídy TCollection a TStream dodávané
s Borland Pascalem (jsou v unitě objects). TCollection představuje
kolekci "objektů" stejného typu (třeba bajtů, třeba stringů, třeba
buttonů..). Do kolekce lze přidávat, odmazávat, vyhledávat, provádět
libovolné akce na všechny prvky... K prvkům je přímý přístup přes pole
pointerů. TStream představuje proud dat, ze kterého lze číst, psát
do něj, seekovat v něm.
_____ C++ _____
Vážně se nechceš naučit radši C++? :)
Dee