Programovanie

Dedičstvo verzus zloženie: Ako si vybrať

Dedičnosť a zloženie sú dve programovacie techniky, ktoré vývojári používajú na nadviazanie vzťahov medzi triedami a objektmi. Zatiaľ čo dedičstvo odvodzuje jednu triedu od druhej, kompozícia definuje triedu ako súčet jej častí.

Triedy a objekty vytvorené dedením sú Tesne spojené pretože zmena rodiča alebo nadtriedy v dedičskom vzťahu môže spôsobiť porušenie kódu. Triedy a objekty vytvorené kompozíciou sú voľne spojené, čo znamená, že môžete ľahšie meniť jednotlivé súčasti bez toho, aby ste porušili kód.

Pretože voľne spojený kód ponúka väčšiu flexibilitu, mnoho vývojárov sa dozvedelo, že kompozícia je lepšia technika ako dedičstvo, ale pravda je zložitejšia. Výber programovacieho nástroja je podobný výberu správneho kuchynského nástroja: Na krájanie zeleniny by ste nepoužili nôž na maslo a rovnako by ste nemali zvoliť zloženie pre každý scenár programovania.

V tomto Java Challenger sa naučíte rozdiel medzi dedičstvom a zložením a ako sa rozhodnúť, ktoré je pre váš program správne. Ďalej vám predstavím niekoľko dôležitých, ale náročných aspektov dedičstva Java: prepísanie metódy, Super kľúčové slovo a typ casting. Na záver otestujete, čo ste sa naučili postupovaním po príklade dedičstva po riadkoch, aby ste určili, aký by mal byť výstup.

Kedy použiť dedičstvo v Jave

V objektovo orientovanom programovaní môžeme dedičstvo použiť, keď vieme, že medzi dieťaťom a jeho nadradenou triedou existuje vzťah „je“. Niektoré príklady:

  • Osoba je a človek.
  • Mačka je zviera.
  • Auto je a vozidlo.

V každom prípade je dieťaťom alebo podtriedou a špecializovaný verzia nadradenej alebo nadtriedy. Dedenie zo nadtriedy je príkladom opätovného použitia kódu. Ak chcete lepšie pochopiť tento vzťah, venujte chvíľu štúdiu Auto triedy, ktorá dedí z Vozidlo:

 trieda Vozidlo {značka reťazca; Farba šnúrky; dvojitá váha; dvojnásobná rýchlosť; void move () {System.out.println ("Vozidlo sa pohybuje"); }} verejná trieda Auto rozširuje vozidlo {String licensePlateNumber; Majiteľ reťazca; Reťazec bodyStyle; public static void main (String ... inheritanceExample) {System.out.println (nové vozidlo (). značka); System.out.println (nová značka Car ().); new Car (). move (); }} 

Keď uvažujete o použití dedičstva, položte si otázku, či je podtrieda skutočne špecializovanejšou verziou nadtriedy. V takom prípade je auto typom vozidla, takže dedičský vzťah má zmysel.

Kedy použiť zloženie v Jave

V objektovo orientovanom programovaní môžeme použiť kompozíciu v prípadoch, keď jeden objekt „má“ (alebo je súčasťou) iný objekt. Niektoré príklady:

  • Auto batéria (batéria je súčasťou auto).
  • Osoba srdce (srdce je súčasťou osoba).
  • Dom obývacia izba (obývacia izba je súčasťou dom).

