Poznáváme C# a Microsoft .NET – 5. díl

Po dědičnosti a řízení verzí s tímto dílem přichází další problematika spojená s objektově orientovaným přístupem k vývoji aplikací. Jedná se o statické členy s jejichž použitím se v tomto díle seznámíme. Také si vysvětlíme pojem polymorfizmus, který je také s objektovým programováním silně spjat.

Statické členy tříd

V minulých dílech, ve kterých jsme se zaobírali modelováním tříd, jsme se dozvěděli, že třídy mohou mít atributy a operace. Zatím jsme v našich příkladech používali pouze členy, které mohli být využity až po vytvoření instance třídy – objektu. Tyto členy jsou nazývány instanční a jsou tedy svázány až s konkrétním objektem. Existují však ještě členy třídy, které využijeme v případě, když potřebujeme, aby nebyly spojeny s nějakou konkrétní instancí dané třídy, ale s třídou jako takovou. Takovéto členy označujeme jako členy statické. K těmto členům potom přistupujeme nikoliv prostřednictvím jména instance, ale prostřednictvím jména třídy.

Statické atributy

Nejjednodušším druhem statického člena je statický atribut třídy. Statický atribut se v jazyku C# deklaruje klíčovým slovem static. Školáckým příkladem využití statického atributu třídy je atribut pro ukládání počtu vytvořených instancí třídy. Zdrojový kód by mohl vypadat takto:

using System;
namespace PrikladyZive5
{
 public class NaseTrida
 {
  private static int pocetInstanci;
  //definice vlastnosti, která zpristupnuje atribut pocetInstanci
  public static int PocetInstanci
  {
   get{return pocetInstanci;}
  }
  public NaseTrida()
  {
   //pri kazdem vytvareni noveho objektu podle nasi tridy se
   // hodnota atributu zvysi
pocetInstanci++;
  }
 }
}

Poznámka: Výraz pocetInstanci++ , zařídí zvýšení hodnoty proměnné pocetInstanci o jedna. Je to stejné jako kdybychom napsali : pocetInstanci = pocetInstanci + 1; Analogicky k tomu by výraz pocetInstanci-- zařídil to samé jako výraz pocetInstanci = pocetInstanci – 1;

Jak jsem zmínil výše, k statickým členům přistupujeme prostřednictvím jména třídy. Takže přečtení atributu počtu instancí, uložení této hodnoty do proměnné a její vypsání na obrazovku bysme zapsali takto:

public class Zive5TestApp
 {
  public static void Main(string[] args)
  {     
   NaseTrida nase1 = new NaseTrida();
   NaseTrida nase2 = new NaseTrida();
   int pocet = NaseTrida.PocetInstanci;
   Console.WriteLine(pocet);
   Console.ReadLine();
  }
 }

Statické metody

Stejně jako atributy můžeme i metody pomocí klíčového slova static označit jako statické. Použití statickým metod s sebou nese jedno nezanedbatelné omezení a tím je, že tyto metody nemohou být virtuální. Pokud bychom se statickou metodu pokusili nadefinovat jako virtuální, kompilátor zahlásí chybu. Zápis naší ukázkové třídy obohacené o statickou metodu by vypadal takto:

using System;
namespace PrikladyZive5
{
 public class NaseTrida
 {
  private static int pocetInstanci;
  public static int PocetInstanci
  {
   get{return pocetInstanci;}
  }
  public NaseTrida()
  {
   pocetInstanci++;
  }
  public static string VypisPozdrav()
  {
   return "Ahoj";
  }
 }
}

Následné vyvolání statické metody a uložení její návratové hodnoty do proměnné by se zapsalo tímto způsobem :

string textpozdravu = NaseTrida.VypisPozdrav();

Statické konstruktory

Tak jako mohou existovat i jiné druhy statických členů, mohou existovat i statické konstruktory. Statické konstruktory se využívají ke spuštění inicializačních operací pracujícími se statickými členy. Tyto konstruktory jsou volány před vytvořením první instance třídy nebo před prvním použitím jiného statického členu.

Zdrojový kód naší ukázkové třídy se statickým konstruktorem:

public class NaseTrida
 {
  private static int pocetInstanci;
  private static string jmeno;
  public static string Jmeno
  {
   get{return jmeno;}
  }
  //staticky konstruktor
static NaseTrida()
  {
   jmeno = "Petr";
  }
  …

Pokud se pokusíme přečíst hodnotu atributu jméno, aniž bychom ho předtím nastavovali, dostaneme hodnotu Petr. To je zapříčiněno právě tím, že před prvním přístupem k němu je zavolán statický konstruktor.

Polymorfismus

Polymorfismus, neboli mnohotvárnost je spolu se znovuvyužitelností kódu nejdůležitější aspekt v objektově orientovaném programování. Polymorfismus znamená možnost použít mnoho druhů typů se stejným rozhraním (veřejně přístupnými operacemi a atributy) bez ohledu na detaily jejich implementace. Polymorfismus tedy zajišuje to, že nám stačí znát rozhraní bázové třídy a použitím tohoto rozhraní volat operace a atributy, které jsou různě naimplementovány v odvozených třídách. Z tohoto tvrzení plyne, že polymorfismus je svázán s použitím dědičnosti.

Použití polymorfizmu si demonstrujeme na jednoduchém příkladu z obrazci. Nadefinujeme si třídu Obrazec, která bude implementovat virtuální metodu pro vykreslení. Třída Obrazec bude sloužit jako bázová pro třídy Čtverec, Obdélník a Kruh. Tyto třídy budou překrývat metodu Vykresli svoji odpovídající verzí a navíc implementují metodu DejObsah. Diagram příkladu s použitím známé notace UML je realizován takto:

Implementace těchto tříd není ničím významným zajímavá, protože využívá stejné principy jako minulý příklad se zaměstnanci. Zajímavější je ovšem spustitelná třída, která je využívá.

V ní totiž využijeme výhody polymorfismu a to tak, že deklarujeme referenční proměnnou ( = proměnná pro referenční typ) pro objekt typu Obrazec, která bude odkazovat na objekt typu Kruh.

Obrazec obrazecPromenna = new Kruh();

Kompilátor neohlásí chybu, že se snažíme referenční proměnnou odkazovat na objekt jiného typu, protože v objektově orientovaném programování platí, že potomek může nahradit předka, což se v tomto případě děje. Kruh tedy může nahradit obrazec, nebo je jeho potomkem.

Polymorfismus zajistí, že pokud zavoláme metodu Vykresli, bude zavolána její verze implementovaná ve třídě Kruh a ne implementace ve třídě Obrazec jak by jistě mnozí čekali.

Analogické chování bychom zaznamenali, pokud by tato proměnná odkazovala na objekt typu Obdélník nebo Čtverec. Takže pokud spustíme tento kód:

Obrazec obrazecPromenna = new Kruh();
Console.WriteLine(obrazecPromenna.Vykresli());

..výstup bude „Kruh“.

Tento příklad demonstruje základní myšlenku polymorfismu a to tím, že každý typ obrazce umí provést operaci Vykresli(), ale každý z nich ji činí jinak – má jinou implementaci metody.

Následující obrázek zjednodušeně zobrazuje, jak tato situace vypadá v paměti.

To, že jsme nechali referenční proměnnou předka odkazovat na objekt potomka se v terminologii označuje jako upcasting. Jinými slovy se automaticky provedlo takzvané implicitní přetypování, které zapříčiní, že budeme moci na nově vzniklé instanci volat pouze členy tvořící rozhraní předka. To znamená,že členy, které jsou na odvozené třídě navíc (například metoda DejObsah), by v tomto případě nebyly k dispozici. To, které členy jsou k dispozici totiž závisí na typu referenční proměnné.

Pokud bychom chtěli na této instanci volat všechny členy její třídy (v našem případě kruhu), museli bychom provést downcasting a to pomocí explicitního přetypování. Při explicitním přetypování musíme, na rozdíl od přetypování implicitního, uvést na jaký typ chceme objekt převést. Zápis by byl následující.

Kruh kruhPromenna = (Kruh)obrazecPromenna;

Po tomto kroku bude možno na instanci zavolat i metodu DejObsah. Ovšem pozor, pokud bychom se pokoušeli takto přetypovat instanci, která není typu Kruh, běh programu by skončil chybou.

Uvedeného volání členů přes rozhraní jejich společného předka je hojně využíváno za účelem odstínění klienta využívajícího našich knihoven od skutečné implementace operací na potomcích. Jinak řečeno klienta vůbec nemusí zajímat, která konkrétní implementace metody se zavolá, protože díky polymorfismu bude použita ta správná.

Kompletní zdrojové kódy příkladů, si můžete stáhnout zde.

V příštím díle se budeme zaobírat abstraktními třídami a objasníme si princip použití rozhraní.

Diskuze (98) Další článek: Microsoft nabízí zdarma 2000 licencí antiviru NOD32

Témata článku: , , , , , , , , , , , ,