Programování

Dědičnost versus složení: Jak si vybrat

Dědičnost a složení jsou dvě programovací techniky, které vývojáři používají k navázání vztahů mezi třídami a objekty. Zatímco dědičnost odvozuje jednu třídu od druhé, složení definuje třídu jako součet jejích částí.

Třídy a objekty vytvořené prostřednictvím dědičnosti jsou úzce spojeno protože změna nadřazené nebo nadtřídy v dědickém vztahu riskuje rozbití vašeho kódu. Třídy a objekty vytvořené pomocí kompozice jsou volně vázané, což znamená, že můžete snáze měnit součásti bez porušení kódu.

Protože volně spojený kód nabízí větší flexibilitu, mnoho vývojářů zjistilo, že kompozice je lepší technika než dědičnost, ale pravda je složitější. Výběr programovacího nástroje je podobný výběru správného kuchyňského nástroje: K krájení zeleniny byste nepoužili nůž na máslo a stejným způsobem byste neměli volit složení pro každý scénář programování.

V tomto Java Challengeru se naučíte rozdíl mezi dědičností a složením a jak se rozhodnout, která je pro váš program správná. Dále vám představím několik důležitých, ale náročných aspektů dědičnosti Java: přepsání metody, super klíčové slovo a typ casting. Nakonec otestujete, co jste se naučili prací v příkladu dědičnosti řádek po řádku, abyste určili, jaký by měl být výstup.

Kdy použít dědičnost v Javě

V objektově orientovaném programování můžeme použít dědičnost, když víme, že existuje vztah „je“ mezi dítětem a jeho nadřazenou třídou. Některé příklady by byly:

  • Osoba je člověk.
  • Kočka je zvíře.
  • Auto je vozidlo.

V každém případě je dítětem nebo podtřídou a specializované verze nadřazené nebo nadtřídy. Zdědění ze nadtřídy je příkladem opětovného použití kódu. Abyste tomuto vztahu lépe porozuměli, věnujte chvilku prostudování Auto třída, která dědí z Vozidlo:

 třída Vozidlo {Řetězcová značka; Barva řetězce; dvojitá hmotnost; dvojnásobná rychlost; void move () {System.out.println ("Vozidlo se pohybuje"); }} veřejná třída Car rozšiřuje Vehicle {String licensePlateNumber; Majitel řetězce; String bodyStyle; public static void main (String ... inheritanceExample) {System.out.println (nová značka Vehicle ().); System.out.println (nová značka Car ().); new Car (). move (); }} 

Když uvažujete o použití dědičnosti, zeptejte se sami sebe, zda je podtřída opravdu specializovanější verzí nadtřídy. V tomto případě je automobil typ vozidla, takže vztah dědictví má smysl.

Kdy použít složení v Javě

V objektově orientovaném programování můžeme použít kompozici v případech, kdy jeden objekt „má“ (nebo je jeho součástí) jiný objekt. Některé příklady by byly:

  • Auto baterie (baterie je část auto).
  • Osoba srdce (srdce je část osoba).
  • Dům obývací pokoj (obývací pokoj je část dům).

