5.11.2002
|
čo sa budeme dnes učiť
- základné pravidlá pri
definovaní a jednoduchom používaní
objektov
- zoznámime sa s pojmami trieda,
inštancia, metóda, konštruktor,
dedičnosť, ...
Úvod do Objektovo Orientovaného Programovania
Pripomeňme si, aké pojmy sme zaviedli v 2. prednáške pri definovaní
korytnačky:
Objekt korytnačka
V doterajších príkladoch sme zadefinovali a používali buď celočíselné
premenné (integer) alebo premenné typu Korytnačka. Tieto korytnačkové
premenné sa líšia od "obyčajných" premenných tým, že
- si pamätajú svoj momentálny stav v svojich tzv. stavových premenných
(napr. pozícia, farba, ...)
- majú svoje súkromné príkazy, pomocou ktorých ich nejako riadime,
resp. meníme ich stavové premenné - takýmto príkazom - sú to procedúry -
hovoríme metódy a "rozumejú" im len korytnačky (premenné typu TKor)
- musia byť vytvorené (nie deklarované) špeciálnym spôsobom ( TKor.Create(...); ) a kým sa takto nevytvoria,
nesmú sa vôbec používať
- takýmto premenným hovoríme OBJEKT a typom, z ktorých vytvárame
objekty (napr. TKor) hovoríme TRIEDA (po anglicky object a
class); niekedy sa objektu hovorí aj inštancia triedy
- okrem korytnačiek sme sa už stretli aj s inými objektmi, napr. Form1,
Image1, Button1 aj premenná g, ktorá bola inštanciou triedy TCanvas
- zatiaľ si o objektoch treba zapamätať, že sú to premenné, ktoré môžu v sebe
obsahovať veľa stavových premenných a tiež "v sebe" obsahujú nejaké svoje
procedúry (metódy) => tomuto hovoríme zapuzdrenie (enkapsulácia), lebo
v jednom "puzdre" sú aj údaje (stavové premenné) aj algoritmy (metódy), ktoré
vedia s týmito údajmi pracovať.
- Z doterajších skúseností vieme, že s objektom korytnačka sa pracuje veľmi
podobne ako s obyčajným záznamom (record). Pozrime sa na tieto deklarácie
(definujeme miesto - bod v grafickej ploche):
porovnanie deklarácií záznamu
a tiredy:
|
type
zMiesto = record
x,y:integer;
end;
var
zm:zMiesto;
|
type
TMiesto = class
x,y:integer;
end;
var
om:TMiesto;
|
|
- druhá definícia deklaruje triedu (a teda premenná om je objekt) a pre
používateľa pritom vznikajú tieto rozdiely:
- premenná zm typu zMiesto už po svojom zadeklarovaní existuje a
zatiaľ má nedefinovanú hodnotu, môžeme do nej (do jej položiek) už priradzovať
(napr. zm.x:=10;)
- premenná om typu TMiesto, t.j. inštancia triedy
TMiesto, zatiaľ ešte nie je vytvorená a preto s ňou
nesmieme pracovať, musíme ju najprv vytvoriť (skonštruovať) a až potom s ňou
môžeme pracovať, napr. priradiť hodnoty do jej položiek (stavových premenných)
- objekty sa vytvárajú pomocou takéhoto zápisu:
inštancia :=
trieda.Create; alebo inštancia :=
trieda.Create(parametre); napr. musíme zapísať
m:=TMiesto.Create;
- s položkami záznamu aj objektu pracujeme úplne rovnako ... (napr. môžeme
použiť with)
- zvykneme po skončení práce s objektom tento zrušiť pomocou Free,
napr. om.Free; (je to podobné uzatvoreniu súboru pomocou CloseFile)
- Ak zadefinujeme TMiesto ako triedu namiesto záznamu získavame
silný nástroj - objektovo orientované programovanie. Ukážeme novú vlastnosť,
ktorú poskytujú iba objekty.
Dedenie
- z hotovej triedy môžeme vytvoriť ďalšie odvodené triedy tak, že Delphám
(jazyku pascal) oznámime, že chceme ponechať všetko, čo už bolo zadefinované
doteraz a pridáme nejaké nové položky (stavové premenné):
deklarovanie odvodenej triedy:
|
type
TMiesto = class
x,y:integer;
end;
TBod = class(TMiesto)
vid:boolean;
end;
var
m:TMiesto;
b:TBod;
|
- trieda TBod je vytvorená tak, že má všetky vlastnosti triedy
TMiesto a pridala si novú stavovú premennú vid, t.j. táto trieda
má 3 stavové premenné; nové pojmy:
- trieda TBod vznikla ako potomok triedy TMiesto
(descendent type [di'sendent])
- trieda TMiesto je predok triedy TBod (ancestor
type [aenseste])
- trieda TBod zdedila všetky vlastnosti triedy
TMiesto (inherit)
- každá trieda môže mať ľubovoľný počet potomkov, ale len jediného predka
- potomkovia môžu dodefinovať nové stavové premenné, ale môžu aj predefinovať
už zdedené
príklad demonštruje použitie inštancií týchto tried:
|
type
TMiesto = class
x,y:integer;
end;
TBod = class(TMiesto)
vid:boolean;
end;
var
m:TMiesto;
b:TBod
begin
m:=TMiesto.Create;
b:=TBod.Create;
m.x:=100; m.y:=150;
with b do begin
x:=200; y:=m.y;
end;
m.Free;
b.Free;
end;
|
Metódy objektu
- sú to procedúry alebo funkcie, ktoré sa deklarujú vo vnútri objektu a sú s
ním pevne spojené
- slúžia napr. na modifikovanie stavových premenných, zabezpečujú "správanie"
objektu, ...
- zadefinujeme procedúru na nastavenie hodnôt stavových premenných:
definovanie triedy:
|
procedure TMiesto.ZmenXY(NoveX,NoveY:integer);
begin
x:=NoveX; y:=NoveY;
end;
|
- aby túto definíciu pascal správne pochopil, t.j. že patrí k triede TMiesto,
musí byť jej hlavička zadefinovaná aj v definícii triedy:
deklarovanie metódy:
|
type
TMiesto = class
x,y:integer;
procedure ZmenXY(NoveX,NoveY:integer);
end;
|
- tieto dve definície metódy musia byť identické
- vo vnútri metódy sa pracuje so stavovými premennými momentálnej triedy ako
keby bola obklopená neviditeľným príkazom with
- nakoľko trieda TBod je potomkom triedy TMiesto, tak zdedila aj metódy triedy
TMiesto a preto aj inštancie triedy TBod môžu používať tieto metódy:
dedenie metódy:
|
var
b:TBod;
begin
b:=TBod.Create;
b.ZmenXY(200,150); // volanie metódy
...
|
- potomok môže prekryť metódu svojho (hociktorého) predka (môže hoci aj
zmeniť typ a počet parametrov), napr.
prekrytá metóda:
|
type
TBod = class(TMiesto)
vid:boolean;
procedure ZmenXY(NoveX,NoveY:integer; NovyVid:boolean);
end;
procedure TBod.ZmenXY(NoveX,NoveY:integer; NovyVid:boolean);
begin
x:=NoveX; y:=NoveY; vid:=NovyVid;
end;
|
- alebo môžeme využiť pri definovaní prekrývajúcej metódy aj zdedenú metódu,
napr.
zdedenie metódy pri prekrytí:
|
procedure TBod.ZmenXY(NoveX,NoveY:integer; NovyVid:boolean);
begin
inherited ZmenXY(NoveX,NoveY);
vid:=NovyVid;
end;
|
Nasledujúci príklad ukáže
použitie objektov - kružníc:
- do formulára umiestnime grafickú plochu (Image1) a 5 tlačidiel (Button1 až
Button5), ktorým budeme postupne priraďovať nejaké akcie
- zadefinujeme triedu TKruh, ktorá popisuje kružnicu v grafickej ploche
(x,y,r,farba,vid) s metódami na vytvorenie kruhu, vykreslenie, ukrytie, zmenu
farby a posun obrázku
- procedúra, ktorá spracováva udalosť OnFormCreate (TForm1.FormCreate) priradí
do globálnej premennej g:=Image1.Canvas
- ďalej zadefinujeme 3 globálne premenné typu TKruh
- na prvé tlačidlo sa niekde v ploche vytvorí kružnica nejakej veľkosti a
farby
- podobne aj na 2. a 3. tlačidlo ďalšie kružnice
príklad s kružnicami:
|
var
g:TCanvas;
{ trieda TKruh }
type
TKruh = class // potomok TObject
x,y,r:integer;
f:TColor;
vid:boolean;
constructor Create(xx,yy,rr:integer);
procedure ukaz;
procedure skry;
procedure ZmenFarbu(ff:TColor);
procedure posun(dx,dy:integer);
end;
constructor TKruh.Create(xx,yy,rr:integer);
begin
// inherited Create;
x:=xx; y:=yy; r:=rr; vid:=false; f:=clBlack;
end;
procedure TKruh.ukaz;
begin
g.Pen.Color:=f;
g.Brush.Style:=bsClear;
g.Ellipse(x-r,y-r,x+r,y+r);
vid:=true;
end;
procedure TKruh.skry;
begin
g.Pen.Color:=clWhite;
g.Brush.Style:=bsClear;
g.Ellipse(x-r,y-r,x+r,y+r);
vid:=false;
end;
procedure TKruh.ZmenFarbu(ff:TColor);
begin
f:=ff;
if vid then ukaz;
end;
procedure TKruh.posun(dx,dy:integer);
var
b:boolean;
begin
b:=vid; if vid then skry;
inc(x,dx); inc(y,dy);
if b then ukaz;
end;
//////////////////////////////
var
a,b,c:TKruh;
procedure TForm1.FormCreate(Sender: TObject);
begin
g:=Image1.Canvas;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
a:=TKruh.Create(100,100,50);
with a do begin ZmenFarbu(clGreen); ukaz; end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
b:=TKruh.Create(300,200,100); b.ZmenFarbu(clRed); b.ukaz;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
c:=TKruh.Create(200,250,70); c.ukaz;
end;
|
- v tomto príklade sme predefinovali aj "konštruovanie" objektu (Create)
Konštruktor objektu
- je to špeciálna procedúra, ktorá sa používa iba pre vytvorenie novej
inštancie
- jej hlavnou úlohou je vyhradiť v počítači miesto pre samotný objekt a do
premennej (inštancie) priradiť referenciu na toto miesto
- okrem toho môže táto procedúra (konštruktor), napr. aj inicializovať obsahy
stavových premenných, prípadne robiť iné akcie
- namiesto procedure musíme deklarovať slovom constructor
- všimnite si, že konštruktor sme používali aj v predchádzajúcom príklade o
triede TMiesto (m:=TMiesto.Create;) a pritom sme ho nikde
nedeklarovali ani nedefinovali - platí jedna dôležitá vec: každá trieda, ktorej
neuvedieme predka, je odvodená (je potomkom) základnej triedy TObject -
tento objekt má definovaný konštruktor Create, ktorý má prázdne telo,
t.j.
platí:
|
constructor TObject.Create;
begin
end;
|
- a preto nemá zmysel v nami definovanom konštruktore triedy, ktorá nemá
predka, písať
inherited Create; - namiesto TKruh = class by sme mohli deklarovať aj
Tkruh = class(TObject) - bolo by to to isté
- konštruktorov môže byť definovaných
aj viac - musia sa líšiť menom, napr.
druhý konštruktor:
|
type
TKruh = class
...
constructor Create(xx,yy,rr:integer);
constructor Create1(xx,yy,rr:integer);
...
end;
constructor TKruh.Create(xx,yy,rr:integer);
begin
x:=xx; y:=yy; r:=rr; vid:=false; f:=clBlack;
end;
constructor TKruh.Create1(xx,yy,rr:integer);
begin
Create(xx,yy,rr);
Ukaz;
end;
|
- zadefinovali sme druhý konštruktor Create1,
ktorý sa líši od prvého tým,
že objekt po skonštruovaní hneď zobrazí
- zrejme, keď je definovaných viac konštruktorov, pri
vytváraní novej inštancie sa musíme
rozhodnúť, ktorý z konštruktorov použijeme
(vždy môžeme použiť len jeden!)
Pokračujeme v rozrobenom projekte:
- štvrté tlačidlo rozhýbe kruhy tak, že každý sa paralelne pohne nejakým
smerom 20 krokov
- použijeme na to časovač (Timer1- nastavíme mu v inšpektore Interval na 100 a
Enabled na False):
posúvanie kruhov:
|
var
poc:integer;
procedure TForm1.Button4Click(Sender: TObject);
begin
poc:=20;
Timer1.Enabled:=true;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
a.posun(3,2);
b.posun(-5,-1);
c.posun(2,-3);
dec(poc);
if poc<=0 then Timer1.Enabled:=false;
end;
|
- lenže ak by sme stlačili toto tlačidlo predtým, ako sme vyrobili všetky
kruhy, program nám spadne na už známej chybe "Acces violation at address...",
preto musíme zabezpečiť, že posúvať budeme len ten kruh, ktorý už bol naozaj
vyrobený. Ako zistíme, či bola inštancia už vytvorená (Create)?
- v skutočnosti v premennej a nie je priamo obsah objektu (stavové
premenné, tak ako by to bolo v zázname), ale referencia na nejaké miesto
v pamäti počítača, kde sa naozaj tento objekt nachádza
- kým nie je objekt vytvorený (skonštruovaný), tak v premennej a nie je
žiadna referencia, čo sa v pascale označuje slovom nil a teda
testovanie na nil:
|
procedure TForm1.Timer1Timer(Sender: TObject);
begin
if a<>nil then a.posun(3,2);
if b<>nil then b.posun(-5,-1);
if c<>nil then c.posun(2,-3);
dec(poc);
if poc<=0 then Timer1.Enabled:=false;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
g:=Image1.Canvas;
a:=nil;
b:=nil;
c:=nil;
end;
|
- je dobré si zvyknúť hneď na začiatok programu (napr. do FormCreate) dať
priradenia
a:=nil; b:=nil; ...
- dohodli sme sa, že každý objekt musíme po skončení uvoľniť pomocou
Free - v našom príklade to urobíme v udalosti OnFormDestroy:
záverečné zrušenie inštancií:
|
procedure TForm1.FormDestroy(Sender: TObject);
begin
a.Free; // if a<>nil then a.Destroy;
b.Free;
c.Free;
end;
|
- piate tlačidlo uvoľní všetky inštancie (a, b, c):
korektné zrušenie inštancie:
|
procedure TForm1.Button5Click(Sender: TObject);
begin
if a<>nil then
with a do begin
if vid then skry;
Free;
a:=nil; // aby a nereferencovalo na už nedefinovaný objekt
end;
if b<>nil then
with b do begin
if vid then skry;
Free; b:=nil;
end;
if c<>nil then
with c do begin
if vid then skry;
Free; c:=nil;
end;
end;
|
- dvojicu príkazov a.Free; a:=nil;
môžeme nahradiť volaním štandardnej
procedúry FreeAndNil(a);
Vlastnosti objektov:
- enkapsulácia (encapsulation - zapuzdrenie)
- nový dátový typ trieda (class)
- spojenie typu record a procedúry/funkcie pre manipuláciu so stavovými
premennými
- dedičnosť (inheritance)
- od definovaného objektu môžeme odvodiť celú hierarchiu objektov
- t.j. potomkov, ktorí dedia prístup k dátovým aj programovým zložkám
- polymorfizmus
- zdieľanie akcií v hierarchii objektov
- budeme sa učiť až neskôr
NDÚ:
- začnite študovať súbor KorUnit.pas -- už by ste dosť veľa z toho mohli
rozumieť
- zadefinujte triedu pre veľké čísla, napr.:
trieda veľké čísla:
|
const
max=100000;
type
TVelkeCislo = class
c:array[1..max] of byte;
p:0..max; // počet platných cifier
constructor Create;
procedure prirad(x:longint);
procedure pricitaj(x:longint);
procedure nasob(x:longint);
procedure vypis;
end;
|
- aby sa dal, napr. vypočítať veľký faktoriál, napr.
použitie veľkých čísel:
|
var
f:TVelkeCislo;
begin
f:=TVelkeCislo.Create;
f.prirad(1);
for i:=2 to N do f.nasob(i);
f.vypis;
end;
|
|