IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

7. diel - Dedičnosť a polymorfizmus

V minulej lekcii, Java - Aréna s bojovníkmi, sme dokončili našu arénu, simulujúcu zápas dvoch bojovníkov.

Dnes si opäť rozšírime znalosti o objektovo orientovanom programovaní. V úvodnom tutoriále do OOP sme si hovorili, že OOP stojí na troch základných pilieroch: zapuzdrenie, dedičnosti a polymorfizmu. Zapúzdrenie a používanie modifikátora private nám je už dobre známe. Dnes sa pozrieme na zvyšné dva piliere.

Dedičnosť

Dedičnosť je jedna zo základných vlastností OOP a slúži na tvorenie nových dátových štruktúr na základe starých. Vysvetlime si to na jednoduchom príklade.

Budeme programovať informačný systém. To je celkom reálny príklad, aby sme si však učenie spríjemnili, bude to informačný systém pre správu zvierat v ZOO :) Náš systém budú používať dva typy užívateľov: používateľ a administrátor. Užívateľ je bežný ošetrovateľ zvierat, ktorý bude môcť upravovať informácie o zvieratách, napr. ich váhu alebo rozpätie krídiel. Administrátor bude môcť tiež upravovať údaje o zvieratách a navyše zvieratá pridávať a mazať z databázy. Z atribútov bude mať navyše telefónne číslo, aby ho bolo možné kontaktovať v prípade výpadku systému. Bolo by určite zbytočné a neprehľadné, keby sme si museli definovať obe triedy úplne celé, pretože mnoho vlastností týchto 2 objektov je spoločných. Užívateľ aj administrátor budú mať určite meno, vek a budú sa môcť prihlásiť a odhlásiť. Nadefinujeme si teda iba triedu User (nepôjde o funkčnú ukážku, dnes to bude len teória, programovať budeme nabudúce):

public class User {
    private String name;
    private String password;
    private int age;

    public boolean logIn(String password) {
        // ...
    }

    public boolean logOut() {
        // ...
    }

    public void setWeight(Animal animal) {
        // ...
    }

    // ...

}

Triedu som len naznačil, ale iste si ju dokážeme dobre predstaviť. Bez znalosti dedičnosti by sme triedu Admin definovali asi takto:

public class Admin {
    private String name;
    private String password;
    private int age;
    private String telNumber;

    public boolean logIn(String password) {
        // ...
    }

    public boolean logOut() {
        // ...
    }

    public void setWeight(Animal animal) {
        // ...
    }

    public void addAnimal(Animal animal) {
        // ...
    }

    public void removeAnimal(Animal animal) {
        // ...
    }

    // ...

}

Vidíme, že máme v triede množstvo redundantného (duplikovaného) kódu. Akékoľvek zmeny musíme teraz vykonávať v oboch triedach, kód sa nám veľmi komplikuje. Teraz použijeme dedičnosť, definujeme teda triedu Admin tak, aby z triedy User dedila. Atribúty a metódy užívateľa teda už nemusíme znovu definovať, Java nám ich do triedy sama dodá:

public class Admin extends User {
    private String telNumber;

    public void addAnimal(Animal animal) {
        // ...
    }

    public void removeAnimal(Animal animal) {
        // ...
    }

    // ...
}

Vidíme, že na zdedenie sme použili kľúčové slovo extends. V anglickej literatúre nájdete dedičnosť pod slovom inheritance.

V príklade vyššie nebudú v potomkovi prístupné privátne atribúty, ale iba atribúty a metódy s modifikátorom public. Atribúty private a metódy sú chápané ako špeciálna logika konkrétnej triedy, ktorá je potomkovi utajená, aj keď ju vlastne používa, nemôže ju meniť. Aby sme dosiahli požadovaný výsledok, použijeme nový modifikátor prístupu protected.

