1. Pegaso

Diamo uno sguardo più approfondito al terzo principio dell'OOP : l' ereditarietà . Questo è un argomento molto interessante che userai spesso. Per chi non lo sapesse, la programmazione è indistinguibile dalla magia. Quindi iniziamo con un'interessante analogia...;

Diciamo che sei un mago che vuole creare un cavallo volante. Da un lato, potresti provare a evocare un pegaso. Ma poiché i pegasi non esistono in natura, sarà molto difficile. Dovrai fare molto da solo. È molto più facile prendere un cavallo ed evocare le sue ali.

In programmazione, questo processo è chiamato "ereditarietà". Supponiamo di dover scrivere una classe molto complessa. Ci vuole molto tempo per scrivere il codice da zero e quindi testare tutto a lungo per cercare errori. Perché andare nel modo più duro? È meglio controllare se esiste già una classe del genere.

Supponiamo di trovare una classe i cui metodi implementano l'80% delle funzionalità necessarie. Cosa ne fai dopo? Potresti semplicemente copiare il suo codice nella tua classe. Ma questa soluzione presenta diversi inconvenienti:

  1. La classe che trovi potrebbe essere già compilata in bytecode e potresti non avere accesso al suo codice sorgente.
  2. Il codice sorgente della classe è disponibile, ma tu lavori per un'azienda che potrebbe essere citata in giudizio per un paio di miliardi per aver utilizzato anche solo 6 righe del codice di qualcun altro. E poi il tuo datore di lavoro ti farà causa.
  3. Duplicazione non necessaria di una grande quantità di codice. Inoltre, se l'autore di una classe esterna trova un bug in essa e lo corregge, avrai ancora il bug.

Esiste una soluzione più elegante e non richiede l'accesso legale al codice della classe originale. In Java, puoi semplicemente dichiarare quella classe come genitore della tua classe. Ciò equivarrà ad aggiungere il codice per quella classe al tuo codice. La tua classe vedrà tutti i dati e tutti i metodi della classe genitore. Ad esempio, puoi fare questo: ereditiamo "cavallo" e poi aggiungiamo "ali" per ottenere un "pegaso"


2. Classe base comune

L'ereditarietà può essere utilizzata anche per altri scopi. Supponiamo che tu abbia dieci classi molto simili. Hanno gli stessi dati e metodi. È possibile creare una classe base speciale, spostare i dati (ei metodi associati) in questa classe base e dichiarare discendenti quelle dieci classi. In altre parole, in ogni classe indicare che la sua classe genitore è questa classe base.

Proprio come i vantaggi dell'astrazione si rivelano solo insieme all'incapsulamento laterale, così anche i vantaggi dell'ereditarietà sono molto migliorati quando si usa il polimorfismo. Ma lo scoprirai un po 'più tardi. Oggi esamineremo diversi esempi di utilizzo dell'ereditarietà.

Pezzi degli scacchi

Supponiamo di scrivere un programma che gioca a scacchi con un utente umano. Di conseguenza, abbiamo bisogno di classi per rappresentare i pezzi. Che classi sarebbero?

Se hai mai giocato a scacchi, la risposta ovvia è Re, Regina, Alfiere, Cavaliere, Torre e Pedone.

Ma le classi stesse avrebbero comunque bisogno di memorizzare informazioni su ogni pezzo. Ad esempio, le coordinate x e y e il valore del pezzo. Dopo tutto, alcuni pezzi sono più preziosi di altri.

Inoltre, i pezzi si muovono in modo diverso, il che significa che le classi implementeranno un comportamento diverso. Ecco come potresti definirli come classi:

class King
{
   int x;
   int y;
   int worth;

   void kingMove()
   {
     // Code that decides
     // how to move
     // the king
   }
}
class Queen
{
   int x;
   int y;
   int worth;

   void queenMove()
   {
     // Code that decides
     // how to move
     // the queen
   }
}
class Rook
{
   int x;
   int y;
   int worth;

   void rookMove()
   {
     // Code that decides
     // how to move
     // the rook
   }
}
class Knight
{
   int x;
   int y;
   int worth;

   void knightMove()
   {
     // Code that decides
     // how to move
     // the knight
   }
}
class Bishop
{
   int x;
   int y;
   int worth;

   void bishopMove()
   {
     // Code that decides
     // how to move
     // the bishop
   }
}
class Pawn
{
   int x;
   int y;
   int worth;

   void pawnMove()
   {
     // Code that decides
     // how to move
     // the pawn
   }
}

Questa è una descrizione molto primitiva dei pezzi degli scacchi.

Classe base comune

Ed ecco come puoi usare l'ereditarietà per ridurre la quantità di codice. Possiamo portare i metodi e i dati comuni in una classe comune. Lo chiameremo ChessItem. Non ha senso creare oggetti di ChessItem class, poiché la classe non corrisponde a nessun pezzo degli scacchi . Detto questo, la classe si rivelerà molto utile:

class King extends ChessItem
{
   void kingMove()
   {
     // Code that decides
     // how to move the king
   }
}
class Queen extends ChessItem
{
   void queenMove()
   {
     // Code that decides
     // how to move the queen
   }
}
class Rook extends ChessItem
{
   void rookMove()
   {
     // Code that decides
     // how to move the rook
   }
}
class ChessItem
{
   int x;
   int y;
   int worth;
}
class Knight extends ChessItem
{
   void knightMove()
   {
     // Code that decides
     // how to move the knight
   }
}
class Bishop extends ChessItem
{
   void bishopMove()
   {
     // Code that decides
     // how to move the bishop
   }
}
class Pawn extends ChessItem
{
   void pawnMove()
   {
     // Code that decides
     // how to move the pawn
   }
}

Questo è un ottimo modo per semplificare il codice per oggetti simili. I vantaggi sono particolarmente evidenti quando nel progetto sono presenti migliaia di oggetti diversi e centinaia di classi. Quindi le classi genitore (base) opportunamente selezionate consentono non solo di semplificare notevolmente la logica, ma anche di ridurre di dieci volte il codice.


3. Ereditarietà di classe —extends

Quindi cosa ci vuole per ereditare una classe? Affinché una classe ne erediti un'altra, devi scrivere la extendsparola chiave dopo la dichiarazione della classe figlia e quindi scrivere il nome della classe genitore. Di solito assomiglia a questo:

class Descendant extends Parent

Questo è ciò che devi scrivere quando dichiari la classe Descendant. A proposito, una classe può ereditare solo una classe.

Nella foto vediamo che una mucca ha ereditato un maiale, che ha ereditato una gallina, che ha ereditato un uovo. Un solo genitore! Tale eredità non è sempre logica. Ma quando tutto ciò che hai è un maiale e hai davvero bisogno di una mucca, i programmatori spesso non possono resistere all'impulso di trasformare un maiale in una mucca.

Java non ha ereditarietà multipla: una classe non può ereditare due classi. Ogni classe può avere una sola classe padre. Se non viene specificata alcuna classe genitore, la classe genitore è Object.

Detto questo, Java ha un'ereditarietà multipla delle interfacce. Questo attenua leggermente il problema. Parleremo delle interfacce un po' più tardi, ma per ora continuiamo ad esplorare l'ereditarietà.