Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


Algoritmi e programmazione: ereditarietà, Appunti di Algoritmi E Programmazione Avanzata

Appunti del corso di ALGORITMI E PROGRAMMAZIONE del prof. Fabio Sartori. Ereditarietà

Tipologia: Appunti

2022/2023

In vendita dal 05/06/2023

rossimartaa
rossimartaa 🇮🇹

4.3

(18)

156 documenti

1 / 11

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
INTRODUZIONE ALL’EREDITARIETÀ
EREDITARIETÀ: CONCETTI DI BASE
L’ereditarietà è uno dei concetti base della programmazione ad oggetti. L’ereditarietà è la possibilità
di ridefinire un codice incapsulato all’interno di un metodo per variarne l’implementazione del
comportamento.
Concetto di base:
- L’ereditarietà permette di usare una classe precedentemente definita per la definizione di
una nuova: mentre l’overloading permetteva, all’interno di una classe, di riutilizzare un
codice già definito, l’ereditarietà permette di utilizzare le caratteristiche di una classe già
definita per definirne una nuova. Ciò aumenta la capacità espressiva del linguaggio e mi
permette di caratterizzare in maniera completa, da un punto di vista tassonomico, un
dominio.
(Esempi: classe veicolo o classe animale per arrivare ad un oggetto cane concreto (astrazione
della realtà proveniente da un’osservazione puramente umana))
L’ereditarietà è una tecnica tale per cui è possibile definire:
- una classe estremamente generale
- successivamente classi più specializzate semplicemente aggiungendo nuovi dettagli alla
classe di partenza
EREDITARIETÀ: DEFINIZIONE PIÙ FORMALE
Da un punto di vista formale, l’ereditarietà è una relazione tra classe in cui una classe viene detta
“superclasse” o “classe base” e raccoglie a fattor comune le caratteristiche comuni di una o più altre
classi (sottoclassi o specializzazioni). Le caratteristiche comuni sono quelle che ne derivano lo stato e
il comportamento: attributi e metodi.
Le classi in cui vengono raccolti attributi e metodi sono dette “sottoclassi” o “specializzazioni”.
Le sottoclassi ereditano tutte le caratteristiche della superclasse.
In UML si indica graficamente
con una freccia dalla sottoclasse
alla superclasse.
Le sottoclassi avranno delle
caratteristiche in comune (ruote,
motore…) ma avranno
sicuramente anche delle
differenze: specificherò le
differenze nella realizzazione di ciascuna di esse mentre le caratteristiche comuni le inserirò nella
rappresentazione della superclasse. In questo modo si hanno due grandi vantaggi: la compattezza
della rappresentazione e la possibilità di avere maggior controllo sul codice che sto scrivendo. Avere
un unico punto in cui definisco le caratteristiche rende il codice più gestibile da un punto di vista
della manutenzione: un eventuale errore viene corretto unicamente nella classe base, ma
automaticamente sarà corretto anche nelle classi figlie.
pf3
pf4
pf5
pf8
pf9
pfa

Anteprima parziale del testo

Scarica Algoritmi e programmazione: ereditarietà e più Appunti in PDF di Algoritmi E Programmazione Avanzata solo su Docsity!

INTRODUZIONE ALL’EREDITARIETÀ

EREDITARIETÀ: CONCETTI DI BASE

