19.Prednaska

Z Pascal
Prejsť na: navigácia, hľadanie
19. Prednáška

úlohy | cvičenie


Statické metódy

Trieda TKruznica



Na 14. prednáške - úvod do objektového programovania - sme vytvorili projekt, v ktorom sme zadefinovali triedu TKruznica. Mierne túto triedu pozmeníme a zadefinujeme ju v samostatnej jednotke, napr. KruznicaUnit.pas:

unit KruznicaUnit;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, Graphics;
 
type
 
  { TKruznica }
 
  TKruznica = class
  private
    X, Y, R: Integer;
    Farba: TColor;
    C: TCanvas;           // grafická plocha
  public
    constructor Create(CC: TCanvas; XX, YY, RR: Integer);
    procedure Kresli(F: TColor);
    procedure Posun(DX, DY: Integer);
    procedure ZmenFarbu(F: TColor);
  end;
 
implementation
 
{ TKruznica }
 
constructor TKruznica.Create(CC: TCanvas; XX, YY, RR: Integer);
begin
  X := XX;
  Y := YY;
  R := RR;
  C := CC;
  Farba := clBlack;
end;
 
procedure TKruznica.Kresli(F: TColor);
begin
  C.Pen.Color := F;
  C.Brush.Style := bsClear;
  C.Ellipse(X - R, Y - R, X + R, Y + R);
end;
 
procedure TKruznica.Posun(DX, DY: Integer);
begin
  Kresli(clWhite);       // zmaže kružnicu na pôvodnom mieste
  Inc(X, DX);
  Inc(Y, DY);
  Kresli(Farba);
end;
 
procedure TKruznica.ZmenFarbu(F: TColor);
begin
  Farba := F;
  Kresli(Farba);
end;
 
end.

Poznámky:

  • grafickú plochu (presnejšie plátno grafickej plochy, t.j. TCanvas) posielame objektu pri jeho vytváraní, objekt si ju zapamätá vo svojej súkromnej stavovej premennej C a ďalej do plochy kreslí prostredníctvom tejto premennej;
  • stavové premenné X, Y, R, ... sme zadeklarovali ako privátne, t.j. k týmto premenným sa nedostane nik mimo tohto unitu (KruznicaUnit).

Do formulára sme na tri tlačidlá priradili vytvorenie troch inštancií triedy TKruznica. Štvrté tlačidlo bude tieto kruhy pohybovať (naštartuje, resp. zastaví časovač) a piate tlačidlo zruší všetky vytvorené inštancie TKruznica. Keďže na objekty sa odvolávame v rôznych procedúrach (Button1Click, Button2Click, ...), pamätáme si ich v globálnych premenných A, B, C (tieto sú automaticky inicializované na nil):

uses
  KruznicaUnit;
 
var
  A: TKruznica;
  B: TKruznica;
  C: TKruznica;
 
{ TForm1 }
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  Image1.Canvas.FillRect(Image1.ClientRect);
  Timer1.Enabled := False;
  Timer1.Interval := 100;
end;
 
procedure TForm1.Button1Click(Sender: TObject);
begin
  A := TKruznica.Create(Image1.Canvas, 200, 100, 50);
  A.ZmenFarbu(clRed);
end;
 
procedure TForm1.Button2Click(Sender: TObject);
begin
  B := TKruznica.Create(Image1.Canvas, 300, 200, 100);
  B.ZmenFarbu(clBlue);
end;
 
procedure TForm1.Button3Click(Sender: TObject);
begin
  C := TKruznica.Create(Image1.Canvas, 200, 250, 70);
  C.ZmenFarbu(clGreen);
end;
 
procedure TForm1.Button4Click(Sender: TObject);
begin
  Timer1.Enabled := not Timer1.Enabled;
end;
 
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  // Image1.Canvas.Brush.Style := bsSolid;
  // Image1.Canvas.FillRect(Image1.ClientRect);
  if A <> nil then
    A.Posun(1, 3);
  if B <> nil then
    B.Posun(-2, -1);
  if C <> nil then
    C.Posun(1, -2);
end;
 
