Programmering

Arv kontra sammensætning: Hvordan man vælger

Arv og komposition er to programmeringsteknikker, som udviklere bruger til at etablere forhold mellem klasser og objekter. Mens arv stammer en klasse fra en anden, definerer sammensætning en klasse som summen af ​​dens dele.

Klasser og objekter skabt gennem arv er tæt koblet fordi det at skifte forælder eller superklasse i et arveforhold risikerer at bryde din kode. Klasser og objekter skabt gennem komposition er løst forbundet, hvilket betyder, at du lettere kan ændre komponentdelene uden at bryde din kode.

Fordi løst koblet kode giver mere fleksibilitet, har mange udviklere lært, at komposition er en bedre teknik end arv, men sandheden er mere kompleks. At vælge et programmeringsværktøj svarer til at vælge det rigtige køkkenværktøj: Du bruger ikke en smørkniv til at skære grøntsager, og på samme måde skal du ikke vælge komposition til ethvert programmeringsscenarie.

I denne Java Challenger lærer du forskellen mellem arv og sammensætning og hvordan man beslutter, hvad der er korrekt til dit program. Dernæst introducerer jeg dig til flere vigtige, men udfordrende aspekter af Java-arv: metodeoverstyring, den super nøgleord og type casting. Endelig vil du teste, hvad du har lært ved at arbejde gennem et arveeksempel linje for linje for at bestemme, hvad output skal være.

Hvornår skal du bruge arv i Java

I objektorienteret programmering kan vi bruge arv, når vi ved, at der er et "er et" forhold mellem et barn og dets overordnede klasse. Nogle eksempler er:

  • En person er en human.
  • En kat er en dyr.
  • En bil er en køretøj.

I begge tilfælde er barnet eller underklassen en specialiseret version af forælder eller superklasse. Arv fra superklassen er et eksempel på genbrug af kode. For at forstå dette forhold bedre, skal du tage et øjeblik at studere Bil klasse, som arver fra Køretøj:

 klasse køretøj {String brand; Strengfarve; dobbelt vægt; dobbelt hastighed; ugyldig flytning () {System.out.println ("Køretøjet kører"); }} offentlig klasse bil udvider køretøj {String licensPlateNumber; String ejer String bodyStyle; public static void main (String ... inheritanceExample) {System.out.println (nyt køretøj (). brand); System.out.println (ny bil (). Brand); ny bil (). flyt (); }} 

Når du overvejer at bruge arv, så spørg dig selv, om underklassen virkelig er en mere specialiseret version af superklassen. I dette tilfælde er en bil en type køretøj, så arveforholdet giver mening.

Hvornår skal du bruge komposition i Java

I objektorienteret programmering kan vi bruge komposition i tilfælde, hvor et objekt "har" (eller er en del af) et andet objekt. Nogle eksempler er:

  • En bil har en batteri (et batteri er en del af en bil).
  • En person har en hjerte (et hjerte er en del af en person).
  • Et hus har en stue (en stue er en del af et hus).