L’ereditarietà è uno dei concetti base della programmazione ad oggetti. L’ereditarietà è la possibilità di ridefinire un codice incapsulato all’interno di un metodo per variarne l’implementazione del comportamento. Concetto di base:

  • L’ereditarietà permette di usare una classe precedentemente definita per la definizione di una nuova: mentre l’overloading permetteva, all’interno di una classe, di riutilizzare un codice già definito, l’ereditarietà permette di utilizzare le caratteristiche di una classe già definita per definirne una nuova. Ciò aumenta la capacità espressiva del linguaggio e mi permette di caratterizzare in maniera completa, da un punto di vista tassonomico, un dominio. (Esempi: classe veicolo o classe animale per arrivare ad un oggetto cane concreto (astrazione della realtà proveniente da un’osservazione puramente umana)) L’ereditarietà è una tecnica tale per cui è possibile definire:
  • una classe estremamente generale
  • successivamente classi più specializzate semplicemente aggiungendo nuovi dettagli alla classe di partenza EREDITARIETÀ: DEFINIZIONE PIÙ FORMALE Da un punto di vista formale, l’ereditarietà è una relazione tra classe in cui una classe viene detta “superclasse” o “classe base” e raccoglie a fattor comune le caratteristiche comuni di una o più altre classi (sottoclassi o specializzazioni). Le caratteristiche comuni sono quelle che ne derivano lo stato e il comportamento: attributi e metodi. Le classi in cui vengono raccolti attributi e metodi sono dette “sottoclassi” o “specializzazioni”. Le sottoclassi ereditano tutte le caratteristiche della superclasse. In UML si indica graficamente con una freccia dalla sottoclasse alla superclasse. Le sottoclassi avranno delle caratteristiche in comune (ruote, motore…) ma avranno sicuramente anche delle differenze: specificherò le differenze nella realizzazione di ciascuna di esse mentre le caratteristiche comuni le inserirò nella rappresentazione della superclasse. In questo modo si hanno due grandi vantaggi: la compattezza della rappresentazione e la possibilità di avere maggior controllo sul codice che sto scrivendo. Avere un unico punto in cui definisco le caratteristiche rende il codice più gestibile da un punto di vista della manutenzione: un eventuale errore viene corretto unicamente nella classe base, ma automaticamente sarà corretto anche nelle classi figlie.

ASTRAZIONE

Nel mondo delle classi non abbiamo concretezza: abbiamo concretezza nel momento in cui istanziamo un oggetto. Facendo ciò, creiamo delle entità che agiscono nel mondo reale. Possiamo quindi creare anche delle architetture di classi necessarie per rappresentare un dominio da un punto di vista concettuale ma avremo istanziazione solo su una di queste classi. Riusciamo quindi a costruire un legame semantico con la realtà tramite la doppie via mondo reale → superclassi. PERCHÉ L’EREDITARIETÀ Economia

  • tutti gli attributi, operazioni e associazioni comuni a un certo insieme di classi vengono scritte una sola volta Consistenza
  • se un attributo, operazione o associazione della superclasse viene cambiato, la modifica si ripercuote automaticamente su tutte le sottoclassi Riuso
  • la superclasse può essere riusata in contesti diversi Estendibilità
  • è sempre possibile, anche in un secondo momento, aggiungere nuove sottoclassi senza dover riscrivere la parte comune contenuta nella superclasse
  • ogni sottoclasse può ridefinire a piacimento attributi e operazioni (overriding). L’overloading è la ridefinizione di un concetto all’interno della stessa classe: con l’overriding invece ridefiniremo attributi e metodi passando da una classe all’altra, da un metodo di una prima classe ad un metodo di una seconda classe o classe genitore. Concettualmente avrò lo stesso passaggio ma sarà totalmente differente da un punto di vista implementativo: sarà una specializzazione dovuta non solo ad un cambiamento di un set di parametri ma anche ad un cambiamento di livello della gerarchia. è dunque diverso da un punto di vista semantico: qui stiamo andando a un livello più specifico nella gerarchia dell’ereditarietà. Non estendo il codice ma ridefinisco il metodo rispetto a quello definito nella classe genitore.

EREDITARIETÀ MULTIPLA

Una classe può ereditare da più superclassi (ereditarietà multipla). In questo caso la sottoclasse eredita le caratteristiche di entrambe le superclassi. Esempio:

  • lo studente lavoratore avrà un nome, cognome e numero di Previdenza ereditate dalla classe Lavoratore e un nome, cognome (uguali) e un numero di matricola dalla classe Studente. EREDITARIETÀ MULTIPLA: PROBLEMI L’ereditarietà multipla è semplice concettualmente, ma porta a una serie di problemi tecnici:
  • Cosa succede se una classe eredita due volte dalla stessa superclasse?
  • Cosa succede se due superclassi diverse hanno un attributo con lo stesso nome? Fortunatamente Java non supporta l’ereditarietà multipla Potrei rappresentare la gestione raccogliendo a fattor comune il nome e cognome in una classe persona, ma poi il problema risulterebbe capire da quale classe andare a recuperare questi attributi. Il problema risulta essere più evidente se le classi da gestire sono n e gli attributi comuni sono m. In questo caso il problema si ribalterebbe sulla gerarchia.