procedure TForm1.Button5Click(Sender: TObject);
begin
  if A <> nil then
  begin
    A.Kresli(clWhite);
    A.Free;
    A := nil;
  end;
  if B <> nil then
  begin
    B.Kresli(clWhite);
    B.Free;
    B := nil;
  end;
  if C <> nil then
  begin
    C.Kresli(clWhite);
    C.Free;
    C := nil;
  end;
end;

Takto vytvorený projekt s novou triedou TKruznica pracuje korektne.



Trieda TStvorec



Keďže v objektovom programovaní sme sa už zoznámili aj s dedičnosťou, dodefinujeme novú triedu TStvorec ako podtriedu (potomka triedy) TKruznica. Všimnite si, že v tejto prvej verzii triedy TStvorec sme sa spoľahli len na zdedenie stavových premenných a konštruktora a všetky metódy sme zadefinovali znovu (skopírovali sme ich definície). Túto definíciu sme vytvorili v novom unite StvorecUnit:

unit StvorecUnit;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, Graphics, KruznicaUnit;
 
type
 
  { TStvorec }
 
  TStvorec = class(TKruznica)
    procedure Kresli(F: TColor);
    procedure Posun(DX, DY: Integer);
    procedure ZmenFarbu(F: TColor);
  end;
 
implementation
 
{ TStvorec }
 
procedure TStvorec.Kresli(F: TColor);
begin
  C.Pen.Color := F;
  C.Brush.Style := bsClear;
  C.Rectangle(X - R, Y - R, X + R, Y + R);
end;
 
procedure TStvorec.Posun(DX, DY: Integer);
begin
  Kresli(clWhite);
  Inc(X, DX);
  Inc(Y, DY);
  Kresli(Farba);
end;
 
procedure TStvorec.ZmenFarbu(F: TColor);
begin
  Farba := F;
  Kresli(Farba);
end;
 
end.

Nakoľko sme všetky stavové premenné v triede TKruznica definovali ako private, ani definícia triedy TStvorec ich nebude vidieť a preto sa tento unit nepodarí ani skompilovať. Tu nám pomôže iný špecifikátor namiesto private. V Pascale máme k dispozícii tieto špecifikátory pre definovanie viditeľnosti stavových premenných a metód v triede:

  • private - identifikátor je viditeľný len v unite, v ktorom je definovaná trieda; takýto identifikátor sa môže používať nielen vo vnútri metód triedy, ale aj v iných podprogramoch, resp. metódach iných tried.
  • public - identifikátor je viditeľný všade, kde je viditeľná táto trieda
  • protected - má rovnaké pravidlá viditeľnosti okrem prípadu, keď je viditeľný aj v metódach odvodených typov - tieto môžu byť definované aj v iných unitoch.

Musíme teda opraviť aj špecifikátor v definícii triedy TKruznica:

type
 
  { TKruznica }
 
  TKruznica = class
  protected
    X, Y, R: Integer;
    Farba: TColor;
    C: TCanvas;           // grafická plocha
  public
    constructor Create(CC: TCanvas; XX, YY, RR: Integer);
  ...

Ďalej jednu z premenných - inštancií kruhu v testovacom programe teraz prerobíme na štvorec:

uses
  KruznicaUnit;
 
var
  A: TKruznica;
  B: TStvorec;
  C: TKruznica;
 
procedure TForm1.Button2Click(Sender: TObject);
begin
  B := TStvorec.Create(Image1.Canvas, 300, 200, 100);
  B.ZmenFarbu(clBlue);
end;

Takto pozmenený projekt funguje správne: druhé tlačidlo vytvorí štvorec a štvrté tlačidlo rozhýbe všetky útvary, teda hýbe sa aj štvorec.



Nefungujúci TStvorec



Ak porovnáme metódy ZmenFarbu a Posun v oboch triedach TKruznica aj TStvorec, zistíme, že sú úplne identické. Preto v triede TStvorec vyskúšajme vyhodiť jednu z nich Posun:

unit StvorecUnit;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, Graphics, KruznicaUnit;
 
type
 
  { TStvorec }
 
  TStvorec = class(TKruznica)
    procedure Kresli(F: TColor);
    // procedure Posun(DX, DY: Integer);
    procedure ZmenFarbu(F: TColor);
  end;
 
