OOP prakticky: Polymorfismus

…aneb sto objektů, sto metod!
24. 12. 2008

Konečně se dostane na metody zjednodušení nebo derivování. Metodu pro derivaci budou mít jistě všechny matematické výrazy. Pokud jste dobře četli předchozí kapitolu, tak víte, že pak definice takovéto metody patří do vrchní třídy Matematický Výraz. Ovšem naskýtá se jeden zajímavý problém. Přestože každý Matematický Výraz, se kterým my pracujeme, je derivovatelný, implementace derivace se pro každý výraz liší. Jinak se derivuje konstanta a jinak třeba součet. Potřebovali bychom tudíž mít v hlavní třídě Matematický Výraz definovanou metodu pro derivování, abychom ji mohli používat v každém Matematickém Výrazu nezávisle na jeho přesném typu a zároveň bychom potřebovali tuto metodu v každé třídě přepsat podle pravidel derivování té dané třídy. A přesně to nám umožňuje polymorfismus a přepisování metod.

Díky polymorfismu může mít každá třída ve stromu dědičnosti stejnou metodu s různou implementací. Pokud si vzpomenete na předchozí příklad s Geometrickými útvary, tak společná metoda by mohl být ten obsah. U Obdélníku i u Kružnice jsme schopni spočítat obsah, avšak pokaždé naprosto jiným způsobem. Další hezký příklad na polymorfismus je kopírování objektů. Každý objekt jistě může vytvořit svou kopii, avšak každý objekt bude tu kopii vytvářet jinak, protože každý objekt má (respektive může mít) jiné sloty/vlastnosti. Touto metodou tedy začneme, protože je nejnázornější a budeme ji využívat dále při derivování a zjednodušování. Nejprve si definujeme abstraktní metodu Kopie() v třídě Matematický Výraz. Abstraktní metoda značí, že každý objekt, který je potomkem, musí tuto metodu přepsat. Abstraktní metoda vlastně jen definuje hlavičku metody, vůbec se nezabývá implementací; tím se zabývají až třídy níže. Ono by to také bylo nelogické, kdyby se tím vrchní třída zabývala – jak má třída Matematický Výraz vědět, jak zkopírovat třeba Součet? Jdeme implementovat:

   1: abstract class MatematickyVyraz
   2: {
   3:     public abstract MatematickyVyraz Kopie();
   4: }

Kompilátor C# nám momentálně zahlásí mnoho chyb, protože ostatní třídy nerespektují nařízení své matičky a neimplementují metodu Kopie(). Takže ji jdeme přidat ostatním třídám. Při přepisování metod se v C# používá klíčové slovo override. S tím už jsme se setkali, když jsme přepisovali metodu ToString(). Tato metoda je definovaná úplně nejvýše a každičký objekt může tuto metodu přepsat. Zpět ke kopírování. Pokud chceme zkopírovat určitý objekt, vytvoříme instanci téhož objektu a zkopírujeme hodnoty z původního objektu do nového objektu. Metoda pro třídu Konstanta:

   1: public override MatematickyVyraz Kopie()
   2: {
   3:     return new Konstanta(Hodnota);
   4: }

Metoda pro třídu Proměnná:

   1: public override MatematickyVyraz Kopie()
   2: {
   3:     return new Promenna(Nazev);
   4: }

Metoda pro třídu Součet:

   1: public override MatematickyVyraz Kopie()
   2: {
   3:     return new Soucet(A.Kopie(), B.Kopie());
   4: }

Zde bych se trochu zastavil. Pokud chceme zkopírovat objekt součet, nestačí vytvořit nový objekt Součet a suše přiřadit A a B. Neprovedla by se totiž skutečná kopie, protože v novém i starém objektu by A a B odkazovalo na stejný objekt. Byly by to dvě různé třídy, které by se odkazovaly na stejné hodnoty A a B. Pokud bychom v jedné třídě změnili hodnotu A, změnila by se hodnota i ve druhé třídě. Metoda pro Rozdíl:

   1: public override MatematickyVyraz Kopie()
   2: {
   3:     return new Rozdil(A.Kopie(), B.Kopie());
   4: }

A teď si ukážeme to pravé kouzlo a tu největší sílu polymorfismu. Ve chvíli, kdy všechny naše třídy implementují metodu Kopie(), můžeme na libovolný objekt poslat metodu Kopie() a získáme kopii všech členů, které výraz obsahuje. Pokud máme výraz (3 + 10), zavolá se nejprve metoda Kopie() u Součtu. Ta vytvoří nový Součet a zavolá Kopii na své dvě hodnoty. Vrátí se tedy kopie trojky a desítky. Pokud bychom vzali složitější příklad, třeba (5 + (10 – 4)), tak se nejprve vytvoří nový součet a pak se zavolá kopie pětky a kopie rozdílu. A kopie rozdílu sama vytvoří nový Rozdíl a udělá kopii konstant 10 a 4. Není to kouzelné, jak díky polymorfismu jsme schopni prolézt celý strom objektů? Můžeme si to hned vyzkoušet:

   1: static void Main(string[] args)
   2: {
   3:     Konstanta a = new Konstanta(10);
   4:     Konstanta b = new Konstanta(20);
   5:  
   6:     Promenna x = new Promenna("x");
   7:     Promenna y = new Promenna("y");
   8:  
   9:     Soucet soucet1 = new Soucet(a, x);
  10:     Soucet soucet2 = new Soucet(b, y);
  11:  
  12:     Rozdil rozdil1 = new Rozdil(soucet1, soucet2);
  13:  
  14:     Console.WriteLine("Rozdíl1: {0}", rozdil1.ToString());
  15:  
  16:     MatematickyVyraz rozdil2 = rozdil1.Kopie();
  17:  
  18:     Console.WriteLine("Rozdíl2: {0}", rozdil2.ToString());
  19:  
  20:     rozdil1 = new Rozdil(a, b);
  21:  
  22:     Console.WriteLine("Rozdíl1: {0}", rozdil1.ToString());
  23:     Console.WriteLine("Rozdíl2: {0}", rozdil2.ToString());
  24:  
  25:     Console.Read();
  26:  
  27:     /*
  28:      * Výstup z programu:
  29:      * Rozdíl1: ((10 + x) - (20 + y))
  30:      * Rozdíl2: ((10 + x) - (20 + y))
  31:      * Rozdíl1: (10 - 20)
  32:      * Rozdíl2: ((10 + x) - (20 + y))
  33:     */
  34: }