L’EREDITARIETÀ IN JAVA

La parola chiave utilizzata per l’ereditarietà è "extends". La sintassi utilizzata in Java per esplicitare che una classe deriva da un’altra è la seguente: public class ClassePadre { ... } public class ClasseFiglia extends ClassePadre { ... } In questo modo, tutto ciò che è stato definito nella classe padre verrà incluso nella definizione della classe figlia, venendo richiamato a run time nella creazione degli oggetti. Si osservi che tutto ciò viene fatto a run-time (non lo si vede esplicitamente). ESEMPIO Proviamo ad implementare il seguente diagramma di classi

  • nessun costruttore e metodo toString() Creiamo un’altra classe con main in cui:
  • creiamo una persona con nome Mario, cognome Rossi
  • creiamo uno studente con nome Marco, cognome Verdi e matricola 123456
  • stampiamo lo stato degli oggetti Sorgente ... In studente estendo gli attributi di Persona con matricola e il set e get. public class Persona { private String nome; private String cognome; public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public String getCognome() { return cognome; } public void setCognome(String cognome) { this.cognome = cognome; } public String toString() {

Se cancellassi anche il toString della classe persona e ricompilassi il codice all’interno della cartella sorgente, il codice funzionerebbe nuovamente. Da dove deriva quindi il toString? Se non definissi il toString all’interno della classe, verrebbe stampato l’indirizzo di memoria. La stampa risulterebbe Persona@4617c Studente@7a81197d Non riesco a stampare lo stato perché non l’ho definito io ma posso stamparne l’identità. L’insieme alfanumerico di numeri dopo @ (hashCode) deriva dalla classe object di toString(). Infatti ogni volta che definisco una classe in Java, richiamo una serie di metodi definiti nella classe Object(), che può essere considerata come la classe root della gerarchia delle classi. Ogni volta che scriviamo una classe in java implicitamente stiamo estendendo una classe object, quindi implicitamente richiamiamo una serie di metodi che sono naturalmente definiti nella classe object, come l’equals(), il toString()... Quando implementiamo nelle nostre classi il toString() senza saperlo stavamo facendo un overriding e di conseguenza quando poi lo richiamiamo viene invocato quello che abbiamo riscritto. Se invece non ridefiniamo il toString() e lo richiamiamo, automaticamente si usa il toString() della classe object che stampa l’identità dell’oggetto. Un’altra cosa che ereditiamo dalla classe object è il costruttore di default, per questo quando definisco un costruttore questo viene eliminato, poichè viene sovrascritto. Con la keyword super prima di toString (super.toString()) stamperei lo stato della superclasse. LA CLASSE OBJECT In Java qualsiasi classe specializza la classe Object. Fino ad ora, non sapendolo, abbiamo scritto classi che sono specializzazione della classe Object

  • quindi qualsiasi metodo ed attributo definito nella classe Object è ereditato dalle sottoclassi Alcuni metodi importanti della classe Object:
  • public boolean equals(Object o)
  • public String toString()

ESEMPIO

Se modifichiamo il main dell’esercizio della slide 12 nel modo seguente: ... Non avremo errori di compilazione:

  • su oggetti di tipo Persona è possibile invocare il metodo toString() poiché ereditato dalla classe Object Output del tipo: NomeClasse@hashCode public class Test { public static void main(String a[]) { Persona p = new Persona(); p.setNome("Mario"); p.setCognome("Rossi"); Studente s = new Studente(); s.setNome("Marco"); s.setCognome("Verdi"); s.setMatricola(123456); System.out.println("Persona "+ p.getNome() + " " + p.getCognome()); System.out.println("Studente "+ s.getNome() + " " + s.getCognome() + " " + s.getMatricola()); System.out.println(p.toString()); } } VISIBILITÀ Quando una classe ne specializza un’altra, eredita tutto a prescindere dalla visibilità! Però:
  • se un attributo è definito private nella superclasse non sarà accessibile tramite nome!
  • Occorrerà passare attraverso metodi (ovviamente non privati!)
  • se un metodo è definito private nella superclasse, non ci sarà alcun modo per permettere alle sottoclassi di invocarlo Se le sottoclassi hanno la necessità di accedere in modo diretto ad attributi e/o metodi definiti nella superclasse devo utilizzare un’altro tipo di visibilità: protected La keyword Protected mi permette di mantenere il tipo di visibilità pubblica all’interno della gerarchia delle sottoclassi, ma privata per tutte le altre. Esempio: Auto + Taxi

COSTRUTTORI

Cosa succede per i costruttori che accettano in ingresso parametri? Regola da rispettare: ogni classe inizializza i propri attributi!

  • ritornando all’esempio della Persona e della classe Studente
    • il/i costruttori della classe Persona inizializzeranno gli attributi nome e cognome
    • il/i costruttori della classe Studente inizializzeranno l’attributo matricola
  • ma uno studente è caratterizzato anche dal possedere un nome e un cognome dal momento che è una specializzazione della classe Persona - quindi, quando si crea uno studente, occorre inizializzare anche nome e cognome Come si fa? Esistono 2 modi: Meno elegante:
  • nel/nei costruttori della sottoclasse si inizializzano i valori per tutti gli attributi Più elegante:
  • si invoca il costruttore della superclasse passando come argomenti i valori per gli attributi li definiti
  • per fare ciò si utilizza la keyword super Come abbiamo keyword this che permette overloading del costruttore in una stessa classe avremo keyword this che permette overriding di un metodo. L’invocazione al costruttore della superclasse deve essere la prima istruzione ad essere eseguita: quindi non ci possono essere contemporaneamente super e this: infatti ciò valeva anche per la keyword this, che deve essere la prima istruzione ad essere eseguita. Super è l’overriding del costruttore. Non possiamo dunque fare contemporaneamente l’overriding e overloading di un costruttore: se abbiamo più costruttori ,prima estendiamo costruttore della superclasse con overriding e poi facciamo overloading all’interno della sottoclasse ogni volta che lo necessito. Il super può essere usato anche sui metodi. REGOLE Sono sempre obbligato ad invocare esplicitamente il costruttore della superclasse?
  • No: se la superclasse esporta il costruttore di default
  • Si: se la superclasse esporta esclusivamente costruttori con parametri Se la superclasse esporta il costruttore di default, non sono obbligato a invocarlo direttamente, ma questo verrà richiamato implicitamente. Invece se nella superclasse ho ridefinito il costruttore con parametri, nelle sottoclassi sono obbligato a invocarlo. Anche se io non invoco esplicitamente il costruttore della superclasse, Java inserisce all’interno dei costruttori definiti nella sottoclasse l’invocazione al costruttore di default della superclasse
  • ciò comporta che il primo costruttore ad essere eseguito è sempre quello della classe Object
  • ciò è valido se il costruttore non ha l’invocazione this! NB: Se la superclasse usa costruttore di default (anche ridefinito, non esclusivamente quello della classe object), non sono obbligato a richiamarlo, ma in realtà se io non lo richiamo viene fatto implicitamente. Il super vale anche sui metodi. Se ho definito un metodo sulla superclasse lo richiamo con la keyword super e aggiungo la parte nuova riguardante esclusivamente l’oggetto in cui mi trovo. IMPLEMENTAZIONE RELAZIONE IS‐A Ricordandoci che l’ereditarietà realizza una relazione is‐a, è lecito scrivere: SuperClasse ref = new SottoClasse(); In questo modo sto assegnando un ref di una superclasse ad uno di una sottoclasse. Ma non è lecito scrivere: SottoClasse ref = new SuperClasse(); In questo modo assegnerei ad un ref più specifico (sottoclasse) un qualcosa di meno specifico: ciò non mi è permesso dal compilatore. ESEMPI Dovrò accertarmi che nel mio oggetto di partenza io abbia a disposizione gli strumenti che utilizzo. Qui ho un errore a run time: il metodo setcognome in realtà non c’è e quindi l’istruzione non sarà consentita. Nella realtà sto lavorando su dei reference che fanno riferimento a delle informazioni che non ho a compile time ma a run time.