Hlavní navigace

Kylix (3) - OOP

30. 4. 2002
Doba čtení: 8 minut

Sdílet

Vítejte u třetího dílu seriálu o Kylixu. Podle různých ohlasů jsem vypozoroval, že spousta lidiček si pod pojmem Object Pascal představí své setkání s Turbo Pascalem z doby, kdy tady ještě byli husiti. Není tedy jiné cesty, než objasnit, co Object Pascal vlastně umí.

Některé věci již byly uvedeny v minulých dílech a něco uvedeme příště. Dnes se vrhneme na objektově orientované programování. Jsem si vědom toho, že někteří programátoři OOP nenávidí, ale zkusím to risknout a budu doufat, že i pro ně to bude trošičku užitečné.

Na konci určitě uvedu opět nějakou tu komponentku a aplikaci psanou v Kylixu.

Třída a objekt

Programátoři často chybují v pojmech třída a objekt. Oblíbeným rčením je, že objekt je instance třídy.

Nejjednodušší je uvést analogii mezi typem proměnné a proměnnou. Třída je jakoby typ a objekt je jakoby proměnná. Ve své podstatě typ a třída existují jen jako pomůcka pro programátora a kompilátor.

Pokud tedy napíšeme var objekt:TMujObject, tak sice je v paměti vymezen prostor pro objekt (= pointer = u 32bitového překladače čtyři bajty), ale objekt ještě neexistuje. Teprve voláním konstruktoru (dle konvence

Create, ale není to předpis, navíc může být konstruktorů více) je v paměti na haldě alokováno místo o potřebné velikosti. Destruktor (dle konvence Destroy) objekt z paměti uvolní a paměť je vrácena. Pozn.: místo Destroy volejte metodu Free, která interně destruktor zavolá. Proměnná ale pořád obsahuje odkaz na paměť. Lepší je volat proceduru FreeAndNil, která má parametr typu TObject a po zavolání Free nastaví parametr na nil, což značí prázdný objekt.

Dobré je také používat konstrukci

if Assigned(objekt) then FreeAndNil(objekt)

Funkce Assigned je optimalizovaná varianta objekt<>nil.

Zde bych se rád trošku pozastavil u správy paměti Kylixu. Jelikož jsou často alokovány malé bloky paměti, bylo by neefektivní, aby tím byl neustále obtěžován správce paměti v systému. Místo toho si Kylix vyžádá blok paměti (64 Kb) a vlastním správcem paměti z něho přiděluje malé kousky. Až paměť dojde, je vyžádán další kus. Na konci běhu jsou všechny bloky paměti vráceny zpět systému.

Pokud zapomeneme zavolat konstruktor, není objekt vytvořen. Jelikož si ale kompilátor nemůže být jist, že to tak nechceme schválně, je zahlášen pouze warning. Případné volání nějaké metody skončí s chybou přístupu k paměti a je vyvolána výjimka EAccessViolation. Tato, stejně jako ostatní výjimky, je následníkem třídy Exception.

Zde bych rád uvedl, že pokud si to programátor vysloveně nepřeje, nedojde v takovýchto případech k ukončení činnosti programu, ale pouze k zobrazení chyby. Kylix totiž instaluje globální ovladač výjimek, kterým jsou odchyceny všechny výjimky neodchycené programem. Může mne trefit, když mi některý program v Linuxu spadne se signálem SIGSEV, ale přitom by šlo ve většině případů určitě klidně pracovat dále, jen by se zobrazila informace o chybě. Tohle se v Kylixu stane doopravdy málokdy a jsem si jist, že je to bez urážky mnohem robustnější řešení, jelikož opravdu málo chyb je takového významu, že by nešlo alespoň uložit data.

Viditelnosti polí

V Object Pascalu existují čtyři stupně viditelnosti polí v rámci třídy.

TMyClass=Class(TObject)
  private
  protected
  public
  published
end;

V každé z těchto sekcí může být uvedena deklarace metody, proměnné (někdo tomu říká i jinak) nebo property (má význam hlavně u public

a published).

Sekce private je nejpřísnější. Co je zde uvedeno, není vidět nikde kromě jednotky, kde je třída umístěna. Píši schválně jednotky a ne třídy, jelikož dvě třídy v jedné jednotce si mohou navzájem přistupovat k polím v části private.

