Sei sulla pagina 1di 21

EREDITARIETA’

Definizione di ereditarietà strutturale (sub-typing)


Definizione di ereditarietà
Tra gli elementi più importanti che caratterizzano il paradigma di
sviluppo OOP c'è la Gerarchia delle classi, quindi è abbastanza intuitivo
comprendere quanto sia centrale il concetto di Ereditarietà in java.
Si dice che una classe A è una sottoclasse di B(e allo stesso tempo B è
una superclasse di A) quando:
• A eredita da B sia il suo stato che il suo comportamento
• E quindi una istanza della classe A è utilizzabile in ogni parte del
codice in cui sia possibile utilizzare una istanza della classe B
Ereditarietà strutturale (sub-typing)
Analizziamo ora la più semplice forma di ereditarietà disponibile in
Java(che possiamo definire strutturale o sub-typing)ma teniamo
presente che la definizione di ereditarietà che abbiamo dato prima è
realizzabile in almeno due modi:
• Il primo consiste nell’esprimere il fatto che un elemento della classe A
è anche un elemento della classe B (un cerchio è anche una figura
piana);
• Il secondo consiste nel dire che gli elementi della classe A si
comportano come elementi della classe B (il cerchio ha un’area ed un
perimetro proprio come tutte le figure piane);
Extends, estendere (o derivare da) una classe in Java

In Java questa relazione tra le classi viene resa con la keyword extends
che deve essere usata nella dichiarazione della classe:

class A extends B {
// ...
}
Implica che ogni istanza della classe A sarà anche di tipo B ed avrà a
disposizione tutti i metodi della classe B e nel suo stato saranno
presenti tutte le variabili che si trovano nella super classe B.
/** /**
* ColloInVendita è la classe "derivata"
* Collo è la classe "base"
*/
*/ public class ColloInVendita extends Collo {
public class Collo { // dati (oltre quelli di Collo)
// dati private int price;

private int x_size, y_size, z_size; // coefficienti da applicare alla vendita

protected int weight; private static final float A0 = 1;


private static final float B0 = 1.2;
// funzione getter di Weight
private static final float C0 = 1.5;
public int getWeight() { return weight; } public int getPrice() {
// Costruttore return price;
public Collo(int w, int xs, int ys, int zs) { }
this.weight = w; // Costruttore della classe derivata

this.x_size = xs; public ColloInVendita(int w, int xs, int ys, int zs, int price) {
// richiama il costruttore della classe base
this.y_size = ys;
super(w, xs, ys, zs);
this.z_size = zs;
this.price = price;
} }
public int getVolume() { public float getDeliveryCost() {
return x_size * y_size * z_size; return A0*weight + B0*getVolume() + C0*price;

} }

} }
E’ utile osservare come, nella dichiarazione di ColloInvendita, siano utilizzabili sia i metodi che i
campi (a patto che siano public o protected) della super-classe Collo, che possono essere
eventualmente prefissati con la keyword super, utile per eventuali disambiguazioni.

Una nota speciale merita il costruttore, infatti il costruttore della classe ColloInVendita (derivata)
deve essere in grado di costruire una istanza della classe Collo e quindi se per quest'ultima non è
previsto un costruttore 'default' (senza argomenti) la classe derivata lo dovrà chiamare
esplicitamente passandogli gli argomenti necessari con la sintassi super(...) che deve essere
obbligatoriamente il primo statement del costruttore della classe figlia.

Leggendo la dichiarazione della classe Collo, dove non compare la keyword extends, potrebbe
nascere la convinzione che questa classe non derivi da nessuna "super-class"; per la verità vale la
pena di osservare che in Java ogni classe deriva da almeno una classe genitrice che, se la
keyword extends viene omessa, è per default la classe java.lang.Object, dalla quale ogni oggetto
Java eredita, per esempio, i metodi hashCode(), getClass(), toString().
Incapsulamento
Definizione di Incapsulamento
Nei programmi Object Oriented si è soliti mettere in stretta relazione
tra loro un pezzo di informazione con il comportamento specifico che
agisce su tale informazione. Questo è ciò che abbiamo definito oggetto.
L'incapsulamento è proprio legato al concetto di "impacchettare" in un
oggetto i dati e le azioni che sono riconducibili ad un singolo
componente.
Un altro modo di guardare all'incapsulamento è quello di pensare a
suddividere un'applicazione in piccole parti (gli oggetti, appunto) che
raggruppano al loro interno alcune funzionalità.
Esempio pratico
Ad esempio, pensiamo ad un conto bancario. Le informazioni utili (le proprietà)
potranno essere rappresentate da: il numero di conto, il saldo, il nome del cliente,
l'indirizzo, il tipo di conto, il tasso di interesse e la data di apertura.

