13. Úvod do OOP


Posledná zmena: 6.11.2002

Banner Text 5.11.2002

    čo sme sa doteraz naučili

    • už vieme pracovať s niektorými druhmi objektov, napr. s korytnačkami, bitmapmi a tiež s mnohými komponentmi, ktoré sú vlastne tiež objekty

    č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;


© 2002 AB, KVI
blaho@fmph.uniba.sk