2.3. Dědičnost

Na konci předchozí sekce jste řešili úlohu týkající se rozšíření směnárny o možnost platby poplatků za prodej. Tuto úlohu jste zřejmě řešili tak, že jste prostě upravili text původní třídy, možná jste ji i přejmenovali a uložili do jiného souboru. Nabízí se ovšem podstatně elegantnější metoda, která nám umožní na základě již existující třídy definovat třídu novou, a tou je dědičnost.

Dědičnost umožňuje využít již existující třídy a přidat k ní další proměnné nebo metody, případně některé metody předefinovat. Právě možnost předefinování metod nám dává do rukou velmi silný nástroj pro vývoj programů, pomocí něhož jsme schopni vytvářet různé implementace téže operace v závislosti na objektu, nad kterým je operace vyvolána.

Příklad 2.3. Řešený příklad
Definujte odvozenou třídu SmenarnaSPoplatkem reprezentující směnárnu s poplatky za prodej valut zákazníkovi. Výška poplatku je stanovena procentem z částky a je stanovena jeho minimální hodnota.

Tato třída SmenarnaSPoplatkem bude odvozena od původní třídy Smenarna - přibudou instanční proměnné pro minimální poplatek a velikost poplatku v procentech, změní se konstruktor a metoda prodej bude předefinovaná tak, aby zahrnula i výpočet poplatku.

class SmenarnaSPoplatkem extends Smenarna { 
   public SmenarnaSPoplatkem(double kurz,  
      double pMin, double pProc)  
   {  
      super(kurz); 
      this.pMin = pMin; 
      this.pProc = pProc; 
   } 
    
   private double pMin; 
   private double pProc; 
 
   public double prodej(double kolik)  
   {  
      double castkaBezPoplatku = kolik * this.vratKurz(); 
      double poplatek = castkaBezPoplatku * pProc / 100; 
      if( poplatek < pMin ) poplatek = pMin; 
      return castkaBezPoplatku + poplatek;  
   } 
 
   public static void main(String[] args) { 
      Smenarna smenarna = new SmenarnaSPoplatkem(30.0, 50, 2); 
      System.out.println("Vykup(10): "   + smenarna.vykup(10)); 
      System.out.println("Prodej(100): " + smenarna.prodej(100)); 
   } 
}

Třída SmenarnaSPoplatkem je odvozena od třídy Smenarna, jak je uvedeno za klíčovým slovem extends. Ovšem to neznamená, že by tato třída měla automaticky přístup ke všem soukromým proměnným a metodám původní třídy. Musí tedy hodnotu aktuálního kurzu zjišťovat voláním metody vratKurz(); pokud bychom chtěli odvozeným třídám přímý přístup umožnit, musíme místo klíčového slova private v deklaraci proměnné nebo metody použít protected.

Konstruktor odvozené třídy má kromě inicializace vlastních instančních proměnných ještě za úkol provést inicializaci spojenou s nadřazenou třídou, tedy zavolat také její konstruktor.

Volání konstruktoru nadřazené třídy se provádí pomocí klíčového slova super a musí být prvním příkazem těla konstruktoru. V případě, že konstruktor nadřazené třídy nezavoláme, zařadí se do programu jeho volání automaticky - v tom případě se volá tzv. implicitní konstruktor, tedy konstruktor bez parametrů.

Ještě si povšimněte toho, že po vytvoření instance třídy SmenarnaSPoplatkem operátorem new jsme referenci na vytvořený objekt uložili do proměnné typu Smenarna; takové přiřazení je zcela v pořádku, referenci na odvozenou třídu můžeme kdykoliv použít v místě, kde se očekává reference na třídu nadřazenou. Přiřazením reference do proměnné, která ale ukazuje na nadřazenou třídu, však přicházíme o možnost přístupu k proměnným a metodám, které má odvozená třída navíc (pokud neprovedeme přetypování zpět na původní typ).

2.3.1. Úlohy k řešení 2.3.

  1. Vytvořte třídu SmenarnaSBonusem, která při výkupu valut nad určitou částku nebude účtovat poplatek.
  2. Rozšiřte model jednoduchého objednávkového systému z předchozí kapitoly o faktury a dodací listy k realizovaným objednávkám. Pro definici základních typů dokladů využijte dědičnosti.