Le azioni che operano su tali informazioni (i metodi) saranno, invece: apertura,


chiusura, versamento, prelevamento, cambio tipologia conto, cambio cliente e cambio
indirizzo. L'oggetto Conto incapsulerà queste informazioni e azioni al suo interno.

Come risultato, ogni modifica al sistema informatico della banca che implichi una
modifica ai conti correnti, potrà essere implementata semplicemente nell'oggetto
Conto.

Un altro vantaggio derivante dall'incapsulamento è quello di limitare gli effetti


derivanti dalle modifiche ad un sistema software.
Cerchiamo ora di esaminare una situazione che preveda una modifica da apportare
rimanendo nel contesto del sistema bancario di cui abbiamo definito proprietà e metodi.
Supponiamo che la banca in questione abbia deciso che se un determinato cliente ha un
conto di credito nella stessa banca, tale conto possa essere utilizzato come copertura per
eventuali scoperti sul conto corrente.

Per gestire tale situazione, in un sistema non incapsulato, si procederebbe con un


approccio decisamente poco efficace. Ovvero, non sapendo dove siano localizzati nel
codice i punti in cui viene gestita l'operazione di prelievo, l'unica alternativa possibile
sarebbe quella di ricercare dappertutto, aggiungendo la nuova funzionalità al programma
ogni volta che si identifichi un punto da modificare.