Sekce protected viditelnost lehce uvolňuje, jelikož pole zde definovaná lze vidět i v následnících mateřské třídy. V ostatních případech pole vidět nejsou.

Pole v sekci public jsou vidět všude.

Pole v sekci published mají stejnou viditelnost jako public. Rozdíl je v tom, že se za běhu vytvářejí RTTI (RunTime Type Information). Díky tomu mohou externí aplikace získat za běhu o těchto polích doplňující informace. Mohou zde být uvedeny pouze třídy.

Metody

Metody poskytují rozhraní pro přístup k proměnným třídy.

Jistě jste si všimli, že někde používám výraz procedura, někde funkce a někde metoda. Funkce se liší od procedury tím, že vrací hodnotu (hodnota je v těle funkce reprezentována proměnnou Result stejného typu, jako je návratová hodnota). Metodou je „procedura“ nebo „funkce“ u třídy.

Pozn: klasickým názorem je, že OOP je nutně pomalejší než klasické programování. Když se oprostíme od již uvedených výhod OOP, tak bych rád upozornil, že většina metod nemá parametry (většina jejich parametrů je součástí definice třídy), a tudíž by volání mělo být teoreticky rychlejší. Jak je to ve skutečnosti, záleží na kompilátoru a (asi) na okolnostech.

Metody statické

Metody statické jsou všechny metody, pokud není uvedeno jinak. Pokud kompilátor narazí na kód typu Třída.Statická­Metoda, tak prostě vygeneruje kód pro volání StatickáMetoda v objektu Třída nebo pro volání této metody u předchůdce, od kterého tuto metodu zdědila.

Metody virtuální

Metody virtuální jsou označovány v deklaraci třídy jako virtual.

  procedure Test5; virtual;

Na rozdíl od statických metod kompilátor negeneruje kód přímo pro volání konkrétní třídy, ale používá se mechanismu pozdního svázání. Metoda, která se bude volat, je určena až za běhu.

Virtuální metoda může být v následnících předefinována (override). Při předefinování musí být deklarace přesně zachována (mění se pouze implementace).

  procedure Test5; override;

Pokud tedy kompilátor narazí na konstrukci Třída.Virtuál­níMetoda, je možné, že byla metoda předefinována. Vygenerovaný kód musí za běhu nalézt v tabulce virtuálních metod správnou metodu a zavolat ji.

Metody dynamické

Metody dynamické se používají ke stejnému účelu jako virtuální. Jsou označovány v deklaraci třídy jako dynamic.

  procedure Test5; dynamic;

Rozdílem je, že dynamické procedury jsou pomalejší, ale zabírají méně prostoru. Používají se v případě, že základní třída má mnoho metod, které se sice mohou předefinovat, ale neděje se tak příliš často, a navíc má mnoho následníků.

Metody abstract

Metoda je definovaná, ale nemá implementaci. Instance této třídy, způsobí výjimku (narozdíl od Javy jde přeložit, i když s warningem). Metoda musí být v následnících předefinována, a proto musí být virtuální.

  procedure Test5; virtual; abstract;

Přetěžování funkcí

Pokud chceme definovat několik metod (nebo např. konstruktorů) se stejným názvem, musíme tuto skutečnost kompilátoru sdělit. Tím kouzelným slůvkem je overload. Každá z metod pak musí mít svou implementaci.

  procedure Test6 (aValue:String); overload;
  procedure Test6 (aValue:Integer); overload;

Volání předchůdce

Předchůdce zavoláme pomocí klíčového slova inherited.

Destructor TMyClass.Destroy;
begin
  inherited;
// nebo taky inherited Destroy;
end;

Přetypování

Třídu můžeme přetypovat použitím klíčového slova AS. Přetypování bychom měli použít jen tehdy, když jsme si jisti, že daný objekt obsahuje instanci (nebo následníka) třídy, na kterou ho chceme přetypovat. Pokud to není pravda, je vyvolána výjimka EInvalidCast.

Explicitní přetypování je také možno způsobem Třída(Objekt). V tomto případě ale není zaručeno, že nedojde k chybě přetypováním (není kontrolován typ objektu).

Operátor IS slouží k určení, zda je typ daného objektu roven danému typu třídy nebo některému následníku. Například if Objekt IS Třída then …

Dědičnost