Pre lepšie pochopenie tohto typu vzťahu zvážte zloženie a House:

 verejná trieda CompositionExample {public static void main (String ... houseComposition) {new House (new Bedroom (), new LivingRoom ()); // Dom je teraz zložený zo spálne a LivingRoom} statickej triedy House {Bedroom bedroom; LivingRoom livingRoom; Dom (spálňa, LivingRoom livingRoom) {this.bedroom = spálňa; this.livingRoom = livingRoom; }} statická trieda Spálňa {} statická trieda LivingRoom {}} 

V takom prípade vieme, že dom má obývaciu izbu a spálňu, takže môžeme použiť dom Spálňa a Obývačka predmety v zložení a House

Získajte kód

Získajte zdrojový kód pre príklady v tomto Java Challenger. Podľa nasledujúcich príkladov môžete spustiť svoje vlastné testy.

Dedičstvo vs zloženie: dva príklady

Zvážte nasledujúci kód. Je to dobrý príklad dedičstva?

 import java.util.HashSet; verejná trieda CharacterBadExampleInheritance rozširuje HashSet {public static void main (Reťazec ... badExampleOfInheritance) {BadExampleInheritance badExampleInheritance = nový BadExampleInheritance (); badExampleInheritance.add ("Homer"); badExampleInheritance.forEach (System.out :: println); } 

V tomto prípade je odpoveď nie. Podradená trieda zdedí veľa metód, ktoré nikdy nepoužije, čoho výsledkom je úzko spojený kód, ktorý je neprehľadný a ťažko sa udržuje. Ak sa pozriete pozorne, je tiež zrejmé, že tento kód neprejde testom „je“.

Teraz skúsime ten istý príklad pomocou kompozície:

 import java.util.HashSet; import java.util.Set; verejná trieda CharacterCompositionExample {static Set set = new HashSet (); public static void main (String ... goodExampleOfComposition) {set.add ("Homer"); set.forEach (System.out :: println); } 

Použitie kompozície pre tento scenár umožňuje CharacterCompositionExample triedy používať iba dve z HashSetmetódy bez toho, aby som ich všetky zdedil. Výsledkom je jednoduchší a menej spojený kód, ktorý bude ľahšie pochopiteľný a udržiavateľný.

Príklady dedičstva v JDK

Java Development Kit je plná dobrých príkladov dedičstva:

 trieda IndexOutOfBoundsException rozširuje RuntimeException {...} trieda ArrayIndexOutOfBoundsException rozširuje IndexOutOfBoundsException {...} trieda FileWriter rozširuje OutputStreamWriter {...} trieda OutputStreamWriter rozširuje Writer {...} rozhranie Stream rozširuje BaseStream {...} 

Všimnite si, že v každom z týchto príkladov je podradená trieda špecializovanou verziou svojho rodiča; napríklad, IndexOutOfBoundsException je typ RuntimeException.

Prepísanie metódy s dedičnosťou Java

Dedenie nám umožňuje znovu použiť metódy a ďalšie atribúty jednej triedy v novej triede, čo je veľmi výhodné. Aby však dedičstvo skutočne fungovalo, musíme tiež byť schopní zmeniť niektoré zdedené správanie v rámci našej novej podtriedy. Napríklad by sme mohli chcieť špecializovať zvuk a Kat robí:

 trieda Zviera {void emitSound () {System.out.println ("Zviera vydalo zvuk"); }} class Cat extends Animal {@Override void emitSound () {System.out.println ("Meow"); }} class Dog extends Animal {} public class Main {public static void main (String ... doYourBest) {Animal cat = new Cat (); // Mňauká Zvierací pes = nový Pes (); // Zviera vydalo zvuk Zviera zviera = nové Zviera (); // Zviera vydalo zvuk cat.emitSound (); dog.emitSound (); animal.emitSound (); }} 

Toto je príklad dedičstva Java s prepísaním metódy. Najprv my predĺžiť the Zviera triedy na vytvorenie nového Kat trieda. Ďalej my prepísať the Zviera triedy emitSound () metóda na získanie konkrétneho zvuku Kat robí. Aj keď sme typ triedy deklarovali ako Zviera, keď to vytvoríme inštanciou ako Kat dostaneme mačku mňau.

Prevažujúcou metódou je polymorfizmus

Možno si z môjho posledného príspevku pamätáte, že prepísanie metódy je príkladom polymorfizmu alebo vyvolania virtuálnej metódy.

Má Java viacnásobné dedičstvo?

Na rozdiel od niektorých jazykov, napríklad C ++, Java neumožňuje viacnásobné dedenie s triedami. S rozhraniami však môžete použiť viac dedičstiev. Rozdiel medzi triedou a rozhraním je v tomto prípade ten, že rozhrania nezachovávajú stav.

Ak sa pokúsite o viac dedičstiev, ako mám nižšie, kód sa nezhromadí:

 trieda zviera {} trieda cicavec {} trieda pes rozširuje zviera, cicavec {} 

Riešením využívajúcim triedy by bolo dediť po jednom:

 trieda Zviera {} trieda Cicavce rozširuje Zviera {} trieda Pes rozširuje cicavce {} 

Ďalším riešením je nahradiť triedy rozhraniami:

 interface Animal {} interface Mammal {} class Dog implementuje Animal, Mammal {} 

Používanie „super“ na prístup k metódam nadradených tried

Keď sú dve triedy spojené prostredníctvom dedenia, podradená trieda musí mať prístup k každému prístupnému poľu, metóde alebo konštruktoru svojej nadradenej triedy. V Jave používame vyhradené slovo Super aby sa zabezpečilo, že trieda dieťaťa bude mať naďalej prístup k prepísanej metóde svojho rodiča:

 public class SuperWordExample {class Character {Character () {System.out.println ("Znak bol vytvorený"); } void move () {System.out.println ("Chôdza po znakoch ..."); }} trieda Moe rozširuje znak {Moe () {super (); } void giveBeer () {super.move (); System.out.println ("Dajte pivo"); }}} 

V tomto príklade Postava je rodičovská trieda pre Moe. Použitím Super, máme prístup Postavaje presunúť () spôsob, ako dať Moe pivo.

Používanie konštruktorov s dedičstvom

Keď jedna trieda dedí od druhej, najskôr sa načíta konštruktor nadtriedy a až potom sa načíta jej podtrieda. Vo väčšine prípadov vyhradené slovo Super bude automaticky pridaný do konštruktora. Ak má však nadtrieda vo svojom konštruktore parameter, budeme musieť zámerne vyvolať Super konštruktor, ako je uvedené nižšie:

 public class ConstructorSuper {class Character {Character () {System.out.println ("Bol vyvolaný superkonštruktor"); }} trieda Barney rozširuje znak {// Nie je potrebné deklarovať konštruktor ani vyvolávať superkonštruktor // K tomu bude JVM}} 

Ak má nadradená trieda konštruktor s aspoň jedným parametrom, potom musíme deklarovať konštruktor v podtriede a použiť Super výslovne vyvolať nadradený konštruktor. The Super vyhradené slovo sa nepridá automaticky a kód sa bez neho kompiluje. Napríklad:

 public class CustomizedConstructorSuper {class Character {Character (String name) {System.out.println (name + "was invoked"); }} trieda Barney rozširuje znak {// Budeme mať chybu kompilácie, ak explicitne nevyvoláme konštruktor // Musíme ho pridať Barney () {super ("Barney Gumble"); }}} 

Zadajte casting a ClassCastException

Casting je spôsob výslovnej komunikácie s kompilátorom, že skutočne chcete previesť daný typ. Je to ako povedať: „Hej, JVM, viem, čo robím, preto prosím obsadte túto triedu týmto typom.“ Ak trieda, ktorú ste obsadili, nie je kompatibilná s typom triedy, ktorý ste deklarovali, dostanete a ClassCastException.

V rámci dedičstva môžeme triedu dieťaťa priradiť k triede rodiča bez prenášania, ale nemôžeme priradiť triedu rodiča k triede dieťaťa bez použitia prenášania.

Uvažujme o nasledujúcom príklade:

 verejná trieda CastingExample {public static void main (String ... castingExample) {Animal animal = new Animal (); Pes pes Zviera = (Pes) zviera; // Dostaneme ClassCastException Dog dog = new Dog (); Zvierací pesWithAnimalType = nový pes (); Dog specificDog = (Pes) dogWithAnimalType; specificDog.bark (); Zviera anotherDog = pes; // Je to tu v poriadku, nie je potrebné prenášať System.out.println ((((Dog) anotherDog)); // Toto je ďalší spôsob obsadenia objektu}} class Animal {} class Dog extends Animal {void bark () {System.out.println ("Au au"); }} 

Keď sa pokúšame odhodiť Zviera inštancia na a pes dostaneme výnimku. Je to preto, lebo Zviera nevie nič o svojom dieťati. Môže to byť mačka, vták, jašterica atď. O konkrétnom zvierati nie sú k dispozícii žiadne informácie.

Problém v tomto prípade je, že sme vytvorili inštanciu Zviera Páči sa ti to:

 Zvieracie zviera = nové zviera (); 

Potom to skúsil obsadiť takto:

 Pes pes Zviera = (Pes) zviera; 

Pretože nemáme pes napríklad je nemožné priradiť Zviera do pes. Ak sa pokúsime, dostaneme ClassCastException

Aby sme sa vyhli výnimke, mali by sme vytvoriť inštanciu pes Páči sa ti to:

 Pes pes = nový Pes (); 

potom to priradiť k Zviera:

 Zviera anotherDog = pes; 

V tomto prípade, pretože sme rozšírili Zviera triedy, pes inštancia nemusí byť ani obsadená; the Zviera typ nadradenej triedy jednoducho prijme zadanie.

Casting s supertypmi

Je možné deklarovať a pes so supertypom Zviera, ale ak chceme vyvolať konkrétnu metódu z pes, budeme to musieť obsadiť. Ako príklad, čo keby sme chceli vyvolať súbor štekať() metóda? The Zviera supertyp nemá žiadny spôsob, ako presne zistiť, na ktorú zvieraciu inštanciu sa odvolávame, takže musíme nahodiť pes manuálne predtým, ako môžeme vyvolať štekať() metóda:

 Zvierací pesWithAnimalType = nový pes (); Dog specificDog = (Pes) dogWithAnimalType; specificDog.bark (); 

Môžete tiež použiť casting bez priradenia objektu k typu triedy. Tento prístup je užitočný, ak nechcete deklarovať inú premennú:

 System.out.println ((((Dog) anotherDog)); // Toto je ďalší spôsob obsadenia objektu 

Prijmite výzvu v oblasti dedičstva Java!

Naučili ste sa niekoľko dôležitých pojmov dedičstva, takže teraz je čas vyskúšať dedičskú výzvu. Najskôr si prečítajte nasledujúci kód:

$config[zx-auto] not found$config[zx-overlay] not found