Con buona probabilità, se saremo stati bravi, saremo riusciti ad individuare l'80 per cento
dei punti da modificare. Con l'utilizzo dell'incapsulamento sarà, invece, sufficiente
identificare l'oggetto che gestisce l'azione di prelevamento (ovvero l'oggetto Conto) ed
eseguire la modifica all'interno di esso, completando la variazione del sistema richiesta
senza intaccare tutto il resto del sistema stesso.
Come fare per effettuare correttamente l’incapsulamento?

Per effettuare correttamente l’incapsulamento in una classe Java basta


inserire il modificatore di accesso ‘private’ per tutti i campi della classe
interessata, in modo da poterli visualizzare o nel caso cambiare valore
tramite i metodi getter e setter (o anche rispettivamente ‘accessor’ e
‘mutator’);

public class Pippo{


private String nome;
private String cognome;

//metodi getter e setter per l’accessibilità ai campi


}
Polimorfismo
Polimorfismo in Java
Il concetto di Polimorfismo, nell'ambito dei linguaggi di
programmazione, si riferisce in generale alla possibilità data ad una
determinata espressione di assumere valori diversi in relazione ai tipi di
dato a cui viene applicata.
Secondo questa generica definizione, è dunque polimorfa anche
l'espressione:
a+b
che, in base al tipo di a e b, potrebbe rappresentare un valore di tipo
int, float oppure addirittura di tipo String (ed in tal caso operare il
concatenamento delle String a e b).
Tipi di Polimorfismo
Tecnicamente parlando in un linguaggio non tipato o dinamicamente
tipato (e.g. il Javascript) ogni espressione è polimorfa, mentre in un
linguaggio staticamente tipato come Java, la questione è assai più
articolata; nel contesto della programmazione OO e del linguaggio Java
si possono infatti distinguere almeno 3 tipi di polimorfismo:
• ad hoc polymorphism
• inclusion polymorphism
• parametric polymorphism (o generic programming)
Per ora ci concentriamo su i primi due tipi. Il terzo è legato ad una
feature di Java detta generics, quindi non avendo le basi non verrà
trattata.
1- Ad hoc polymorphism, Overload dei metodi
Prendiamo per esempio la situazione in cui abbiamo la classe Rettangolo e
dobbiamo implementare il metodo ‘sommaLati’. Andremo ad implementare la
formula più semplice per il calcolo della somma, ad esempio:

Somma = l1 + l2 +l3 +l4

Nel caso in cui parliamo di lati non decimali, possiamo implementare il metodo più
semplice che ci viene in mente, tipo:

public int plusMethod(int l1, int l2, int l3, int l4) {
return l1 + l2 + l3 + l4;
}
Overload dei metodi
Ma nel caso in cui andiamo ad operare su un oggetto Rettangolo che però ha tutti i lati decimali?
Ovviamente non possiamo utilizzare questo metodo bensì avremo bisogno di qualcosa simile a:
public double plusMethod(double l1, double l1, double l1, double l1){
return l1 + l2 + l3 + l4;
}
Poiché i 2 metodi hanno parametri diversi (4 int il primo, 4 double il secondo) Java è in grado di
capire, durante la compilazione (a "compile-time"), quale è il metodo che intendiamo utilizzare e
decidere quindi quale invocare.
Se il linguaggio non permettesse l'overloading (es. il C) potremmo comunque implementare i 2
metodi ma saremmo costretti a utilizzare per loro due nomi diversi (per esempio plusMethodInt e
plusMethodDouble) e ottenere lo stesso risultato, al costo però di commettere una, seppure piccola,
violazione del principio di incapsulamento avendo dovuto esplicitare nel nome di un messaggio
(metodo) alcune informazioni circa la sua implementazione, cosa che, se evitabile, è bene non fare.
Ridefinire i metodi ereditati (override o "Inclusion polymorphism")

A differenza del polimorfismo 'ad hoc' questa forma di polimorfismo è


strettamente legata al concetto di ereditarietà e di sub-typing e consiste nella
possibilità che una sottoclasse A di una data classe B ridefinisca uno dei metodi
della super-classe e che quindi quando verrà utilizzata una istanza della classe A le
invocazioni al metodo ridefinito (spesso detto overridden) eseguiranno il codice
definito nella sotto-classe.
In Java per default tutti i metodi non private sono ridefinibili ma è possibile
specificare la keyword final per istruire il compilatore a non ammetterne la
ridefinizione.
-

public class B { public class A extends B {


public int metodo(int i, int j) { public int metodo(int i, int j) {
return i-j;
return i+j;
}
}
// questa ridefinizione non è ammessa in
public final int metodoFinal(int i, quanto il metodo è final
int j) { // Infatti se de-commentiamo queste righe il
return i+j; compilatore
} // segnala un errore
/*
}
public int metodoFinal(int i, int j) {
return i-j;
}
*/
}
-

public static void main(String[] args) { • La prima riga dell'output ci dice che è stato
B b = new B(); chiamato il metodo metodo così come
A a = new A(); implementato nella classe B e sia la variabile
B aa = new A(); // A è sottoclasse di B quindi è che l'istanza sono di tipo B.
// assegnabile a variabili di tipo B
int c;
c = b.metodo(5, 7); • Il secondo risultato indica che è stata utilizzata
System.out.println(c); l'implementazione data nella classe A, poiché
c = a.metodo(5, 7); sia la variabile che l'istanza sono di tipo A.
System.out.println(c);
c = aa.metodo(5, 7);
System.out.println(c);
• Il fatto che l'ultima riga di output sia -2 implica
}
invece che nonostante la variabile aa sia di
In console otteniamo: tipo B, l'oggetto ritornato dal costruttore A(),
12 benchè assegnabile a una variabile di tipo B,
-2 ha dentro di sé riferimenti ai metodi come
-2 definiti per la classe A.
Esercizi
1- Il gestore di un negozio associa a tutti i suoi Prodotti un codice a barre univoco, una descrizione sintetica del prodotto e il suo prezzo unitario.
Realizzare una classe Prodotti con le opportune variabili d'istanza e metodi get.

1a-Aggiungere alla classe Prodotti un metodo applicaSconto che modifica il prezzo del prodotto diminuendolo del 5%.

1b-Aggiungere alla classe Prodotti un'implementazione specifica dei metodi ereditati dalla classe Object.

2-Il gestore del negozio vuole fare una distinzione tra i prodotti Alimentari e quelli Non Alimentari . Ai prodotti Alimentari viene infatti associata
una data di scadenza (si veda la classe LocalDate), mentre a quelli Non Alimentari il materiale principale di cui sono fatti. Realizzare le sottoclassi
Alimentari e NonAlimentari estendendo opportunamente la classe Prodotti.

3-Modificare le due sottoclassi dell'esercizio 2 specializzando il metodo applicaSconto in modo che nel caso dei prodotti Alimentari venga
applicato uno sconto del 20% se la data di scadenza è a meno di 10 giorni dalla data attuale (si usi il metodo getDifference della classe Data),
mentre nel caso dei prodotti Non Alimentari venga applicato uno sconto del 10% se il prodotto è composto da un materiale riciclabile (carta, vetro
o plastica).

4-Realizzare una classe ListaSpesa che chieda all'utente di inserire i prodotti acquistati e calcoli il totale della spesa applicando gli opportuni sconti
se il cliente ha la tessera fedeltà.
Esercizi 2
• 1 Scrivere un programma Java che chiede all’utente di inserire due stringhe e
che visualizza all’utente true se le stringhe sono uguali e false se sono diverse.

• 2 Scrivere un programma che effettui la somma, la sottrazione, la


moltiplicazione e la divisione tra due numeri interi

• 3 Scrivere lo stesso programma del punto 2 ma effettuando le operazioni tra


due numeri double ed effettuare il recast in tipo Integer

Potrebbero piacerti anche