Pokud definujeme, že nějaká třída je následníkem jiné třídy, znamená to kromě jiného, že nová třída získá všechny metody a proměnné, které byly definovány pro předchůdce (s ohledem na viditelnost).

Vlastní operace je zapsána takto:

type
  TNaslednik=class (TPredchudce)
  // zde přidáme nové
  end;

Předchůdce zavoláme pomocí inherited. Klíčové slovo Self je referencí na sebe sama (např. self.Print).

Polymorfismus

Polymorfismus je jednou ze základních vlastností OOP. Raději rovnou uvedu příklad:

unit uObjects;

interface
type
  TZvire=class
    function Zvuk:String;virtual;
  end;

  TPes=class (TZvire)
    function Zvuk:String;override;
  end;

implementation

function TZvire.Zvuk:String;
begin
// obecný zvuk roztomilého zvířátka
  Result:='grrrrrrrr';
end;

function TPes.Zvuk:String;
begin
  Result:='hafinky haf'; //pes je taky zvíře
end;

end.

V uvedeném unitu jsou definovány dvě třídy: TZvire a TPes. Třída TPes je následníkem TZvire a je v ní předefinována jedna metoda.

Hlavní program (uložen v TestOOP.dpr):

program TestOOP;
uses
  Classes,
  uObjects in 'uObjects.pas';

var
  zvire:TZvire;
  pes:TPes;

procedure ProjevSe(const Kdo:String; aZvire:TZvire);
begin
 writeln(Kdo+' dělá '+aZvire.Zvuk);
end;

begin
  zvire:=TZvire.Create; // instance zvířete
  pes:=TPes.Create;  // instance psa
  try
    ProjevSe('Zvíře',zvire);
    ProjevSe('Pes',pes);
  finally
    pes.Free;
    zvire.Free;
  end;
end.

Jádrem příkladu je druhý parametr procedury (nikoliv metody – viz výše).

Všimněte si, že je typu TZvire, a přesto ji v hlavním programu volám i s objektem pes. Metoda Zvuk je virtuální, a proto je za běhu zavolána ta, která odpovídá objektu. Výsledkem běhu programu bude:

Zvíře dělá grrrrrrrr Pes dělá hafinky haf

A co příště?

To by pro dnešek z OOP stačilo. Příště uvedu už jen pár poznámek ohledně dědičnosti rozhraní (Object Pascal nemá vícenásobnou dědičnost, pouze dědičnost rozhraní – jako Java) a procedurálních typů. A pak KONEČNĚ zkusíme naklikat nějakou GUI aplikaci. Myslím si totiž, že je důležité uvést nejdříve principy a až pak vás ohromit okýnkama.

Poznámky

Minule jsem popsal, jak se zbavit splash screenu u GUI aplikací. Pokud nechcete ani informaci o GPL u konzolové aplikace, stačí nedefinovat symbol APPTYPE na CONSOLE. Každá aplikace v Linuxu je totiž (narozdíl od Windows) konzolová.

A co příkaz for?

Oblíbeným tématem v diskusích je omezenost implementace příkazu for. Jelikož by se to tu jistě jednou objevilo, rád bych tedy na začátku rovnou uvedl, že příkaz for se liší od příkazu for v Cčku tím, že pracuje s ordinálními typy (tedy nejen s celými čísly, ale i s intervalem, výčtem atd.). Pascal je totiž přísně typovým jazykem. Pro for jako v Cčku použijte while.

type
  TVycet=(vPrvni, vDruhy, vTreti);
  TInterval=21..45;
var
  xVycet:TVycet;
  xInt:TInterval;
.....
  for xVycet:=vPrvni to vTreti do;
  for xInt:=Low(TInterval) to High(TInterval) do;

Komponenty – FreeCLX

Opensource verze knihovny CLX od firmy Borland. Je nutná, pokud chcete v Kylixu Open Edition psát DB aplikace. Instalací získáte hafušo komponent.

S instalací pod Kylix 2 pomůže HOW-TO na adrese http://www.ze­oslib.org/how_to­.php.

Podporovány jsou Delphi a Kylix.

Licence: GPL

Zdrojové kódy: ANO

CS24_early

Homepage: http://freeclx­.sourceforge.net

Zajímavá aplikace

Na adrese http://www.sy­sinternals.com je FileMon pro Linux napsaný v Kylixu.

Byl pro vás článek přínosný?