implementation
 
{ TStvorec }
 
procedure TStvorec.Kresli(F: TColor);
begin
  C.Pen.Color := F;
  C.Brush.Style := bsClear;
  C.Rectangle(X - R, Y - R, X + R, Y + R);
end;
 
{procedure TStvorec.Posun(DX, DY: Integer);
begin
  Kresli(clWhite);
  Inc(X, DX);
  Inc(Y, DY);
  Kresli(Farba);
end;} 
 
procedure TStvorec.ZmenFarbu(F: TColor);
begin
  Farba := F;
  Kresli(Farba);
end;
 
end.

Pre Pascal to znamená, že definícia tejto metódy sa zdedí z triedy TKruznica. Lenže dedenie neznamená, že sa v novej triede automaticky vytvorí jej kópia, ale volanie TStvorec.Posun v skutočnosti spôsobí volanie TKruznica.Posun. Po spustení programu môžete vidieť, že projekt prestal správne fungovať: zatlačenie druhého tlačidla správne vytvorí modrý štvorec, ale hýbanie tohto štvorca v časovači pomocou metódy posun už spraví nezmysel. Štvorec by sa mal posúvať tak, že sa najprv zmaže (prekreslí bielou farbou), potom sa zmenia súradnice a znovu sa nakreslí, teraz už modrou farbou. Lenže štvorec sa teraz maže prekreslením bielej kružnice a po zmene súradníc sa nakreslí ako modrá kružnica.

Aby sme pochopili, čo sa udialo a ako tento problém správne vyriešiť, musíme vysvetliť mechanizmus statických a virtuálnych metód.



Statické metódy



To, čo sa nám teraz stalo, vzniklo z toho dôvodu, že všetky doterajšie nami vytvorené metódy sú volané mechanizmom pre statické metódy. Ak zavoláme TStvorec.Posun, (t.j. inštancii triedy TStvorec zavoláme metódu Posun), vďaka dedičnosti sa zavolá metóda TKruznica.Posun. Táto v sebe obsahuje dve volania TKruznica.Kresli. A toto je ten problém: v prípade že metódu TKruznica.Posun zavolá štvorec a nie kruh, potrebujeme aby sa nevolala metóda TKruznica.Kresli, ale namiesto nej TStvorec.Kresli.

Schematicky to môžeme zapísať takto: volanie TStvorec.Posun sa prepne na TKruznica.Posun a toto zavolá TKruznica.Kresli, hoci my by sme očakávali volanie TStvorec.Kresli:

TKruznica.Posun
TKruznica.Kresli
   
TStvorec.Posun
 
TStvorec.Kresli


Virtuálne metódy

Objektové programovanie poskytuje nový mechanizmus volania metód, tzv. mechanizmus virtuálnych metód. Tento mechanizmus zabezpečí, že niektoré metódy budú mať významné postavenie a kompilátor pascalu sa pri nich bude správne rozhodovať, ktorú verziu metódy bude v skutočnosti volať. Napr. ak nejaká kružnica zavolá svoju metódu Posun, tak táto bude volať metódu TKruznica.Kresli, pričom ak túto istú metódu zavolá (vďaka dedičnosti) nejaký štvorec, tak počas nej sa už bude volať TStvorec.Kresli. Tou špeciálnou metódou je v tomto prípade metóda Kresli: každý potomok TKruznica ju môže mať vo svojej verzii a mechanizmus bude podľa konkrétnej inštancie volať vždy tú správnu verziu. Takýmto špeciálnym metódam hovoríme virtuálne metódy, napr. v našom príklade musí byť virtuálnou metódou Kresli. Schematicky to zakreslíme takto:

TKruznica.Posun
 
TKruznica.Kresli
20px link=http://pascal.input.sk/images/b/b7/P19.1.png  
TStvorec.Posun
 
TStvorec.Kresli

Toto rozpoznanie správnej verzie metódy sa zrejme nedá urobiť počas kompilácie - trieda TKruznica sa predsa nachádza v samostatnej programovej jednotke a v čase, keď sa prekladá, kompilátor ešte nevie o ďalších potenciálnych potomkoch (odvodených triedach).



Polymorfizmus



