Živě.cz o počítačích a internetu

Tipy a triky v Delphi, díl 161. - vylepšujeme vlastní komponenty

Jan Šindelář 3.11.2004

Po čase se opět vracíme k tématu tvorby komponent, které jsme tak trochu nakousli před časem. Povíme si něco o konstruktorech a destruktorech.

Pokud čtete seriál pravidelně, jistě si vzpomenete na tento díl seriálu, kdy jsme si ukazovali na velmi jednoduchém příkladu základní postup při tvorbě nové komponenty. Článek měl sloužit spíše začínajícím uživatelům, aby poznali, že tvorba komponent není žádná velká věda (alespoň většinou) a od "běžného" programování se liší jen v určitých aspektech zápisu kvůli následné znovupoužitelnosti kódu.

Pro uvedení do obrazu bude lépe si článek přečíst, pokud jste jej dosud nečetli, a nebo alespoň pro oživení v rychlosti prolétnout. Shrnuto se jednalo o tvorbu jednoduché grafické komponenty (potomek TGraphicControl) v podobě čáry.

Dnes jsou na řadě konstruktory a destruktory, které jsou vždy součástí každého objektu a sami poznáte, jak užitečné jsou. Mojí snahou nebude vysvětlovat do detailu principy objektově orientovaného programování, o tom náš seriál není a proto prosím odborníky o pochopení jistého zjednodušení ve výkladu, které je na tomto místě nutné. Snahou tedy bude vysvětlit věc jen do takové míry, do jaké zasahuje do naší problematiky komponent na této počáteční úrovni.

Ale zpět ke konstruktorům. Konstruktor (či lépe constructor) je zvláštní druh procedury, která je volána při vzniku daného objektu (v našem případě komponenty). Ať už je daná komponenta umístěna na formulář během návrhu aplikace nebo vytvářena až dynamicky za běhu aplikace, vždy musí dojít k volání konstruktoru. Konstruktor se obvykle podle běžných zažitých konvencí pojmenovává Create, ale může se jmenovat i jinak. A k čemu tedy přesně slouží, resp. co do takové procedury vlastně umístit? Každý vzniknuvší objekt si alokuje jisté systémové zdroje. Ať už sám o sobě a nebo vy sami používáte řadu proměnných, polí a dalších prvků, který je nutné nejprve inicializovat (alokovat, nastavit počáteční hodnoty, otevřít soubory...). Všechny tyto důležité části je nutné umístit právě do konstruktoru, protože je to velmi elegantní řešení. Zajistíme si tím totiž to, že veškeré alokace a nastavení proběhnou vždy při vzniku objektu a nemusíme na ně myslet později. Eliminuje se tím řada chyb a výjimek.

V hierarchii Delphi nejvyšší objekt typu TObject má svůj konstruktor s názvem Create. Všechny další objekty pak tento konstruktor dědí, takže vlastně není objektu bez konstruktoru. Pokud jste alespoň občas zvyklí vytvářet objekty (třeba vizuální komponenty) dynamicky za běhu aplikace, asi budete používat například následující způsob:

...
var MojeTlacitko: TButton;
...

MojeTlacitko := TButton.Create;

Tím dojde k vytvoření objektu, v tomto případě tlačítka. A právě tak často používané volání Create není nic jiného, než volání konstruktoru. Pokud bychom toto volání vynechali a rovnou se pokusili pracovat s objektem MojeTlacitko, dojde k chybě, navzdory tomu, že objekt je deklarován.

Jestliže tedy vytváříme jednoduchou komponentu jako v našem předchozím článku, nemusíme se prakticky o konstruktor starat, protože nic zvláštního inicializovat nemusíme a samotná existence konstruktoru je zajištěna zděděním od nadřízeného objektu TObject. Častěji však nějaké to počáteční nastavení a alokace provést musíme a v tom případě si u našeho objektu musíme původní "prázdný" konstruktor upravit podle sebe. To uděláme tímto způsobem:

type
  TNaseTlacitko = class(TButton)
  public
    { Public declarations }
    constructor Create;
  end;

implementation

constructor TNaseTlacitko.Create;
begin
  inherited;
  {SEM PRIJDE NASE INICIALIZACNI CAST}
end;

Jak je vidět, postup je to poměrně jednoduchý. Za klíčové slovo inherited, jehož význam si vysvětlíme za chvilku, pak můžeme napsat všechny naše potřebné alokace paměti a další nastavení.

Magické slůvko inherited ("zděděný") je velmi důležité. Již jsme si řekli, že nadřazený TObject obsahuje konstruktor Create, který pak dědí další objekty. V tomto konstruktoru, který vlastně nevidíme pokud se nepodíváme do zdrojového kódu knihoven Delphi, může být (a taky že je) řada užitečných a klíčových základních nastavení. Pokud si vytvoříme pro náš objekt vlastní konstruktor, bude volán právě ten náš a nadřazený bude "zahozen". To by ale byla chyba, přijít o zmiňované důležité alokace z nadřazeného objektu TObject. Museli bychom je napsat do našeho konstruktoru sami znovu, což by bylo dost nepraktické. Proto použijeme klíčové slovo inherited, kterým zajistíme zdědění všeho potřebného z nadřazeného objektu a do našeho konstruktoru pak už napíšeme jen to, co potřebuje my sami navíc přidat.

A jak pracuje destruktor? Jistě už sami tušíte. Je volán při rušení objektu a obsahuje všechno důležité pro uvolnění alokovaných zdrojů (uvolnění paměti, uzavření souborů a podobně). To, co na začátku constructor vytvořil, musí zase destructor uklidit. Velmi logické a pochopitelné. Vypadat pak může třeba takto:

type
  TNaseTlacitko = class(TButton)
  public
    destructor Destroy; override;
  end;

implementation

destructor TNaseTlacitko.Destroy;
begin
  {SEM PRIJDE NASE UKLIDOVA CAST}
  inherited;
end;

Místo Create zde máme příznačné Destroy. Všímavějším z vás určitě neunikl ani další jemný rozdíl a tím je zajímavé klíčové slovo override u deklarace destruktoru. Prozatím se spokojte s tím, že tam prostě být musí a o jeho významu jakož i o dalších věcech, které se týkají tématu, si povíme zase někdy příště.