Modifikátor protected sprístupní atribút alebo metódu buď ľubovoľným potomkom v akomkoľvek balíčku alebo ľubovoľným triedam v tom istom balíčku. Potomok sa teda už k atribútu dostane. Ako problém môže byť, že atribút je zvonku viditeľný, preto sa v súbore .java s triedou často používa iné meno balíčka, než v ktorom sú ostatné časti programu. Tento nový balíček majú spoločný len súvisiace triedy (napr. dedičnosťou), z balíčka v ktorom je hlavný program potom protected atribúty prístupné nebudú.

Ak by sme chceli atribúty alebo metódy sprístupniť iba triede samotnej a triedam v rovnakom balíčku, neuvedieme pred ne žiadny modifikátor prístupu (nazývaný tiež ako package private).

Začiatok triedy User by teda vyzeral takto:

public class User {
    protected String name;
    protected String password;
    protected int age;

    // ...

Keď si teraz vytvoríme inštancie užívateľa a administrátora, obaja budú mať napr. atribút name a metódu logIn(). Java triedu User zdedí a doplní nám automaticky všetky jej atribúty.

Výhody dedenia sú jasné, nemusíme opisovať obom triedam tie isté atribúty, ale stačí dopísať len to, v čom sa líšia. Zvyšok sa zdedí. Prínos je obrovský, môžeme rozširovať existujúce komponenty o nové metódy a tým ich znovu využívať. Nemusíme písať množstvo redundantného (duplikovaného) kódu. A hlavne - keď zmeníme jediný atribút v materskej triede, automaticky sa táto zmena všade zdedí. Nedôjde teda k tomu, že by sme to museli meniť ručne v 20 triedach a niekde na to zabudli a spôsobili chybu. Sme ľudia a chybovať budeme vždy, musíme teda používať také programátorské postupy, aby sme mali možnosť chybovať čo najmenej.

O materskej triede sa niekedy hovorí ako o predkovi (tu User) ao triede, ktorá z nej dedí, ako o potomkovi (tu Admin). Potomok môže pridávať nové metódy alebo si prispôsobovať metódy z materskej triedy (pozri ďalej). Môžete sa stretnúť aj s pojmami nadtrieda a podtrieda.

Ďalšou možnosťou, ako objektový model navrhnúť, by bolo zaviesť materskú triedu User, ktorá by slúžila iba na dedenie. Z triedy User by potom dedili triedy Attendant a z neho Admin. To by sa však vyplatilo pri väčšom počte typov užívateľov. V takom prípade hovoríme o hierarchii tried, budeme sa tým zaoberať ku koncu tejto sekcie. Náš príklad bol jednoduchý a preto nám stačili iba 2 triedy. Existujú tzv. návrhové vzory, ktoré obsahujú osvedčené schémy objektových štruktúr pre známe prípady použitia. Záujemcovia ich nájdu popísané v sekcii Návrhové vzory, je to však už pokročilejšia problematika a tiež veľmi zaujímavá. V objektovom modelovaní sa dedičnosť znázorňuje graficky ako prázdna šípka smerujúca k predkovi. V našom prípade by grafická notácia vyzerala takto:

Dedičnosť objektov – grafická notácia - Objektovo orientované programovanie v Jave

Dátový typ pri dedičnosti

Obrovskou výhodou dedičnosti je, že keď si vytvoríme premennú s dátovým typom materskej triedy, môžeme do nej bez problémov ukladať aj jej potomkov. Je to dané tým, že potomok obsahuje všetko, čo obsahuje materská trieda, spĺňa teda "požiadavky" (presnejšie obsahuje rozhranie) dátového typu. A k tomu má oproti materskej triede niečo navyše. Môžeme si teda urobiť pole typu User a v ňom mať ako používateľov, tak administrátorov. S premennou to teda funguje takto:

User user = new User("John Newman", 33);
Admin admin = new Admin("Jack White", 25);
// now we assign the administrator to the variable of the User type:
user = admin;
// It's fine since the User class is its parent class
// If we try in conversely, we'll cause an error:
admin = user;

V Jave je mnoho konštrukcií, ako operovať s typmi inštancií pri dedičnosti. Podrobne sa na ne pozrieme počas seriálu, teraz si ukážme len to, ako môžeme overiť typ inštancie v premennej:

User user = new Admin("Jack White", 25);
if (user instanceof Admin) {
    System.out.println("The user given has been identified as an administrator");
} else {
    System.out.println("The user given is not an administrator");
}

Pomocou operátora instanceof sa môžeme opýtať, či je objekt daného typu. Kód vyššie otestuje, či je v premennej user užívateľ alebo jeho potomok administrátor.

Jazyky, ktoré dedičnosť podporujú, buď vedia dedičnosť jednoduchú, kde trieda dedí len z jednej triedy, alebo viacnásobnú, kde trieda dedí hneď z niekoľkých tried naraz. Viacnásobná dedičnosť sa v praxi príliš neosvedčila, časom si povieme prečo a ukážeme si aj ako ju obísť. Java podporuje iba jednoduchú dedičnosť, s viacnásobnou dedičnosťou sa môžete stretnúť napr. v C++.

Polymorfizmus

Nenechajte sa vystrašiť príšerným názvom tejto techniky, pretože je v jadre veľmi jednoduchá. Polymorfizmus umožňuje používať jednotné rozhranie na prácu s rôznymi typmi objektov. Majme napríklad mnoho objektov, ktoré reprezentujú nejaké geometrické útvary (kruh, štvorec, trojuholník). Bolo by určite prínosné a prehľadné, keby sme s nimi mohli komunikovať jednotne, hoci sa líšia. Môžeme zaviesť triedu GeometricShape, ktorá by obsahovala atribút color a metódu render(). Všetky geometrické tvary by potom dedili z tejto triedy jej interface (rozhranie). Objekty kruh a štvorec sa ale iste vykresľujú inak. Polymorfizmus nám umožňuje prepísať si metódu render() u každej podtriedy tak, aby robila, čo chceme. Rozhranie tak zostane zachované a my nebudeme musieť premýšľať, ako sa to pri onom objekte volá.

Polymorfizmus býva často vysvetľovaný na obrázku so zvieratami, ktoré majú všetky v rozhraní metódu speak(), ale každé si ju vykonáva po svojom:

Polymorfizmus - Objektovo orientované programovanie v Jave

Podstatou polymorfizmu je teda metóda alebo metódy, ktoré majú všetci potomkovia definované s rovnakou hlavičkou, ale iným telom.

Modifikátory prístupu

Pre prehľadnosť si ešte všetky modifikátory krátko popíšeme:

  • public - Ľubovoľné triedy môžu pristupovať k tejto metóde alebo atribútu, teda aj z iných balíčkov.
  • private - Prístup má iba daná trieda, žiadna iná nemôže.
  • protected - Prístup má daná trieda a potomkovia triedy.
  • Žiadny modifikátor - Prístup majú všetky triedy v rovnakom balíčku (napr. package com.ictdemy). Teda podobné ako modifikátor public, ale obmedzený na konkrétny balíček :)

Vyššie uvedené modifikátory si zhrnieme v tabuľke:

Modifikátor Daná trieda Rovnaký balíček Potomkovia triedy (odvodené triedy) Svet (všetko ostatné)
public
protected
bez modifikátora
private

Polymorfizmus si spolu s dedičnosťou vyskúšame v budúcej lekcii, Aréna s mágom (dedičnosť a polymorfizmus), na bojovníkoch v našej aréne. Pridáme mága, ktorý si bude metódu attack() vykonávať po svojom pomocou many, ale inak zdedí správanie a atribúty bojovníka. Zvonku teda vôbec nepoznáme, že to nie je bojovník, pretože bude mať rovnaké rozhranie. Bude to zábava :)


 

Predchádzajúci článok
Java - Aréna s bojovníkmi
Všetky články v sekcii
Objektovo orientované programovanie v Jave
Preskočiť článok
(neodporúčame)
Aréna s mágom (dedičnosť a polymorfizmus)
Článok pre vás napísal David Hartinger
Avatar
Užívateľské hodnotenie:
26 hlasov
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David sa informačné technológie naučil na Unicorn University - prestížnej súkromnej vysokej škole IT a ekonómie.
Aktivity