Polymorfizmus teda znamená, že metóda sa bude zdieľať v rôznych stupňoch objektovej hierarchie. Napr. vtedy, ak rôzne triedy zdedia "spoločnú" metódu (napr. metódu Posun), ale detaily pre rôzne objekty zodpovedajú ich zvláštnostiam (napr. metóda Kresli).



Včasná a neskorá väzba



Pri preklade projektu sa pri každom volaní podprogramu (metódy) musí kompilátor vedieť rozhodnúť, aký kód bude generovať. Pri obyčajných metódach (statických), vie určiť presnú adresu podprogramu, hovoríme tomu "okamžité rozhodnutie".

Pri preklade volaní virtuálnych metód kompilátor zatiaľ nevie, ktorú metódu (ktorého objektu) tam bude treba naozaj volať. Preto tam vygeneruje taký kód, ktorý sa až počas samotného volania "opýta inštancie" (premennej typu objekt), kde sa nachádza jeho verzia tejto virtuálnej metódy. Hovoríme, že pri virtuálnych metódach je "odložené rozhodnutie".

Pri statických metódach kompilátor hľadá zodpovedajúce meno metódy v hierarchii smerom k predkom od danej úrovne (kde je definovaná metóda), t.j. ak metóda nebola definovaná na danej úrovni, hľadá sa najbližší predok, ktorý ju má zadefinovanú. Potom sa vygeneruje adresa tejto nájdenej metódy - preto tomu hovoríme včasná väzba (early binding).

Pri virtuálnych metódach kompilátor nehľadá nič, ale až počas behu sa hľadá zodpovedajúce meno v hierarchii smerom k predkom. T.j. pri volaní virtuálnej metódy sa v skutočnosti zavolá tá metóda, ktorá prislúcha inštancii a nie triede, kde je toto volanie (ak v TKruznica.Posun je volanie Kresli, nehľadá sa táto metóda v TKruznica, ale pozrie sa, kto túto TKruznica.Posun volal a u neho sa hľadá Kresli). Tomuto mechanizmu hovoríme neskorá väzba (late binding).



Virtuálne metódy



Metódu označíme, že je virtuálna tak, že v deklarácii triedy za deklaráciu metódy (procedure alebo function) zapíšeme slovo virtual. Potom, ak je v nejakej triede definovaná virtuálna metóda, tak aj všetci potomkovia by ju mali mať ako virtuálnu. Buď ju zdedia (znovu nedefinujú) alebo ju chcú prekryť svojou vlastnou verziou a vtedy zapíšu namiesto virtual slovo override. Samozrejme, že musia mať rovnako definované formálne parametre. Ak by sme virtuálnu metódu prekryli statickou, t.j. použili by sme rovnaké meno ale bez slova override, táto definícia sa bude chápať ako "predefinovanie" virtuálnej na statickú a mechanizmus polymorfizmu tu fungovať nebude.

Musíme teda opraviť triedu TKruznica:

type
 
  { TKruznica }
 
  TKruznica = class
  protected
    X, Y, R: Integer;
    Farba: TColor;
    C: TCanvas;           // grafická plocha
  public
    constructor Create(CC: TCanvas; XX, YY, RR: Integer);
    procedure Kresli(F: TColor); virtual;
    procedure Posun(DX, DY: Integer);
    procedure ZmenFarbu(F: TColor);
  end;

Aj triedu TStvorec, z ktorej vyhodíme aj metódu ZmenFarbu, lebo aj tú môžeme zdediť z TKruznica:

unit StvorecUnit;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, Graphics, KruznicaUnit;
 
type
 
  { TStvorec }
 
  TStvorec = class(TKruznica)
    procedure Kresli(F: TColor); override;
  end;
 
implementation
 
{ TStvorec }
 
procedure TStvorec.Kresli(F: TColor);
begin
  C.Pen.Color := F;
  C.Brush.Style := bsClear;
  C.Rectangle(X - R, Y - R, X + R, Y + R);
end;
 
end.

