Articles

Concetto OOP per principianti: cos’è l’ereditarietà?

L’ereditarietà è uno dei concetti fondamentali dei linguaggi OOP (Object-Oriented Programming). È un meccanismo in cui è possibile derivare una classe da un’altra classe per una gerarchia di classi che condividono un insieme di attributi e metodi.

Puoi usarlo per dichiarare diversi tipi di eccezioni, aggiungere logica personalizzata ai framework esistenti e persino mappare il tuo modello di dominio in un database.,

Dichiarare una gerarchia di ereditarietà

In Java, ogni classe può essere derivata solo da un’altra classe. Quella classe è chiamata superclasse o classe genitore. La classe derivata è chiamata sottoclasse o classe figlio.

Si utilizza la parola chiave extends per identificare la classe estesa dalla sottoclasse. Se non si dichiara una superclasse, la classe estende implicitamente l’oggetto class. Object è la radice di tutte le gerarchie di ereditarietà; è l’unica classe in Java che non estende un’altra classe.

Il seguente diagramma e frammenti di codice mostrano un esempio di una semplice gerarchia di ereditarietà.,

La classe BasicCoffeeMachine non dichiara una superclasse ed estende implicitamente l’oggetto class. È possibile clonare il progetto di esempio CoffeeMachine su GitHub.

La classe PremiumCoffeeMachine è una sottoclasse della classe BasicCoffeeMachine.

Modificatori di ereditarietà e accesso

I modificatori di accesso definiscono quali classi possono accedere a un attributo o a un metodo. In uno dei miei post precedenti sull’incapsulamento, ti ho mostrato come potresti usarli per implementare un meccanismo di occultamento delle informazioni. Ma questo non è l’unico caso in cui è necessario avere familiarità con i diversi modificatori., Influenzano anche le entità e gli attributi a cui è possibile accedere all’interno di una gerarchia di ereditarietà.

Ecco una rapida panoramica dei diversi modificatori:

  • Gli attributi o i metodi privati sono accessibili solo all’interno della stessa classe.
  • Gli attributi e i metodi senza un modificatore di accesso sono accessibili all’interno della stessa classe e da tutte le altre classi all’interno dello stesso pacchetto.
  • È possibile accedere agli attributi o ai metodi protetti all’interno della stessa classe, da tutte le classi all’interno dello stesso pacchetto e da tutte le sottoclassi.,
  • Gli attributi e i metodi pubblici sono accessibili da tutte le classi.

Come puoi vedere in quell’elenco, una sottoclasse può accedere a tutti gli attributi e i metodi protetti e pubblici della superclasse. Se la sottoclasse e la superclasse appartengono allo stesso pacchetto, la sottoclasse può anche accedere a tutti gli attributi e i metodi privati del pacchetto della superclasse.

Lo faccio due volte nel costruttore della classe PremiumCoffeeMachine.

Per prima cosa uso la parola chiave super per chiamare il costruttore della superclasse. Il costruttore è pubblico e la sottoclasse può accedervi., La parola chiave super fa riferimento alla superclasse. È possibile utilizzarlo per accedere a un attributo o per chiamare un metodo della superclasse che viene sovrascritto dalla sottoclasse corrente. Ma di più su questo nella sezione seguente.

L’attributo protetto configMap viene definito dalla classe BasicCoffeeMachine. Estendendo quella classe, l’attributo diventa anche parte della classe PremiumCoffeeMachine e posso aggiungere la configurazione necessaria per preparare un espresso alla mappa.,

Metodo di override

L’ereditarietà non solo aggiunge tutti i metodi pubblici e protetti della superclasse alla sottoclasse, ma consente anche di sostituire la loro implementazione. Il metodo della sottoclasse sostituisce quindi quello della superclasse. Questo meccanismo è chiamato polimorfismo.

Lo uso nella classe PremiumCoffeeMachine per estendere le capacità di erogazione del caffè della macchina da caffè. Il metodo brewCoffee del metodo BasicCoffeeMachine può solo preparare caffè filtro.

Sovrascrivo tale metodo nella classe PremiumCoffeeMachine per aggiungere il supporto per CoffeeSelection.,ESPRESSO. Come puoi vedere nello snippet di codice, la parola chiave super è molto utile se si sovrascrive un metodo. Il metodo brewCoffee della BasicCoffeeMachine gestisce già la CoffeeSelection.FILTER_COFFEE e genera una CoffeeException per CoffeeSelections non supportati.

Posso riutilizzarlo nel mio nuovo metodo brewCoffee. Invece di reimplementare la stessa logica, controllo solo se CoffeeSelection è ESPRESSO. Se questo non è il caso, io uso la parola chiave super per chiamare il metodo brewCoffee della superclasse.,

Impedire che un metodo venga sovrascritto