For bedre at forstå denne type forhold skal du overveje sammensætningen af ​​a Hus:

 offentlig klasse CompositionExample {public static void main (String ... houseComposition) {new House (new Bedroom (), new LivingRoom ()); // Huset er nu sammensat med et soveværelse og et LivingRoom} statisk klassehus {Soveværelse; LivingRoom livingRoom; Hus (soveværelse med soveværelse, LivingRoom livingRoom) {dette. Soveværelse = soveværelse; this.livingRoom = livingRoom; }} statisk klassesoveværelse {} statisk klasse LivingRoom {}} 

I dette tilfælde ved vi, at et hus har en stue og et soveværelse, så vi kan bruge Soveværelse og Stue objekter i sammensætningen af ​​en Hus

Få koden

Få kildekoden til eksempler i denne Java Challenger. Du kan køre dine egne tests, mens du følger eksemplerne.

Arv vs sammensætning: To eksempler

Overvej følgende kode. Er dette et godt eksempel på arv?

 importere java.util.HashSet; public class CharacterBadExampleInheritance udvider HashSet {public static void main (String ... badExampleOfInheritance) {BadExampleInheritance badExampleInheritance = new BadExampleInheritance (); badExampleInheritance.add ("Homer"); badExampleInheritance.forEach (System.out :: println); } 

I dette tilfælde er svaret nej. Barneklassen arver mange metoder, som den aldrig vil bruge, hvilket resulterer i tæt koblet kode, der er både forvirrende og vanskelig at vedligeholde. Hvis du ser nøje, er det også klart, at denne kode ikke består "er en" test.

Lad os nu prøve det samme eksempel ved hjælp af komposition:

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

Brug af komposition til dette scenarie tillader CharacterCompositionExample klasse til kun at bruge to af HashSetmetoder uden at arve dem alle. Dette resulterer i enklere, mindre koblet kode, der bliver lettere at forstå og vedligeholde.

Arveeksempler i JDK

Java Development Kit er fyldt med gode eksempler på arv:

 klasse IndexOutOfBoundsException udvider RuntimeException {...} klasse ArrayIndexOutOfBoundsException udvider IndexOutOfBoundsException {...} klasse FileWriter udvider OutputStreamWriter {...} klasse OutputStreamWriter udvider Writer {...} interface Strøm udvider BaseStream {...} 

Bemærk, at i hvert af disse eksempler er barneklassen en specialversion af sin forælder; for eksempel, IndexOutOfBoundsException er en type RuntimeException.

Metodeoverstyring med Java-arv

Arv giver os mulighed for at genbruge metoderne og andre attributter for en klasse i en ny klasse, hvilket er meget praktisk. Men for at arv virkelig kan fungere, skal vi også være i stand til at ændre noget af den arvede adfærd inden for vores nye underklasse. For eksempel vil vi måske specialisere lyden a Kat gør:

 klasse Animal {void emitSound () {System.out.println ("Dyret udsendte en lyd"); }} klasse Cat udvider Animal {@Override ugyldigt emitSound () {System.out.println ("Meow"); }} klasse Hund udvider Animal {} offentlig klasse Main {offentlig statisk ugyldig main (String ... doYourBest) {Animal cat = new Cat (); // Meow Dyrehund = ny hund (); // Dyret udsendte en lyd Dyredyr = nyt dyr (); // Dyret udsendte en sund kat.emitSound (); dog.emitSound (); animal.emitSound (); }} 

Dette er et eksempel på Java-arv med metodeoverstyring. Først vi forlænge det Dyr klasse for at skabe et nyt Kat klasse. Næste, vi tilsidesætte det Dyr klassens emitSound () metode til at få den specifikke lyd Kat gør. Selvom vi har erklæret klassetypen som Dyr, når vi instantierer det som Kat vi får kattens mia.

Metodeoverstyring er polymorfisme

Du husker muligvis fra mit sidste indlæg, at tilsidesættelse af metoden er et eksempel på polymorfisme eller virtuel metodeindkaldelse.

Har Java flere arv?

I modsætning til nogle sprog, såsom C ++, tillader Java ikke flere arv med klasser. Du kan dog bruge flere arv med grænseflader. Forskellen mellem en klasse og en grænseflade er i dette tilfælde, at grænseflader ikke holder tilstand.

Hvis du prøver flere arv som jeg har nedenfor, kompileres koden ikke:

 klasse Dyr {} klasse Pattedyr {} klasse Hund udvider Dyr, Pattedyr {} 

En løsning, der bruger klasser, ville være at arve en efter en:

 klasse Dyr {} klasse Pattedyr udvider Dyr {} klasse Hund udvider Pattedyr {} 

En anden løsning er at erstatte klasserne med grænseflader:

 grænseflade Dyr {} grænseflade Pattedyr {} klasse Hund implementerer Dyr, Pattedyr {} 

Brug af 'super' til at få adgang til overordnede klasses metoder

Når to klasser er relateret gennem arv, skal barneklassen have adgang til alle tilgængelige felter, metoder eller konstruktører i sin overordnede klasse. I Java bruger vi det reserverede ord super for at sikre, at barneklassen stadig kan få adgang til sin forældres tilsidesatte metode:

 offentlig klasse SuperWordExample {class Character {Character () {System.out.println ("Et tegn er oprettet"); } ugyldigt træk () {System.out.println ("Tegn går ..."); }} klasse Moe udvider karakter {Moe () {super (); } ugyldigt giveBeer () {super.move (); System.out.println ("Giv øl"); }}} 

I dette eksempel Karakter er moderklasse for Moe. Ved brug af super, vi er i stand til at få adgang Karakter's bevæge sig() metode for at give Moe en øl.

Brug af konstruktører med arv

Når en klasse arver fra en anden, vil superklassens konstruktør altid blive indlæst først, før den indlæser sin underklasse. I de fleste tilfælde er det reserverede ord super tilføjes automatisk til konstruktøren. Men hvis superklassen har en parameter i sin konstruktør, bliver vi bevidst påkaldt super konstruktør, som vist nedenfor:

 public class ConstructorSuper {class Character {Character () {System.out.println ("Super constructor blev påberåbt"); }} klasse Barney udvider karakter {// Intet behov for at erklære konstruktøren eller at påberåbe sig superkonstruktøren // JVM vil til det}} 

Hvis forældreklassen har en konstruktør med mindst en parameter, skal vi erklære konstruktøren i underklassen og bruge super til eksplicit at påkalde forældrekonstruktøren. Det super reserveret ord tilføjes ikke automatisk, og koden kompileres ikke uden det. For eksempel:

 offentlig klasse CustomisedConstructorSuper {class Character {Character (String name) {System.out.println (name + "blev påkaldt"); }} klasse Barney udvider karakter {// Vi får en kompileringsfejl, hvis vi ikke påberåber konstruktøren eksplicit // Vi skal tilføje det Barney () {super ("Barney Gumble"); }}} 

Skriv casting og ClassCastException

Casting er en måde til eksplicit at kommunikere til compileren, at du virkelig har til hensigt at konvertere en given type. Det er som at sige "Hej, JVM, jeg ved hvad jeg laver, så vær venlig at kaste denne klasse med denne type." Hvis en klasse, du har castet, ikke er kompatibel med den klassetype, du har erklæret, får du en ClassCastException.

I arv kan vi tildele barneklassen til forældreklassen uden at caste, men vi kan ikke tildele en overordnet klasse til underklassen uden at bruge casting.

Overvej følgende eksempel:

 offentlig klasse CastingExample {public static void main (String ... castingExample) {Animal animal = new Animal (); Dog dogAnimal = (Hund) dyr; // Vi får ClassCastException Dog dog = new Dog (); Animal dogWithAnimalType = ny hund (); Dog specificDog = (Dog) dogWithAnimalType; specificDog.bark (); Animal anotherDog = hund; // Det er fint her, intet behov for at caste System.out.println (((Dog) anotherDog)); // Dette er en anden måde at kaste objektet}} klasse Animal {} klasse Dog udvider Animal {void bark () {System.out.println ("Au au"); }} 

Når vi prøver at kaste en Dyr eksempel til en Hund vi får en undtagelse. Dette skyldes, at Dyr ved ikke noget om sit barn. Det kan være en kat, en fugl, en firben osv. Der er ingen oplysninger om det specifikke dyr.

Problemet i denne sag er, at vi har instantificeret Dyr sådan her:

 Dyredyr = nyt dyr (); 

Så prøvede at kaste det sådan her:

 Dog dogAnimal = (Hund) dyr; 

Fordi vi ikke har en Hund Det er f.eks. umuligt at tildele en Dyr til Hund. Hvis vi prøver, får vi en ClassCastException

For at undgå undtagelsen bør vi instantiere Hund sådan her:

 Hundhund = ny hund (); 

tildel det derefter til Dyr:

 Animal anotherDog = hund; 

I dette tilfælde, fordi vi har udvidet Dyr klasse, den Hund eksempel behøver ikke engang at blive kastet; det Dyr overordnet klassetype accepterer simpelthen opgaven.

Støbning med supertyper

Det er muligt at erklære en Hund med supertypen Dyr, men hvis vi vil påberåbe en bestemt metode fra Hund, vi bliver nødt til at kaste det. Som et eksempel, hvad hvis vi ville påberåbe os bark() metode? Det Dyr supertype har ingen måde at vide nøjagtigt, hvilken dyreinstans vi påberåber os, så vi er nødt til at kaste Hund manuelt, før vi kan påberåbe sig bark() metode:

 Animal dogWithAnimalType = ny hund (); Dog specificDog = (Dog) dogWithAnimalType; specificDog.bark (); 

Du kan også bruge casting uden at tildele objektet til en klassetype. Denne tilgang er praktisk, når du ikke vil erklære en anden variabel:

 System.out.println (((Dog) anotherDog)); // Dette er en anden måde at kaste objektet på 

Tag Java-arveudfordringen!

Du har lært nogle vigtige begreber om arv, så nu er det tid til at prøve en arveudfordring. For at starte skal du studere følgende kode:

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