Ako to celé funguje: každá trieda má vytvorenú svoju vlastnú tabuľku všetkých virtuálnych metód, tzv. VMT (Virtual Method Table) - v tejto tabuľke sú adresy všetkých virtuálnych metód, napr. pre TKruznica aj pre TStvorec je tam adresa vlastnej verzie metódy Kresli. Keď sa vytvára nová inštancia pomocou konštruktora, tak vtedy sa na túto inštanciu napojí príslušná tabuľka - napr. inštancia typu TKruznica dostane referenciu na VMT triedy TKruznica, každá inštancia typu TStvorec takto dostane referenciu na VMT pre TStvorec. Táto referencia (adresa) na VMT je vždy prvá položka v zázname, teda všetky stavové premenné objektu nasledujú až za ňou. Hovoríme, že konštruktor môže byť použitý aj na inicializovanie stavových premenných, ale jeho hlavná funkcia je vyhradiť pamäť pre objekt (stavové premenné) a inicializovať referenciu na VMT.



Konštruktory a deštruktory



Vidíme, že konštruktory (a teda aj deštruktory) objektu majú špeciálne postavenie. Keďže sú zadefinované v triede TObject, ktorá je automatickým predkom všetkých tried (teda aj pre TKruznica), ak niekde tieto definície neprekryjeme novými, tak zrejme zdedíme tieto pôvodné. Konštruktor Create zvyčajne nie je virtuálny a preto k nemu nepíšeme ani virtual ani override - už sme videli, že pre Create môžeme zvoliť rôzne formálne parametre a toto by nebolo možné, keby to bola virtuálna metóda (tá by musela mať presne tak isto definované parametre ako u predka). Tiež už vieme, že konštruktorov môžeme mať definovaných aj viac s rôznymi paametrami alebo menami (nemusíme použiť slovo Create).

Na rozdiel od toho, deštruktor Destroy je virtuálna metóda bez parametrov a preto, ak ju definujeme, musíme uviesť slovo override. Deštruktor slúži na to, aby uvoľnil pamäť, ktorú zaberala daná inštancia, t.j. všetky stavové premenné. Objektová premenná je po zrušení pomocou deštruktora, napr. Objekt.Destroy alebo Objekt.Free, ďalej nepoužiteľná - nemali by sme volať jej žiadne metódy a ani pracovať s jej stavovými premennými. Väčšinou to spôsobí spadnutie programu.


Kompatibilita tried

Do objektovej premennej môžeme priradiť aj inštanciu inej triedy ako je naozaj zadeklarovaná. Pritom ale musí byť splnená podmienka kompatibility: odvodený typ dedí kompatibilitu so svojimi predkami, t.j. kompatibilita je s predkami a nie potomkami. Hovoríme, že kompatibilita je jednosmerná: do objektovej premennej môžeme priradiť nielen inštanciu tej istej triedy ale aj ľubovoľnej odvodenej triedy. Vo všeobecnosti platí, že každá objektová premenná môže obsahovať nielen premennú zadeklarovaného typu, ale aj ľubovoľný objekt, ktorý je potomkom tejto triedy. Táto kompatibilita je nielen medzi inštanciami, ale aj medzi formálnymi a skutočnými parametrami.

Zapamätajte si, že do objektovej premennej sa nedá priradiť inštancia triedy, ktorá je našim predkom.

Ak máme deklarácie:

var
  A: TKruznica;
  B: TStvorec;
  C: TKruznica;

potom je povolené:

     A := TKruznica.Create(...);
     B := TStvorec.Create(...);
     C := B;      // odteraz C referencuje tú istú inštanciu ako B
     C := TStvorec.Create(...);

A tiež ako parameter

procedure Zmen(Kruznica: TKruznica);
begin
  Kruznica.ZmenFarbu(clBlue);
  Kruznica.Posun(1, 1);
end;

Procedúra môže byť volaná aj s inštanciou triedy TStvorec:

  Zmen(B);

Takej situácii, keď napr. formálny parameter môže byť nielen typu TKruznica (ako je zadeklarovaný), ale aj ľubovoľného potomka TKruznica, hovoríme, že formálny parameter Kruznica je polymorfný objekt (t.j. rôznorodý). Všimnite si, že potomok môže kdekoľvek nahradiť svojho predka (predok môže zastupovať potomka).



Polymorfný objekt