Chcete-li lépe porozumět tomuto typu vztahu, zvažte složení a Dům:

 public class CompositionExample {public static void main (String ... houseComposition) {new House (new Bedroom (), new LivingRoom ()); // Dům je nyní složen z ložnice a LivingRoom} statické třídy House {Bedroom bedroom; LivingRoom livingRoom; Dům (ložnice v ložnici, LivingRoom livingRoom) {this.bedroom = ložnice; this.livingRoom = livingRoom; }} statická třída Ložnice {} statická třída LivingRoom {}} 

V tomto případě víme, že dům má obývací pokoj a ložnici, takže můžeme použít Ložnice a Obývací pokoj objekty ve složení a Dům

Získejte kód

Získejte zdrojový kód pro příklady v tomto Java Challenger. Podle následujících příkladů můžete spustit vlastní testy.

Dědičnost vs složení: Dva příklady

Zvažte následující kód. Je to dobrý příklad dědictví?

 import java.util.HashSet; veřejná třída CharacterBadExampleInheritance rozšiřuje HashSet {public static void main (String ... badExampleOfInheritance) {BadExampleInheritance badExampleInheritance = nový BadExampleInheritance (); badExampleInheritance.add ("Homer"); badExampleInheritance.forEach (System.out :: println); } 

V tomto případě je odpověď ne. Podřízená třída dědí mnoho metod, které nikdy nepoužije, což má za následek těsně spojený kód, který je matoucí i obtížně udržovatelný. Pokud se podíváte pozorně, je také jasné, že tento kód neprochází testem „je“.

Nyní zkusme stejný příklad pomocí složení:

 import java.util.HashSet; import java.util.Set; veřejná třída CharacterCompositionExample {static Set set = new HashSet (); public static void main (String ... goodExampleOfComposition) {set.add ("Homer"); set.forEach (System.out :: println); } 

Použití složení pro tento scénář umožňuje CharacterCompositionExample třída použít pouze dva z HashSetmetody, aniž by je všechny zdědil. Výsledkem je jednodušší, méně spojený kód, který bude snazší pochopit a udržovat.

Příklady dědičnosti v JDK

Java Development Kit je plná dobrých příkladů dědičnosti:

 třída IndexOutOfBoundsException rozšiřuje RuntimeException {...} třída ArrayIndexOutOfBoundsException rozšiřuje IndexOutOfBoundsException {...} třída FileWriter rozšiřuje OutputStreamWriter {...} třída OutputStreamWriter rozšiřuje Writer {...} rozhraní Stream rozšiřuje BaseStream {...} 

Všimněte si, že v každém z těchto příkladů je podřízená třída specializovanou verzí svého rodiče; například, IndexOutOfBoundsException je typ RuntimeException.

Přepsání metody s dědičností Java

Dědičnost nám umožňuje znovu použít metody a další atributy jedné třídy v nové třídě, což je velmi výhodné. Aby ale dědictví opravdu fungovalo, musíme také být schopni změnit některé zděděné chování v rámci naší nové podtřídy. Například bychom mohli chtít specializovat zvuk a Kočka dělá:

 třída Zvíře {void emitSound () {System.out.println ("Zvíře vydalo zvuk"); }} třída Cat rozšiřuje Animal {@Override void emitSound () {System.out.println ("Meow"); }} třída Pes rozšiřuje Animal {} veřejná třída Main {public static void main (String ... doYourBest) {Animal cat = new Cat (); // Meow Animal dog = new Dog (); // Zvíře vydalo zvuk Animal animal = new Animal (); // Zvíře vydalo zvukovou kočku.emitSound (); dog.emitSound (); animal.emitSound (); }} 

Toto je příklad dědičnosti Java s přepsáním metody. Nejprve my rozšířit the Zvíře třídy k vytvoření nové Kočka třída. Dále my přepsat the Zvíře třídy emitSound () metoda pro získání konkrétního zvuku Kočka dělá. Přestože jsme typ třídy deklarovali jako Zvíře, když to instancujeme jako Kočka dostaneme kočku mňau.

Přepsání metody je polymorfismus

Možná si z mého posledního příspěvku pamatujete, že přepsání metody je příkladem polymorfismu nebo vyvolání virtuální metody.

Má Java vícenásobné dědictví?

Na rozdíl od některých jazyků, jako je C ++, Java neumožňuje vícenásobné dědičnost s třídami. S rozhraními však můžete použít více dědičností. Rozdíl mezi třídou a rozhraním v tomto případě spočívá v tom, že rozhraní nezachovávají stav.

Pokud se pokusíte o více dědičností, jako mám níže, kód nebude kompilován:

 třída Zvíře {} třída Savec {} třída Pes rozšiřuje zvíře, savec {} 

Řešení využívající třídy by bylo zdědit jeden po druhém:

 třída Zvíře {} třída Savec rozšiřuje Zvíře {} třída Pes rozšiřuje Savec {} 

Dalším řešením je nahradit třídy rozhraními:

 interface Animal {} interface Mammal {} třída Dog implementuje Animal, Mammal {} 

Používání „super“ pro přístup k metodám nadřazených tříd

Když jsou dvě třídy spojené prostřednictvím dědičnosti, podřízená třída musí mít přístup ke každému přístupnému poli, metodě nebo konstruktoru své nadřazené třídy. V Javě používáme vyhrazené slovo super zajistit, aby podřízená třída měla stále přístup k přepsané metodě svého rodiče:

 public class SuperWordExample {class Character {Character () {System.out.println ("A Character has been created"); } void move () {System.out.println ("Procházka znaků ..."); }} třída Moe rozšiřuje Character {Moe () {super (); } void giveBeer () {super.move (); System.out.println ("Dejte pivo"); }}} 

V tomto příkladu Charakter je mateřská třída pro Moe. Použitím super, máme přístup Charakterje hýbat se() způsob, jak dát Moe pivo.

Použití konstruktorů s dědičností

Když jedna třída dědí od jiné, nejprve se načte nejprve konstruktor nadtřídy a poté se načte její podtřída. Ve většině případů vyhrazené slovo super bude automaticky přidán do konstruktoru. Pokud má však nadtřída ve svém konstruktoru parametr, budeme muset záměrně vyvolat super konstruktor, jak je znázorněno níže:

 public class ConstructorSuper {třída Character {Character () {System.out.println ("Byl vyvolán super konstruktor"); }} třída Barney rozšiřuje Character {// Není třeba deklarovat konstruktor nebo vyvolat super konstruktor // K tomu bude JVM}} 

Pokud má nadřazená třída konstruktor s alespoň jedním parametrem, musíme deklarovat konstruktor v podtřídě a použít super explicitně vyvolat nadřazený konstruktor. The super vyhrazené slovo nebude přidáno automaticky a kód se bez něj nezkompiluje. Například:

 public class CustomizedConstructorSuper {class Character {Character (String name) {System.out.println (name + "was invoked"); }} třída Barney rozšiřuje znak {// Budeme mít chybu kompilace, pokud explicitně nevyvoláme konstruktor // Musíme jej přidat Barney () {super ("Barney Gumble"); }}} 

Zadejte casting a ClassCastException

Casting je způsob výslovné komunikace s překladačem, který opravdu chcete převést daný typ. Je to jako říkat: „Hele, JVM, vím, co dělám, tak prosím sesaďte tuto třídu do tohoto typu.“ Pokud třída, kterou sesíláte, není kompatibilní s typem třídy, který jste deklarovali, dostanete a ClassCastException.

V dědičnosti můžeme přiřadit podřízenou třídu k nadřazené třídě bez odlévání, ale nemůžeme přiřadit nadřazenou třídu k podřízené třídě bez použití odlévání.

Zvažte následující příklad:

 veřejná třída CastingExample {public static void main (String ... castingExample) {Animal animal = new Animal (); Pes pesZvíře = (Pes) zvíře; // Získáme ClassCastException Dog dog = new Dog (); Zvířecí pesWithAnimalType = nový pes (); Dog specificDog = (Dog) dogWithAnimalType; specificDog.bark (); Zvíře jiný pes = pes; // Zde je to v pořádku, není třeba házet System.out.println ((((Dog) anotherDog)); // Toto je další způsob, jak vrhnout objekt}} třída Zvíře {} třída Pes rozšiřuje Zvíře {void bark () {System.out.println ("Au au"); }} 

Když se pokusíme vrhnout Zvíře instance k a Pes dostaneme výjimku. Je to proto, že Zvíře neví nic o svém dítěti. Může to být kočka, pták, ještěrka atd. O konkrétním zvířeti nejsou žádné informace.

Problém v tomto případě je, že jsme vytvořili instanci Zvíře takhle:

 Zvířecí zvíře = nové zvíře (); 

Pak to zkusil seslat takto:

 Pes pesZvíře = (Pes) zvíře; 

Protože nemáme Pes například není možné přiřadit Zvíře do Pes. Pokud to zkusíme, dostaneme ClassCastException

Abychom se vyhnuli výjimce, měli bychom vytvořit instanci Pes takhle:

 Pes pes = nový pes (); 

poté jej přiřadit Zvíře:

 Zvíře jiný pes = pes; 

V tomto případě, protože jsme rozšířili Zvíře třída, Pes instance dokonce nemusí být obsazena; the Zvíře typ nadřazené třídy jednoduše přijme přiřazení.

Casting se supertypy

Je možné deklarovat a Pes se supertypem Zvíře, ale pokud chceme vyvolat konkrétní metodu z Pes, budeme to muset vrhnout. Jako příklad, co kdybychom chtěli vyvolat kůra() metoda? The Zvíře supertyp nemá žádný způsob, jak přesně vědět, jakou zvířecí instanci vyvoláváme, takže musíme vrhat Pes ručně, než můžeme vyvolat kůra() metoda:

 Zvířecí pesWithAnimalType = nový pes (); Dog specificDog = (Dog) dogWithAnimalType; specificDog.bark (); 

Můžete také použít casting, aniž byste objekt přiřadili typu třídy. Tento přístup je užitečný, když nechcete deklarovat jinou proměnnou:

 System.out.println ((((Dog) anotherDog)); // Toto je další způsob obsazení objektu 

Přijměte výzvu týkající se dědictví Java!

Naučili jste se některé důležité pojmy dědičnosti, takže nyní je čas vyzkoušet dědičnost. Nejprve si prostudujte následující kód:

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