Dnes dokončíme OOP – řekneme si něco o třech základních rysech OOP: zapouzdření, dědičnosti a polymorfismu.
Dnes se podíváme na 3 základní pojmy objektově orientovaného programování (OOP), a tím je polymorfismus, zapouzdření a dědičnost.
Zapouzdření
Zapouzdření je důležitá vlastnost OOP, protože způsobuje, že dvě instance jedné třídy mohou obsahovat různé, na sobě nezávislé, hodnoty.
Jednodušší asi bude použít nějaký příklad, takže si to ukážeme třeba na obyvatelích nějakého městečka. Nedefinujeme si tedy třídu TObyvatel:
type
TObyvatel = class
jmeno: string;
prijmeni: string;
bydliste: string;
vek: integer;
zamestnani: string;
procedure Prestehuj(kam: string);
procedure ZmenZamestnani(noveZamestnani: string);
procedure UkazInfo;
end;
No a pak samotný program by mohl vypadat třeba takto:
…
var
…
obyvatel1: TObyvatel;
obyvatel2: TObyvatel;
…
…
procedure TForm1.Create(Sender: Tobject);
begin
obyvatel1:=TObyvatel.Create; //vytvoříme novou instanci třídy TObyvatel
obyvatel1.jmeno:='Pepa';
...
obyvatel1.vek:=20;
obyvatel2:=TObyvatel.Create; //vytvoříme další instanci třídy TObyvatel
obyvatel2.jmeno:='Dan';
...
obyvatel2.vek:=25;
end;
procedure TObyvatel.Prestehuj(kam: string);
begin
bydliste:=kam;
end;
procedure TObyvatel.ZmenZamestnani(noveZamestnani: string);
begin
zamestnani:=noveZamestnani;
end;
procedure Tobyvatel.UkazInfo;
begin
MessageDlg('Jméno:'+jmeno+#13+
'Příjmení:'+prijmeni+#13+
'Bydliště:'+bydliste+#13+
'Zaměstnání:'+zamestnani+#13+
'Věk:'+IntToStr(vek),mtInformation,[mbOK],0);
end;
Na formulář nyní přidáme 6 tlačítek. První tři tlačítka budou pro obyvatele1, zbylá tři budou určena pro obyvatele2.
Zde jsou procedury prvních tří tlačítek (zbylé tři budou vypadat stejně, akorát místo obyvatel1 tam bude obyvatel2):
procedure TForm1.Button1Click(Sender: Tobject); //zobrazuje info o obyvateli
begin
obyvatel1.UkazInfo;
end;
procedure TForm1.Button2Click(Sender: TObject); //přestěhuje obyvatele
begin
obyvatel1.Prestehuj('Brno');
end;
procedure TForm1.Button3Click(Sender: TObject) //změní zaměstnání
begin
obyvatel1.ZmenZamestnani('šéf');
end;
Když nyní program spustíte a kliknete na 1. tlačítko, mělo by se vám zobrazit informace o obyvatel1. Když se ale podíváte do procedury TObyvatel.UkazInfo, tak zjistíte, že tam nemáme určeno, o kterém uživateli se má info zobrazit. Když kliknete na 4. tlačítko, zobrazí se zase informace o obyvateli2. To je možná právě díky zapouzdření, kdy jednotlivé instance, jejich metody a vlastnosti jsou od sebe separovány.
Dědičnost
Dědičnost je dalším ze základních rysů OOP a spočívá v možnosti vytvářet nové třídy s využitím objektů jiných tříd tak, aby objekty zůstaly zachovány a jenom se rozšířily o nové – zjednodušeně řečeno: aby nová třída zdědila vše po jiné a ještě si mohla přidat vlastní objekty (tzn. vlastnosti, funkce, procedury…).
Pro lepší pochopení zde ukážu příklad (trochu divný, ale nic lepšího mě nenapadlo): Řekněme, že chceme vytvořit evidenci obyvatel města. Nadefinujeme si tedy třídu pro děti:
type
TDeti = class
jmeno: string;
prijmeni: string;
pohlavi: char;
rodnecislo: integer;
bydliste: string;
end;
Nyní ještě potřebujeme třídu pro dospělé. Ta má být stejná jako u dětí, jen ještě musí obsahovat informace o zaměstnání:
type
TDospeli = class(TDeti)
zamestnani: string;
end;
Třída TDospeli teď má stejné objekty jako třída TDeti a ještě navíc k tomu vlastnost zamestnani. Za klíčovým slovem class se v závorce uvádí název třídy, po které má nová třída dědit. Nemusíte využívat pouze vlastní třídy, ale i existující třídy jako třeba TButton atd., i když to se využívá spíš při vytváření vlastních komponent (i k tomu se možná během seriálu dostaneme).
V programu pak tedy bez problému může být toto:
...
var dospely: TDospeli
dite: TDite;
...
dite.jmeno:='Prokop';
dite.prijmeni:='Buben';
...
dite.bydliste:='Praha';
dospely.jmeno:='Franta';
dospely.prijmeni:='Vomáčka';
...
dospely.zamestnani:='programátor';
ale
dite.zamestnani:='zlatokop';
už je chyba, protože děti pracovat nesmějí a třída TDeti tuto vlastnost ani nemá.
Delphi u zděděných tříd zachovávají určitou kompatibilitu, což nám v případě, že dítě dosáhne plnoletosti a budeme ho chtít přeřadit k dospělým, nebude činit problém udělat toto:
dospely:=dite;
Potom tedy dospely.jmeno bude Prokop, dospely.prijmeni bude Buben atd.
Ale pozor na jednu věc!
dite:=dospely;
je chyba!!! Dospělý se sice může chovat jako dítě, nicméně v registraci je uveden jako plnoletý, a tak ho není možné zase vrátit k dětem.
Stejně jako jsme zde dědili pouze vlastnosti, můžeme klidně dědit i procedury a funkce. Pokud ale ve třídě, která dědí, nadeklarujete proceduru či funkci, která se bude jmenovat stejně jako nějaká jiná funkce v předkovi, tak ta nová tu starou překryje!
type
TPredek = class
…
procedure Udelej(promenna: string);
…
end;
type
TPotomek = class (TPredek)
…
procedure Udelej(promenna1, promenna2: string);
…
end;
…
var deda: TPredek;
vnuk: TPotomek;
…
…
deda.Udelej('nakrm slepice');
vnuk.Udelej('posekej zahradu','umyj nádobí');
…
je správně, ale
...
vnuk.Udelej('vypadni od počítače');
...
je chyba, protože procedura Udelej ve třídě TPotomek má dva parametry, neboť překryla tu původní dědovu, která má jen jeden.
Polymorfismus
Polymorfismus (lze česky označit jako mnohotvárnost) je způsob přístupu k instancím a je to nejvyšší stupeň OOP.
Odborně řečeno: polymorfismus spočívá v praxi například v tom, že na místo, kde je očekávána instance nějaké třídy, můžeme dosadit i instanci libovolné její podtřídy (třídy, která přímo či nepřímo z této třídy dědí), která se může chovat jinak, než by se chovala instance rodičovské třídy (ovšem v rámci mantinelů daných popisem rozhraní).
Pokud tomu moc nerozumíte, tak to není nic zvláštního. Zde je trochu názornější vysvětlení:
Když se program překládá, automaticky se již při překladu určí, jestli se použije metoda předka nebo potomka a ta se potom v programu použije. Tomuto se říká tzv. statická vazba. Pokud je ale nutné, aby se to určovalo až za běhu programu, využívá se tzv. pozdní vazby – ta se používá pro virtuální metody.
V minulém příkladu jsem si řekli, že metoda potomka překrývá metodu předka (viz f-ce. Udelej). Pokud pak metodu předka nadefinujeme jako virtuální, pak se metoda potomkem neodstraní, ale potlačí.
Použití virtuální metody tedy říká, že jestli se použije metoda potomka nebo předka se určí až za běhu.
Závěr
Pro dnešek toho bylo dost, pokračování až zase někdy jindy. Příště dokončíme tu základní teorii a pomalu, ale jistě, se začneme chystat na skutečně programování.
Úkol
A zde jsou dnešní úkoly:
- klasika – to, že se máte pokusit vše pochopit, není nic nového, takže hurá do toho
- Dnešní programovací úkol bude hodně volný, zpracování nechám zcela na vás, zde jsou základní instrukce: vytvořte si novou libovolnou třídu a další třídu, která bude jejím potomkem. Vytvořte 2 instance jedné z těchto tříd, na které jakkoliv demonstrujete, jak jste pochopili zapouzdření a na potomkovi si nějak pohrajte s dědičností. Jak třídy pojmenujete, co budou všechno obsahovat (upozorňuji, že „NIC“ nepovažuji za obsah) už je jen a jen vaše starost, tak si hrajte (prosím nešetřete komentáři, abych případně pochopil, co má program dělat).
- Protože se během několika dílů dostaneme ke komponentám, bylo by vhodné, kdybychom si jejich užití mohli demonstrovat na nějakém programu. Rád bych, abychom většinu komponent mohli nacpat do jedné velké aplikace, kterou bychom postupně vylepšovali. Váš úkol je tedy jasný – napište mi buď na mail, ICQ, nebo do úkolů návrh na program, který byste chtěli vytvářet. Ale musí se jednat o program, na kterém lze demonstrovat větší množství komponent! Co se mi bude líbit nejvíc, to vyberu.