Vidíme, že ako parameter procedúry môže prísť ľubovoľný potomok tohto objektu (zatiaľ nevieme zistiť ktorý). Toto umožňuje spracovať objekty, ktorých typ nie je známy. Tiež môžeme využiť metódy predkov so špecifickými zmenami - samozrejme, že to musíme riadne premyslieť. Ak dobre navrhneme základnú triedu, od ktorej budeme ďalej tvoriť celú hierarchiu tried, umožni nám to začať rozmýšľať nad algoritmami úplne inak - hovoríme tomu objektovo orientované programovanie (OOP).

Preto je dôležitý spôsob (metóda) návrhu hierarchie objektov. Najprv vytypujeme spoločné vlastnosti a metódy a z toho navrhneme bázovú triedu (typ). Rozhodneme, ktoré metódy budú virtuálne (virtual), t.j. budúcemu používateľovi umožníme rozširovanie metód. Môžeme vyrobiť aj "prázdne" virtuálne metódy - pre základnú triedu ešte nevieme zadefinovať funkčnosť, ale myslíme perspektívne rozširovanie odvodených tried.

Ak teda dobre zadefinujeme základné metódy a na ne naviazané virtuálne metódy (ako Posun, ktorý využíva Kresli), dovolí nám to potom veľmi elegantné vytváranie nových tried s relatívne malým programovaním (nemusíme definovať zložité metódy Posun a ZmenFarbu, stačí len nové Kresli). Hovoríme tomu rozšíriteľnosť (extensibility). Uvedomte si, že teraz vlastne meníme funkčnosť dávnejšie naprogramovaných metód (Posun a ZmenFarbu) bez toho, aby sme museli mať k dispozícii pôvodné zdrojové texty. Ak vieme ako, môžeme takto zmeniť aj funkčnosť niektorých podprogramov, ktoré sú v štandardných knižniciach Pascalu, bez toho aby sme zasahovali do ich kódu.

Ukážeme to na správnom riešení hierarchie geometrických útvarov. Najprv zadefinujeme základnú (bázovú triedu) TUtvar, od ktorej sa budú odvádzať

unit UtvarUnit;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, Graphics;
 
type
 
  { TUtvar }
 
  TUtvar = class(TObject)
  protected
    X, Y, R: Integer;
    Farba: TColor;
    C: TCanvas;
  public
    constructor Create(CC: TCanvas; XX, YY, RR: Integer);
    procedure Kresli(F: TColor); virtual;
    procedure ZmenFarbu(F: TColor);
    procedure Posun(Dx, Dy: Integer);
  end;
 
implementation
 
{ TUtvar }
 
constructor TUtvar.Create(CC: TCanvas; XX, YY, RR: Integer);
begin
  X := XX;
  Y := YY;
  R := RR;
  Farba := clBlack;
  C := CC;
end;
 
procedure TUtvar.Kresli(F: TColor);
begin
               // raise Exception.Create('Kresli nefunguje pre TUtvar');
end;
 
procedure TUtvar.ZmenFarbu(F: TColor);
begin
  Farba := F;
  Kresli(F);
end;
 
procedure TUtvar.Posun(Dx, Dy: Integer);
begin
  Kresli(clWhite);
  Inc(X, Dx);
  Inc(Y, Dy);
  Kresli(Farba);
end;
 
end.

A potom definície tried TKruznica a TStvorec budú vyzerať takto:

unit KruznicaUnit;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, Graphics, UtvarUnit;
 
type
 
  { TKruznica }
 
  TKruznica = class(TUtvar)
    procedure Kresli(F: TColor); override;
  end;
 
implementation
 
{ TKruznica }
 
procedure TKruznica.Kresli(F: TColor);
begin
  C.Brush.Style := bsClear;
  C.Pen.Color := F;
  C.Ellipse(X - R, Y - R, X + R, Y + R);
end;
 
end.
unit StvorecUnit;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, Graphics, UtvarUnit;
 
type
 
  { TStvorec }
 
  TStvorec = class(TUtvar)
    procedure Kresli(F: TColor); override;
  end;
 
implementation
 
{ TStvorec }
 
