![](img/tr.png)
- Jmenné prostory
- Třídy
- Dědičnost
- Struktury
- Rozhraní
Dnes si povíme to nejdůležitější o základních prvcích OOP (objektově orientovaném programování). Jsou to nutné základy, které je třeba znát pro další práci se skutečnými aplikacemi.
Třídy
Třída je seskupení proměnných libovolného datového typu. Komunikace probíhá přes její metody. V C# deklarujeme třídu takto:
class MyClass
{
public string str;
protected long lCislo;
private int iCislo;
public MyClass()
{
str = String.Empty;
lCislo = 0;
iCislo = 0;
}
public MyClass(string strValue, long lCisloValue, int iCisloValue)
{
str = strValue;
lCislo = lCisloValue;
iCislo = iCisloValue;
}
public int IncrementiCislo()
{
return iCislo++;
}
public long LCislo
{
get { return lCislo; }
set { lCislo = value; }
}
}
Nadefinovali jsem si třídu MyClass, která obsahuje tři členy (členské proměnné):
- Proměnnou str typu string. Jedná se o veřejný člen (public), takže je k ní všude přístup.
- Proměnnou lCislo typu long. Jedná se o chráněný člen (protected), takže k ní nemáme přístup. Přístup k ní je možný pouze přes odvozenou třídu (potomka).
- Proměnnou iCislo typu int. Jedná se o soukromý člen (private), takže k ní není žádný přístup.
Zopakujme si ještě jednou, co znamenají jednotlivé identifikátory (tato pravidla platí i pro identifikátory metod, vlastností apod.).
public – K proměnné je možné přistupovat zcela volně.
protected – K proměnné je možný přístup pouze „zevnitř“ třídy (v metodách třídy) nebo z odvozené třídy.
private – K proměnné je možný přístup pouze „zevnitř“ třídy.
Konstruktory jsou zvláštním druhem metod, které se spouští pouze při vytváření instance třídy. Konstruktory jsou odlišné od metod především tím, že nevrací vůbec nic. Když myslím nic, tak ani void.
V našem příkladu jsme si vytvořili dva konstruktory. První z nich nemá žádné argumenty a nastavuje proměnné na předem danou hodnotu. Druhý přijímá tři parametry, podle kterých přiřadí všem členům příslušné hodnoty. To, že můžeme definovat dvě stejně pojmenované metody vděčíme tzv. přetěžováním metod. Ve zkratce, dvě stejně pojmenované metody se stejnými návratovými hodnotami ale s různými parametry jsou přetížené – pro nás to znamená, že jsou odlišitelné.
Dvě stejně pojmenované metody se stejnými návratovými hodnotami ale s různými parametry jsou přetížené – pro nás to znamená, že jsou odlišitelné.
Dále jsme v našem příkladu nadefinovali metodu IncrementiCislo, která je bez parametrů a vrací hodnotu typu int. Obecně, metody mohou vracet jakýkoliv typ nebo nic (void).
Vlastnosti
V jazyce C# můžeme využívat vymoženosti nazývané vlastnosti. Jak vidíte v příkladu, vlastnost LCislo vrací číslo typu long nebo jinak hodnotu soukromého členu. Stává se vlastně jedinou možnou cestou, jak přímo komunikovat se soukromým členem. Vlastnosti jsou hodně podobné metodám až na to, že u vlastností můžeme mnohem jednodušeji určovat, co bude tzv. readonly.
Vlastnost má vždy jeden parametr stejného datového typu jako návratová hodnota. Tento parametr označíme v kódu vždy klíčovým slovem value. Samotný kód vlastnosti se skládá z bloku get nebo set nebo obojí (jak je to v našem příkladě). Blok set slouží ke zpracování parametru a blok get slouží k předání návratové hodnoty. Pokud bychom chtěli, aby vlastnost byla pouze ke čtení (readonly), jednoduše nenadefinujeme pouze blok get. Tu samou možnost máme i s blokem set.
Nejlepší bude, když si ukážeme použití vlastností v praxi:
//Vytvoříme si instanci třídy MyClass
MyClass mc = new MyClass("Ahoj", 123456, 22);
//Do proměnné cislo zkopírujeme hodnotu členu lCislo
long cislo = mc.LCislo;
//Členu lCislo přiřadíme novou hodnotu
mc.LCislo = 8888;
Jak vidíte, s vlastností se pracuje úplně stejně jako s proměnnou. Sám kompilátor rozhodne, kdy použije blok set nebo get.
Vlastnosti jako takové vznikly kvůli stále se opukajícímu se schématu metod jako GetValue() nebo SetValue(). Je také dobré vědět, že ve výsledném MSIL kódu jsou vlastnosti nahrazeny obyčejnými Get() nebo Set() metodami. Nám však vlastnosti ušetří práci a zvýší přehlednost kódu.
Dědičnost
Jedním ze základů OOP je dědičnost. Dá se to přirovnat k lidské dědičnosti, kdy jsme něco zdědili po svých rodičích (nemyslím tím majetek či peníze). Máme některé vlastnosti a rysy stejné jako rodiče. Podobně to funguje i v C#. Jako příklad si vytvoříme potomka naší třídy MyClass:
class MyBetterClass : MyClass
{
private string dalsiStr;
public MyBetterClass()
{
dalsiStr = String.Empty;
}
public MyBetterClass(string dalsiStrValue, string strValue, long lCisloValue, int iCisloValue)
: base(strValue, lCisloValue, iCisloValue)
{
dalsiStr = dalsiStrValue;
}
public string DalsiStr
{
get { return dalsiStr; }
}
}
Třída MyBetterClass je potomkem třídy MyClass. MyBetterClass získává všechny vlastnosti, metody a členy svého předka až na jeho soukromé členy. První bezparametrický konstruktor pouze nastaví proměnnou dalsiStr na výchozí hodnotu. Důležité je vědět, že v bezparametrickém konstruktoru potomka je automaticky volán bezparametrický konstruktor předka. V druhém konstruktoru již musíme ručně zavolat přetížený konstruktor předka a předat mu parametry.
Co se týče dědičnosti, existuje v .NET Frameworku jedno důležité omezení. Každá třída může být potomkem pouze jedné třídy. Neexistuje zde tedy vícenásobná dědičnost.
Každá třída může být potomkem pouze jedné třídy. Neexistuje zde tedy vícenásobná dědičnost.
Dědičnost můžeme využívat také přímo v kódu. Je-li třída MyBetterClass potomkem třídy MyClass, můžeme tedy komunikovat s třídou MyBetterClass stejně jako se třídou MyClass:
//Vytvoříme si instanci potomka
MyBetterClass mb = new MyBetterClass();
//instanci potomka přetypujeme na předka
long cislo = ((MyClass)mb).LCislo;
Je důležité si pamatovat, že můžeme přetypovat potomka na předka.
Když jsem výše napsal, že v .NET Frameworku můžete dědit pouze jednou, není to pravda doslova, protože, jak jistě víte, všechny třídy jsou odvozeny od třídy object.
Ovšem díky této skutečnosti můžeme jakýkoliv objekt přetypovat na třídu object.
Může se také stát, že v potomkovi budete chtít implementovat úplně stejnou metodu, jak je u předka, ale jinak. Jednou z možností je k metodě potomka přidat klíčové slovo new.
Statické metody, vlastnosti, proměnné
Statické metody označujeme klíčovým slovem static a jsou užitečné díky tomu, že je můžeme používat bez toho, aniž bychom vytvářeli instanci dané třídy. Existuje ale pravidlo, že ze statické metody nemůžeme volat obyčejnou metodu třídy, do které statická metoda patří. To samozřejmě platí i pro vlastnosti a členské proměnné.
Abstrakce
Dejme tomu, že máme třídu, která implementuje metodu GetInfo(). My ale chceme, aby její možný potomek mohl ale nemusel tuto metodu „přepsat“ – tedy změnit kód v jejím těle. Toho můžeme docílit tak, že před metodu GetInfo() vložíme klíčové slovo virtual. Tuto metodu pak můžeme normálně používat. Když ale vytvoříme potomka této třídy, můžeme implementovat stejnou metodu s klíčovým slovem override.
Někdy v naší praxi můžeme potřebovat deklarovat třídu „abstraktně“. Bavíme se tedy o abstraktních třídách. Třídu uděláme abstraktní, když k deklaraci třídy přidáme klíčové slovo abstract. V této třídě pak implementujeme metody, vlastnosti apod. Některé z těchto prvků třídy pak můžeme označit také slovem abstract. Abstraktní metody ale neimplementujeme, vůbec nepíšeme tělo funkce. Abstraktní třídu je totiž nutné nejdříve zdědit a v potomku přepsat (override) všechny abstraktní metody.
Zapečetěné třídy
Pokud nechcete, aby bylo možné nějakou třídu zdědit, přidejte do její deklarace klíčové slovo sealed. Neznámější zapečetěnou třídou je String. Vzhledem k tomu, že zapečetěnou třídu nelze odvozovat, nesmí obsahovat abstraktní metody. Ani virtuální metody nemají smysl.
Pišme deklaraci jedné třídy do více souborů
Klíčovým slovem partial v deklaraci třídy můžeme definovat třídu ve více souborech. Této skutečnosti využívá např. designer ve Visual Studiu 2005.
Struktury
Struktury jsou velmi podobné třídám. Deklarují se naprosto stejně, jen s rozdílem v klíčovém slovu class a struct. Také obsahují konstruktory a metody či vlastnosti, mohou implementovat rozhraní.
Velkým rozdílem mezi třídami a strukturami je to, že struktura je brána jako hodnotový typ (paměť je alokována na zásobníku). Struktury se tedy hodí pro drobné objekty, které nepotřebují mnoho paměti a jsou využívány ve velkém počtu.
Při rozhodování, zda použít strukturu nebo třídu, existuje pravidlo, že struktura by neměla být větší než 16 bajtů.
Při rozhodování, zda použít strukturu nebo třídu, existuje pravidlo, že struktura by neměla být větší než 16 bajtů.
Rozhraní
Dříve jsem se zmínil o tom, že v .NET Frameworku je omezený počet dědění. Existuje však zvláštní druh tříd, nazývané rozhraní, kterých můžu implementovat libovolný počet. Nadefinujme si tedy rozhraní:
interface ITvar
{
int Obsah
{
get;
}
int Obvod
{
get;
}
string VratNazevTvaru();
}
Rozhraní, reprezentující geometrické tvary, obsahuje vlastnosti Obvod, Obsah a metodu VratNazevTvaru. Říkáme tím, že každá třída, implementující toto rozhraní, musí nadefinovat výše zmíněné vlastnosti (pouze s blokem get, tedy readonly) a metodu. Jak jste si asi všimli, je takovou konvencí, že názvy rozhraní začínají velkým „I“. Zvyšuje se tím přehlednost.
Nadefinujme si tedy třídu, která toto rozhraní implementuje:
class Obdelnik : ITvar, IComparable
{
public int delka;
public int vyska;
public Obdelnik(int delkaValue, int vyskaValue)
{
delka = delkaValue;
vyska = vyskaValue;
}
public int Obsah
{
get { return delka * vyska; }
}
public int Obvod
{
get { return (delka+vyska)*2; }
}
public string VratNazevTvaru()
{
return "Obdelník";
}
public int CompareTo(object obj)
{
Obdelnik other = (Obdelnik)obj;
return Obsah.CompareTo(other.Obsah);
}
}
Naše třída tedy obsahuje všechny požadované vlastnosti a metodu. Mimo to ještě implementuje rozhraní IComparable, které požaduje metodu CompareTo, která slouží ke srovnávání objektů. Vidíte tedy, že můžeme implementovat libovolné množství rozhraní.
Dejme tomu, že chceme vytvořit metodu, která vypíše údaje o jakémkoliv objektu reprezentující geometrický tvar. Tady je kód:
public static void VypisUdaje(ITvar tvar)
{
if (tvar is ITvar)
Console.WriteLine(tvar.VratNazevTvaru() + ": obsah = " + tvar.Obsah.ToString());
else
throw new Exception("Objekt neimplemetuje rozhraní ITvar");
}
Metoda má jeden parametr typu ITvar. Znamená to tedy, že jako parametr přijme každý objekt implementující rozhraní ITvar. Nejprve si tuto skutečnost ověří v podmínce if, kde pomocí klíčového slova is ověří správnost parametru. V případě neúspěchu vyvoláme výjímku (které si popíšeme v dalších lekcích).
Díky rozhraní se tato metoda může spolehnout na to, že každý objekt implementuje její vlastnosti a metodu. Potom není problém s těmito objekty pracovat.
Obdelnik obd = new Obdelnik(12, 55);
VypisUdaje(obd);
Rozhraní má velké pole působnosti. Díky tomu si můžeme vytvářet tzv. standardizované třídy. Jejich užití můžeme vidět například v článku C# aplikace s podporou pluginů.
Udělejme si pořádek pomocí jmenných prostorů
Všechny třídy v .NET Frameworku (a že jich je opravdu hodně) jsou logicky rozděleny do skupin pomocí namespaců, neboli jmenných prostorů. Pokud chceme používat třídu z určitého namespacu, napíšeme klíčové slovo using a název jmenného prostoru.
Úkol číslo 2
Zadání
Vytvořte konzolovou aplikaci, kde nadefinujete třídu Clovek (jaké vlastnosti či metody vymyslíte, záleží jen na vás). Dále nadefinujete 3 různé třídy reprezentující nějakého zaměstnance (např. požárník, hasič apod.). Každá třída bude odvozena od třídy Clovek. I zde záleží jen na vás, jaké metody či vlastnosti si vymyslíte. Každá třída reprezentující zaměstnance bude také implementovat rozhraní (které také nadefinujete), pomocí kterého vypíšete údaje o každém zaměstnanci.
Nápověda
Tento úkol je rozsáhlejší než obvykle.I přesto napište všechen kód do jednoho souboru.
Třídy nemusí být rozsáhlé, měly by však být originální a hlavně by mělo být patrné, že dané problematice rozumíte. To ovšem platí pro každý úkol.