Lezione 5 · Canale 1 · venerdì 2 ottobre 2026
Ereditarietà, classi astratte e associazioni UML
Progettazione del Software
Triangolo: getter senza setter e area
Un triangolo ha tre lati privati. L'ambiente di sviluppo genera i getter da un campo: da lato1 ricava getLato1. Si generano solo i getter, non i setter: il vincolo sui lati (la proprietà triangolare) si controlla una volta sola nel costruttore e il triangolo non cambia più. Nel codice conviene raggruppare prima i campi, poi getter e setter, poi gli altri metodi. Il codice generato va sempre controllato.
L'area si calcola con la formula di Erone, dal semiperimetro e dai lati , , :
double s = (lato1 + lato2 + lato3) / 2;
return Math.sqrt(s * (s - lato1) * (s - lato2) * (s - lato3));
Derivazione e sostituibilità
Una classe derivata (extends) eredita le proprietà della classe base e può aggiungerne altre. Ogni oggetto della classe derivata è anche un oggetto della classe base: si può usare ovunque serva uno della classe base. Il viceversa non vale.
Esempio: Rettangolo e Triangolo derivano da Figura, Quadrato deriva da Rettangolo. Una variabile ha un tipo, e riferisce oggetti di quel tipo e delle sue sottoclassi:
| Variabile di tipo | Può riferire |
|---|---|
Figura | rettangoli, quadrati, triangoli |
Rettangolo | rettangoli, quadrati |
Quadrato | quadrati |
Un oggetto creato con new Rettangolo(…) è di una sola classe, Rettangolo: non è un Quadrato. Una variabile comprende invece anche le sottoclassi: un insieme di figure può contenere oggetti di classi diverse, e un HashSet<Rettangolo> accetta quadrati ma non triangoli.
La gerarchia Java rappresenta l'inclusione fra insiemi in modo solo approssimato. Due classi sono una dentro l'altra oppure disgiunte: Java non ammette classi che si intersecano senza che una sia sottoclasse dell'altra.
Un metodo nella superclasse
Per sommare i perimetri di un insieme di figure si scorre l'insieme con un ciclo:
HashSet<Figura> S = new HashSet<Figura>();
// … S.add(new Rettangolo(…)); S.add(new Triangolo(…)); …
double totale = 0.0;
for (Figura f : S)
totale += f.perimetro();
Dichiarare S crea soltanto un riferimento: come per ogni oggetto, l'insieme vuoto si crea con new. Il compilatore controlla il tipo della variabile: f è una Figura, e perimetro non è definito in Figura, quindi dà errore. Eppure ogni figura deve avere perimetro e area, quindi l'operazione va dichiarata nella superclasse. Con un corpo provvisorio (return -1000;, uno stub) il programma compila.
Il totale somma i perimetri veri delle figure, non lo stub: a ogni chiamata si esegue il perimetro della classe dell'oggetto, non del tipo della variabile. Quadrato non lo ridefinisce e usa quello di Rettangolo: un quadrato è un rettangolo con base uguale all'altezza, quindi il perimetro ereditato è corretto.
Lo stub -1000 non calcola nulla, e una figura generica non ha senso come oggetto. Il passo successivo è la classe astratta.
Classe astratta e metodo astratto
abstract public class Figura {
private String descrizione;
public Figura(String descrizione) {
if (descrizione == null)
throw new IllegalArgumentException();
this.descrizione = descrizione;
}
abstract public double perimetro();
}
- Classe astratta: non ha oggetti propri, e
new Figura(…)è un errore. Una variabile di tipoFiguraresta valida e riferisce oggetti delle sottoclassi. Ogni oggetto appartiene a una sola classe concreta come classe più specifica: rettangolo, quadrato o triangolo, senza figure «generiche» in più. - Costruttore: esiste lo stesso. Lo invocano i costruttori delle sottoclassi con
super(…), che deve essere la prima istruzione. Così il controllo sulla descrizione vale per ogni oggetto creato. - Metodo astratto:
perimetroè dichiarato senza corpo. Tutte le figure hanno un perimetro, ma non si può calcolare senza sapere quale figura sia. Ogni sottoclasse concreta deve realizzarlo.Quadrato, che eredita un corpo già valido daRettangolo, non deve riscriverlo.
public class Rettangolo extends Figura {
private double base;
private double altezza;
public Rettangolo(String descrizione, double base, double altezza) {
super(descrizione);
this.base = base;
this.altezza = altezza;
}
public double perimetro() { return 2 * (base + altezza); }
}
Perché il quadrato estende il rettangolo
Il quadrato ha un lato e il rettangolo due, quindi potrebbe sembrare naturale che Rettangolo estenda Quadrato aggiungendo il secondo lato. Ma essere sottoclasse ha due conseguenze:
- eredita tutte le proprietà della superclasse;
- un suo oggetto si usa ovunque serva uno della superclasse.
Con Rettangolo extends Quadrato un insieme di rettangoli non accetterebbe più i quadrati, e un insieme di quadrati accetterebbe rettangoli che non sono quadrati. Con Quadrato extends Rettangolo valgono entrambe. I lati sono privati e senza setter: un quadrato nasce con base uguale all'altezza e resta così.
Classi e associazioni in UML
Ogni classe è un rettangolo. Una freccia è una generalizzazione (relazione is-a): ogni istanza della sottoclasse è anche istanza della superclasse. Una linea è un'associazione fra oggetti di classi diverse.
Esercizio: una banca con Persona, Cliente, Consulente, Appuntamento e Conto.
ClienteeConsulentesono sottoclassi diPersona: generalizzazione, in Javaextends Persona.AppuntamentoeContonon sono tipi particolari di persona o di cliente: sono classi a sé, collegate da associazioni (linee).Personapuò essere astratta, se le sole persone che interessano sono clienti e consulenti: non si usanew Persona(…), si creano clienti o consulenti. Una classeBancanon serve: si modella una banca sola.
Chi raccoglie i requisiti deve verificare ciò che l'esperto del dominio dà per scontato, per esempio se oltre a clienti e consulenti interessano altri ruoli (impiegati, direttori).
Molteplicità
La molteplicità x..y indica quante istanze di una classe sono legate a una istanza dell'altra: almeno e al più . Si scrive dal lato della classe contata. 0..* (nessun vincolo) si può omettere; 1..1 vuol dire esattamente uno.
| Associazione | Si legge |
|---|---|
Cliente, Consulente | un cliente ha esattamente un consulente; un consulente ha un numero qualsiasi di clienti |
Cliente, Conto | un cliente ha da 1 a 5 conti; un conto ha da 1 a 5 clienti |
Appuntamento, Consulente | un appuntamento ha un solo consulente; un consulente ha appuntamenti senza limite |
Appuntamento, Cliente | un appuntamento ha al più 5 clienti; un cliente ha un numero qualsiasi di appuntamenti, anche nessuno |
Sono possibili associazioni uno a uno (un cliente e il suo indirizzo), uno a molti e molti a molti.
Realizzare le associazioni in Java
Se interessa sapere i clienti di un consulente e il consulente di un cliente, i riferimenti stanno in entrambe le classi. Le sottoclassi riusano i dati di Persona (nome, cognome e data di nascita; qui la data è omessa) e il loro costruttore chiama super(…), perché Persona ha un costruttore con parametri.
public class Consulente extends Persona {
private HashSet<Cliente> clienti = new HashSet<Cliente>();
public Consulente(String nome, String cognome) {
super(nome, cognome);
}
}
public class Cliente extends Persona {
private Consulente consulente;
private HashSet<Conto> conti = new HashSet<Conto>();
public Cliente(String nome, String cognome) {
super(nome, cognome);
}
}
Consulente e Cliente hanno gli stessi dati scalari di Persona: li distinguono le associazioni. L'associazione con Conto è simmetrica (ogni conto ha un insieme di clienti), quella con Consulente no (un solo consulente da un lato, un insieme di clienti dall'altro).
Un cliente può nascere senza conti e riceverli dopo. I titolari di un conto invece restano gli stessi per sempre: per cambiarli si crea un nuovo conto.
Oggetti collegati e database
In un database relazionale ogni classe è una tabella, ogni oggetto una riga e ogni associazione una tabella che dice chi è collegato a chi. Con collegamenti diretti, il cliente ha un riferimento al consulente e il consulente un insieme di riferimenti ai clienti: è la rappresentazione dei database reticolari, usata anche per le strutture in memoria RAM di un programma in esecuzione. I database relazionali servono invece alla memorizzazione stabile su disco.
Per garantire un vincolo come «da 1 a 5 titolari» non basta un insieme privato con il solo getter: chi riceve il riferimento all'insieme può modificarne il contenuto.
Formulario
Derivazione
- la derivata eredita i campi
- può averne di nuovi
- un suo oggetto vale come base
- il viceversa non vale
Classe astratta
- niente
newsulla classe - la variabile è valida
- costruttore:
super(…)prima
Metodo astratto
- senza corpo
- lo realizzano le sottoclassi
- si esegue quello dell'oggetto
UML
- freccia: sottoclasse,
extends - linea: associazione
Molteplicità x..y
- almeno , al più
0..*: nessun vincolo1..1: esattamente uno
Area del triangolo