procedure TStvorec.Kresli(F: TColor);
begin
  C.Brush.Style := bsClear;
  C.Pen.Color := F;
  C.Rectangle(X - R, Y - R, X + R, Y + R);
end;
 
end.

A môžeme dodefinovať napr. aj útvar trojuholník (v samostatnom unite):

unit TrojuholnikUnit;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, Graphics, UtvarUnit;
 
type
 
  { TTrojuholnik }
 
  TTrojuholnik = class(TUtvar)
    procedure Kresli(F: TColor); override;
  end;
 
implementation
 
{ TTrojuholnik }
 
procedure TTrojuholnik.Kresli(F: TColor);
begin
  C.Brush.Style := bsClear;
  C.Pen.Color := F;
  C.Polyline([Point(X, Y - R), Point(X, Y + R), Point(X - R, Y), Point(X, Y - R)]);
end;
 
end.


Príklad s polymorfnými útvarmi by mohol vyzerať takto:

var
  A, B, C: TUtvar;
 
...
 
procedure TForm1.Button1Click(Sender: TObject);
begin
  A := TKruznica.Create(Image1.Canvas, 200, 100, 50);
  A.ZmenFarbu(clRed);
end;
 
procedure TForm1.Button2Click(Sender: TObject);
begin
  B := TStvorec.Create(Image1.Canvas, 300, 200, 100);
  B.ZmenFarbu(clBlue);
end;
 
procedure TForm1.Button3Click(Sender: TObject);
begin
  C := TTrojuholnik.Create(Image1.Canvas, 200, 250, 70);
  C.ZmenFarbu(clGreen);
end;
 
...

Hoci sú všetky tri premenné A, B a C typu TUtvar, priradili sme do nich inštancie triedy TKruznica, TStvorec a TTrojuholnik. Tieto inštancie o sebe "vedia", že nie je len útvar, ale kružnica, štvorec alebo trojuholník a preto všetky metódy, ktoré sa volajú, napr. ZmenFarbu a Posun, vedia že ich volala kružnica, resp. štvorec alebo trojuholník a nie útvar.


Polymorfné pole

Týmto mechanizmom môžeme zadefinovať aj pole objektov triedy TUtvar, ktoré bude v skutočnosti obsahovať nielen kruhy, ale aj štvorce a trojuholníky. Takémuto poľu, o prvkoch ktorého nevieme akého sú naozaj typu, hovoríme polymorfné pole.

uses
  UtvarUnit, KruznicaUnit, StvorecUnit, TrojuholnikUnit;
 
var
  Pole: array of TUtvar;     // polymorfné pole
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  Image1.Canvas.FillRect(Image1.ClientRect);
  Timer1.Enabled := False;
  Timer1.Interval := 100;
end;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  I, X, Y, R: Integer;
begin
  SetLength(Pole, Random(5) + 5);
  for I := 0 to High(Pole) do
  begin
    X := Random(Image1.Width);
    Y := Random(Image1.Height);
    R := Random(20) + 50;
    case Random(3) of
      0: Pole[I] := TKruznica.Create(X, Y, R);
      1: Pole[I] := TStvorec.Create(X, Y, R);
      2: Pole[I] := TTrojuholnik.Create(X, Y, R);
    end;
    Pole[I].ZmenFarbu(Random($1000000));
  end;
end;
 
procedure TForm1.Button4Click(Sender: TObject);
begin
  Timer1.Enabled := not Timer1.Enabled;
end;
 
procedure TForm1.Button5Click(Sender: TObject);
var
  I: Integer;
begin
  for I := 0 to High(Pole) do
  begin
    Pole[I].Kresli(clWhite);
    Pole[I].Free;
  end;
  Pole := nil;
end;
 
procedure TForm1.Timer1Timer(Sender: TObject);
var
  I: Integer;
begin
  for I := 0 to High(Pole) do
    Pole[I].Posun(I, 1 - I);
end;


Poznámky:

  • zatiaľ o prvkoch polymorfného poľa Pole nevieme zistiť, akého sú naozaj typu
  • vieme, že sú to inštancie tried TKruznica, TStvorec alebo TTrojuholnik
  • každý prvok "sám seba vie" správne vykresliť, zafarbiť a aj posunúť


späť | ďalej