Se si desidera assicurarsi che nessuna sottoclasse possa modificare l’implementazione di un metodo, è possibile dichiararlo definitivo. Nell’esempio di questo post, l’ho fatto per il metodo addBeans della classe BasicCoffeeMachine.

È spesso una buona idea rendere definitivi tutti i metodi chiamati da un costruttore. Impedisce a qualsiasi sottoclasse, spesso involontariamente, di modificare il comportamento del costruttore.,

Una sottoclasse è anche del tipo della sua superclasse

Una sottoclasse non solo eredita gli attributi e i metodi della superclasse, ma eredita anche i tipi della superclasse. Nell’esempio, BasicCoffeeMachine è di tipo BasicCoffeeMachine e Object. E un oggetto PremiumCoffeeMachine è dei tipi PremiumCoffeeMachine, BasicCoffeeMachine e Object.

A causa di ciò, è possibile lanciare un oggetto PremiumCoffeeMachine per digitare BasicCoffeeMachine.

BasicCoffeeMachinee coffeeMachine = (BasicCoffeeMachine) PremiumCoffeeMachine(beans);

Che consente di scrivere codice che utilizza la superclasse ed eseguirlo con tutte le sottoclassi.,

In questo esempio, il codice del metodo createCoffeeMachine restituisce e il metodo makeCoffee utilizza la classe BasicCoffeeMachine. Ma il metodo createCoffeeMachine crea un’istanza di un nuovo oggetto PremiumCoffeeMachine. Quando viene restituito dal metodo, l’oggetto viene automaticamente lanciato su BasicCoffeeMachine e il codice può chiamare tutti i metodi pubblici della classe BasicCoffeeMachine.

L’oggetto coffeeMachine viene lanciato su BasicCoffeeMachine, ma è ancora un PremiumCoffeeMachine., Quindi, quando il metodo makeCoffee chiama il metodo brewCoffee, chiama il metodo sovrascritto sulla classe PremiumCoffeeMachine.

Definizione di classi astratte

Le classi astratte sono diverse dalle altre classi di cui abbiamo parlato. Possono essere estesi, ma non istanziati. Ciò li rende ideali per rappresentare generalizzazioni concettuali che non esistono nel tuo dominio specifico, ma ti consentono di riutilizzare parti del tuo codice.

Si utilizza la parola chiave abstract per dichiarare una classe o un metodo astratto. Una classe astratta non ha bisogno di contenere alcun metodo astratto., Ma un metodo astratto deve essere dichiarato da una classe astratta.

Rifattorizziamo l’esempio della macchina da caffè e introduciamo la classe AbstractCoffeeMachine come superclasse della classe BasicCoffeeMachine. Dichiaro quella classe come astratta e definisco il metodo brewCoffee astratto.

public abstract class AbstractCoffeeMachine { protected Map<CoffeeSelection, Configuration> configMap; public AbstractCoffeeMachine() { this.configMap = new HashMap<CoffeeSelection, Configuration>(); } public abstract Coffee brewCoffee(CoffeeSelection selection) throws CoffeeException; }

Come puoi vedere, non fornisco il corpo del metodo brewCoffee astratto. Lo dichiaro semplicemente come farei in un’interfaccia., Quando si estende la classe AbstractCoffeeMachine, sarà necessario definire la sottoclasse come abstract o sovrascrivere il metodo brewCoffee per implementare il corpo del metodo.

Faccio alcune modifiche minori alla classe BasicCoffeeMachine. Ora estende la classe AbstractCoffeeMachine e il metodo brewCoffee già esistente sovrascrive il metodo astratto della superclasse.

Un’altra cosa che ho cambiato è il costruttore della classe BasicCoffeeMachine. Ora chiama il costruttore della superclasse e aggiunge una coppia chiave-valore all’attributo configMap senza istanziare la mappa., È definito e istanziato dalla superclasse astratta e può essere utilizzato in tutte le sottoclassi.

Questa è una delle principali differenze tra una superclasse astratta e un’interfaccia. La classe astratta non solo consente di dichiarare i metodi, ma è anche possibile definire attributi che non sono statici e finali.

Riepilogo

Come hai visto, l’ereditarietà è un concetto potente che consente di implementare una sottoclasse che estende una superclasse. In questo modo, la sottoclasse eredita tutti gli attributi e i metodi protetti e pubblici e i tipi della superclasse., È quindi possibile utilizzare gli attributi ereditati della superclasse, utilizzare o sovrascrivere i metodi ereditati e trasmettere la sottoclasse a qualsiasi tipo di superclasse.

È possibile utilizzare una classe astratta per definire un’astrazione generale che non può essere istanziata. All’interno di tale classe, è possibile dichiarare metodi astratti che devono essere sovrascritti da sottoclassi non astratte. Viene spesso utilizzato se l’implementazione di tale metodo è specifica per ogni sottoclasse, ma si desidera definire un’API generale per tutte le classi della gerarchia.