PROGRAMMAZIONE
CLASSI CUSTOM
CONCETTI BASE
DEV Prof. Joshua B.
REMINDER : CLASSI E OGGETTI
Ripassiamo la differenza tra classe e oggetto (visto nelle precedenti lezioni).
Classe → è un modello (template) che de inisce le proprietà e il comportamento di un oggetto.
Oggetto → è un'istanza concreta della classe, con valori speci ici per gli attributi.
Costruttore (…….)
Manuale IKEA = Classe Sedia = Oggetto
f
f
INTRODUZIONE
Immagina di dover progettare un software per gestire un’automobile, un negozio online o un
videogioco. In tutti questi casi, il programma deve rappresentare elementi reali come automobili,
prodotti o personaggi. La Programmazione Orientata agli Oggetti (OOP) nasce proprio per questo:
permette di modellare il software sulla base del mondo reale, rendendo il codice più intuitivo,
organizzato e facile da mantenere.
Cos’è la programmazione ad oggetti?
L’OOP è un paradigma di programmazione in cui il software viene costruito attorno a oggetti,
che rappresentano entità con proprietà (dati) e comportamenti (funzionalità). Questi oggetti
interagiscono tra loro, proprio come avviene nella realtà
INTRODUZIONE
Perché l’OOP è importante?
Modellazione realistica 🏗 → Permette di rappresentare concetti del mondo reale nel codice (es.
un’auto avrà una marca e un motore).
Riutilizzo del codice 🔄 → Grazie all’ereditarietà, una classe può ereditare caratteristiche da
un'altra, evitando di riscrivere lo stesso codice.
Manutenzione sempli icata 🛠 → Separando il codice in oggetti ben de initi, è più facile
aggiornarlo o correggerlo senza modi icare tutto il programma.
Maggiore organizzazione 📂 → Il codice è più leggibile e scalabile, ideale per progetti complessi.
f
f
f
CLASSI
Una classe è una struttura astratta che de inisce le caratteristiche e il comportamento di un insieme
di oggetti. Essa speci ica:
Una o più proprietà o attributi (variabili d'istanza)
Zero o più metodi
Zero o più costruttori
Uno o più metodi speciali getters e setters
f
f
ESEMPIO
Immaginiamo di dover realizzare un software per gestire automobili, possiamo de inire una Classe
Auto con:
Proprietà → Marca, modello, velocità.
Metodi → Accelerare, frenare, suonare il clacson.
f
ESEMPIO - STEP 2
Creiamo ora un metodo che
permetta all’auto anche di frenare e
non solo di accelerare.
Da notare come, all’interno dei
metodi, possiamo agire sulle
proprietà dell’oggetto. Nel metodo
frena, infatti eseguiamo un controllo
sulla velocità (proprietà) e agiamo di
conseguenza.
ESEMPIO - STEP 3
Come detto nelle precedenti lezioni, per creare un oggetto (istanza di una classe) è necessario anche
un costruttore, al quale passiamo i parametri per la creazione dell’oggetto stesso.
Il costruttore è un metodo “speciale” che prende il nome della classe (nelle prossime slide
spiegheremo il perché della necessità di un costruttore).
ESEMPIO - MAIN
IL COSTRUTTORE
In Java, il costruttore viene invocato automaticamente quando creiamo un nuovo oggetto con la
parola chiave new. Il costruttore ha lo stesso nome della classe e può accettare parametri per
inizializzare l’oggetto.
Riguardo l’esempio della slide precedente:
1. new Auto("Toyota") crea un nuovo oggetto della classe Auto.
2. Il costruttore Auto(String marca) viene chiamato automaticamente.
3. L’oggetto miaAuto viene inizializzato con marca = "Toyota" e velocità = 0.
Attenzione: il “costruttore vuoto” : NomeClasse(), viene introdotto di default dal linguaggio Java e
non è necessario speci icarlo.
f
IL RIFERIMENTO THIS
In Java, la parola chiave this è un riferimento all'istanza corrente della classe. Serve per distinguere
gli attributi della classe dai parametri del metodo o costruttore e per chiamare altri costruttori o
metodi della stessa classe.
1. Distinguere tra variabili d'istanza e parametri con lo stesso nome.
Se un parametro di un metodo o costruttore ha lo stesso nome di una variabile d’istanza,
possiamo usare this. per riferirci alla variabile della classe e non al parametro.
Senza this, Java non saprebbe a quale "marca" assegnare il valore!
IL RIFERIMENTO THIS
2. Chiamare un costruttore da un altro costruttore (costruttori multipli). Possiamo usare this() per
richiamare un altro costruttore della stessa classe ed evitare ripetizioni di codice.
IL RIFERIMENTO THIS
3. Restituire l’istanza corrente : Possiamo usare this nei metodi per restituire l'oggetto stesso. Questo
è utile per metodi a catena (method chaining).
LIMITI DEL NOSTRO APPROCCIO
Finora, abbiamo creato la classe Auto con le proprietà public o private, e le abbiamo inizializzate
direttamente nel costruttore. Tuttavia, questo approccio presenta alcuni problemi, soprattutto
perché:
1. Le proprietà sono accessibili e modi icabili senza controllo
• Se una proprietà è public, possiamo modi icarla direttamente dall'esterno della classe.
Questo può portare a dati incoerenti o errori dif icili da individuare.
• Problema: Qui abbiamo impostato la velocità a -50, cosa che non ha senso nel mondo reale!
f
f
f
LIMITI DEL NOSTRO APPROCCIO
2. Nessuna protezione sulle proprietà
interne
• Anche se una proprietà è private, il
costruttore la imposta senza alcun
controllo. Se creiamo un costruttore
che accetta direttamente un valore
di velocità, un utente del nostro
codice potrebbe accidentalmente
inserire un valore errato.
• Problema: anche in questo caso
riusciamo ad aggirare le regole della
realtà creando un’auto con velocità
-100 km/h.
LIMITI DEL NOSTRO APPROCCIO
In generale : se lasciamo le proprietà liberamente modi icabili, ogni parte del programma potrebbe
cambiarle in modo incontrollato, causando comportamenti inaspettati ed incoerenza dei dati.
Soluzione? Le soluzioni a questi problemi verranno trattate nelle prossime slide.
Incapsulamento …
f
PROGRAMMAZIONE
CLASSI CUSTOM
CARATTERISTICHE OOP
DEV Prof. Joshua B.
CARATTERISTICHE OOP
Un linguaggio di programmazione ad oggetti, come Java, è caratterizzato da diversi principi
fondamentali della programmazione orientata agli oggetti (OOP), tra i quali troviamo:
Incapsulamento (Encapsulation)
Ereditarietà (Inheritance)
Polimor ismo (Polymorphism)
Astrazione (Abstraction)
Modularità e riutilizzabilità
(Modularity & Reusability )
f
PROGRAMMAZIONE
CLASSI CUSTOM
INCAPSULAMENTO
DEV Prof. Joshua B.
INCAPSULAMENTO
L'incapsulamento è il meccanismo che:
Nasconde i dettagli interni di una classe, proteggendo i
dati dall’accesso diretto da parte di altre classi.
Espone solo ciò che è necessario attraverso metodi
pubblici (getter e setter).
Migliora la sicurezza e previene modi iche non
autorizzate ai dati.
In Java, questa proprietà viene implementata attraverso:
Modi icatori di accesso (private, public, protected).
Variabili d'istanza private per impedire l’accesso diretto
ai dati.
Metodi getter e setter per controllare l’accesso alle
variabili.
f
f
MODIFICATORI DI VISIBILITÀ
In Java, i modi icatori di visibilità servono per controllare l'accesso a variabili, metodi e classi. Questi
garantiscono sicurezza e incapsulamento, impedendo modi iche non volute ai dati.
public → Accessibile da qualsiasi parte del programma.
private → Accessibile solo all'interno della stessa classe.
protected → Accessibile nella stessa classe e nelle sottoclassi (ne parleremo meglio quando
introdurremo l’ereditarietà).
f
f
GETTER E SETTER
I getter e i setter sono metodi speciali utilizzati per accedere e modi icare gli attributi privati di una
classe. Ora che abbiamo già affrontato l'incapsulamento, vediamo come funzionano nel dettaglio,
utilizzando this. per migliorare la leggibilità e la coerenza del codice. Perché utilizzare questo tipo
di costrutto?
1. Proteggere l'integrità dei dati
1.1. Permettono di validare i dati prima di assegnarli a una variabile.
1.2. Evitano modi iche indesiderate dall’esterno della classe.
2. Nascondere l’implementazione interna
2.1. L'utente della classe non accede direttamente agli attributi, ma usa i metodi forniti.
2.2. Questo permette di modi icare l'implementazione interna senza cambiare il codice che
utilizza la classe.
f
f
f
GETTER E SETTER
3. Permettono maggiore controllo sulla lettura e scrittura dei dati
3.1. Possiamo applicare formattazioni nei getter (es. aggiungere "km/h" a una velocità).
[Link] limitare i valori accettati nei setter (es. impedire velocità negative).
Quando usare getter e setters?
Non devo ricordarmi
SEMPRE quando usare getter
SEMPRE e setter se li uso
sempre
SEMPRE
SEMPRE
ESEMPIO AUTO
ESEMPIO INCAPSULAMENTO (SBAGLIATO)
PROBLEMI
Chiunque può modi icare i dati
senza restrizioni.
Non c’è controllo sui valori (ad
esempio, l’anno non può essere
negativo).
Dif icile da manutenere in caso di
aggiornamenti.
f
f
ESEMPIO INCAPSULAMENTO (CORRETTO)
ESEMPIO INCAPSULAMENTO (CORRETTO)
Di seguito, troviamo un metodo main all’interno del quale viene utilizzata la classe correttamente
incapsulata. In questo modo sfruttiamo i vantaggi dell’incapsulamento:
Protezione dei dati: nessuno (oltre al costruttore) può modi icare direttamente gli attributi della
classe.
Maggiore controllo: possiamo validare i dati prima di modi icarli (es. controllare che l’anno sia
corretto).
Flessibilità e manutenibilità: se la logica cambia, possiamo aggiornare i metodi senza impattare
il codice esterno.
f
f
GOLDEN RULE INCAPSULAMENTO
Golden Rule: Rendi sempre private le variabili
d’istanza e fornisci metodi di accesso
controllati (getter e setter).
PROGRAMMAZIONE
CLASSI CUSTOM
EREDITARIETÀ
DEV Prof. Joshua B.
EREDITARIETÀ
Perché usare l’ereditarietà?
L’obiettivo principale dell’ereditarietà è riutilizzare codice, evitando di scrivere più volte le
stesse cose e organizzando meglio le relazioni tra le classi. In pratica, permette di de inire una
struttura gerarchica tra classi, in cui le classi più generali forniscono comportamenti comuni,
mentre le classi più speci iche possono estendere e personalizzare questi comportamenti.
Perche è utile?
Evita duplicazione di codice (DRY - Don't Repeat Yourself)
Immaginiamo di avere diverse tipologie di veicoli (Auto, Moto, Camion). Senza ereditarietà,
ogni classe dovrebbe implementare metodi comuni come accelera(), frena(),
mostraInfo(), causando ripetizioni di codice e logiche inutili. Con l’ereditarietà, questi metodi
possono essere de initi una volta sola nella classe padre (Veicolo), e ogni sottoclasse li
erediterà.
f
f
f
EREDITARIETÀ
Perche è utile?
Migliora l’organizzazione del codice
L’ereditarietà aiuta a strutturare il codice in modo più chiaro e logico. Ad esempio, se tutti i
veicoli hanno una velocità e possono accelerare, è naturale de inire queste caratteristiche in
una classe Veicolo, e poi lasciare che le classi Auto e Moto aggiungano le loro speci iche.
Facilita la manutenzione e l’espandibilità
Se in futuro dovremo modi icare o migliorare un comportamento comune (ad esempio, il modo
in cui i veicoli accelerano), possiamo farlo nella superclasse Veicolo, e la modi ica si
applicherà automaticamente a tutte le sottoclassi.
f
f
f
f
EREDITARIETÀ
L’ereditarietà consente a una classe iglia (subclass, derivata) di estendere una classe padre
(superclass, base).
La classe iglia eredita i campi (variabili d'istanza) e i metodi della classe padre, potendo
anche aggiungere o modi icare il comportamento
L’ereditarietà in Java si ottiene utilizzando la parola chiave extends
f
f
f
EREDITARIETÀ - ESEMPIO
Nel contesto delle auto, possiamo immaginare una classe generica Veicolo, da cui possono
derivare classi più speci iche come Auto, Moto o Camion.
f
EREDITARIETÀ - ESEMPIO
Nel contesto delle auto, possiamo immaginare una classe generica Veicolo, da cui possono
derivare classi più speci iche come Auto, Moto o Camion.
f
EREDITARIETÀ - ESEMPIO
Nel contesto delle auto, possiamo immaginare una classe generica Veicolo, da cui possono
derivare classi più speci iche come Auto, Moto o Camion.
f
EREDITARIETÀ
Abbiamo già visto che quando una classe eredita da un'altra, può riutilizzare gli attributi e i metodi
della superclasse. Ma cosa succede con il costruttore?
Il costruttore della superclasse NON viene ereditato, ma può essere chiamato dalla sottoclasse
utilizzando super().
Nell’esempio che abbiamo utilizzato ino ad ora notiamo come, la superclasse Veicolo speci ica un
costruttore che rende possibile valorizzare la proprietà “Marca”.
La sottoclasse Auto invece, speci ica un costruttore che permette di valorizzare sia la proprietà
marca che la proprietà (appartenente solo alla sottoclasse auto stessa) numeroPorte.
La parola chiave super() ci permette di non replicare la valorizzazione della proprietà marca ma
lasciandolo fare alla superclasse Veicolo.
Questo comportamento è molto importante al ine di rispettare le regole della buona
programmazione introdotte ino ad ora.
f
f
f
f
f
PROGRAMMAZIONE
CLASSI CUSTOM
PROPRIETÀ E METODI STATICI
DEV Prof. Joshua B.
APPROFONDIMENTO
Metodi di istanza: Appartengono a un oggetto speci ico e possono accedere ai suoi attributi.
Metodi statici: Appartengono alla classe stessa e non possono accedere direttamente agli attributi
di istanza.
REGOLA PRINCIPALE :
Un metodo statico può essere chiamato senza istanziare un oggetto della classe.
Un metodo di istanza richiede, appunto, un oggetto istanziato della classe per essere usato.
Metodi di istanza (accelera,
getMarca, etc.) lavorano su un
singolo oggetto.
Metodi statici (getAutoCreate)
lavorano sulla classe e non
possono accedere a this.
f
APPROFONDIMENTO
Vediamo qual’è l’output del seguente codice:
APPROFONDIMENTO
Vediamo qual’è l’output del seguente codice:
GOLDEN RULE STATIC VS. INSTANCE
Usa un metodo di istanza quando il comportamento dipende dai dati di un
oggetto speci ico
Usa un metodo statico quando il comportamento è legato alla classe in
generale e non a un singolo oggetto.
f
PROGRAMMAZIONE
CLASSI CUSTOM
POLIMORFISMO
DEV Prof. Joshua B.
POLIMORFISMO
Il polimor ismo è un principio fondamentale della programmazione orientata agli oggetti che
permette di trattare oggetti di classi diverse come se appartenessero alla stessa superclasse.
Esistono due tipi principali di polimor ismo:
1. Polimor ismo dinamico (o polimor ismo di sottotipo) → Avviene grazie all’override dei metodi
nelle sottoclassi.
2. Polimor ismo statico (o polimor ismo parametrico) → Avviene con overload di metodi, che
permette di de inire più metodi con lo stesso nome ma parametri diversi.
Perché il polimor ismo è utile?
Consente di scrivere codice più generico e riutilizzabile.
Facilita la manutenzione e l'estensibilità del codice.
Permette di trattare oggetti diversi in modo uniforme, migliorando la leggibilità
f
f
f
f
f
f
f
f
POLIMORFISMO DINAMICO (OVERRIDE)
Il polimor ismo dinamico si veri ica quando una sottoclasse fornisce una propria
implementazione di un metodo già de inito nella superclasse.
Aggiungiamo il metodo mostraInfo(), nella superclasse Veicolo con l’intenzione che le sottoclassi
eseguiranno un ovverride di questo metodo.
f
f
f
POLIMORFISMO DINAMICO (OVERRIDE)
Nella sottoclasse Auto, il metodo mostraInfo() verrà “overridato” per speci icare un
comportamento diverso o/e per estendere il comportamento speci icato nella superclasse.
In Java (ridicolmente) non c’è un modo immediato per capire se di un metodo è stato eseguito
un ovveride poiché non ci sono parole chiave immediate per quanto riguarda il polimor ismo
f
f
f
POLIMORFISMO STATICO (OVERLOAD)
Il polimor ismo statico si veri ica quando più metodi hanno lo stesso nome, ma parametri diversi.
Il metodo accelera, nella sottoclasse Auto, in questa versione ha due possibili invocazioni: la
prima con un solo parametro e la seconda con un ulteriore parametro turbo.
f
f
PROGRAMMAZIONE
CLASSI CUSTOM
RIDEFINIZIONE METODI OBJECT
OVERRIDE
DEV Prof. Joshua B.
RICHIAMO AL POLIMORFISMO
Il polimor ismo è un principio fondamentale della programmazione orientata agli oggetti che
permette di trattare oggetti di classi diverse come se appartenessero alla stessa superclasse.
Esistono due tipi principali di polimor ismo:
1. Polimor ismo dinamico (o polimor ismo di sottotipo) → Avviene grazie all’override dei metodi
nelle sottoclassi.
2. Polimor ismo statico (o polimor ismo parametrico) → Avviene con overload di metodi, che
permette di de inire più metodi con lo stesso nome ma parametri diversi.
In generale :
1. Override : il metodo ha lo stesso nome e gli stessi parametri del metodo speci icato nella
classe gerarchicamente superiore
2. Overload : il metodo ha lo stesso nome ma parametri diversi. Non è necessario avere una
gerarchia per sfruttare l’overload di un metodo.
f
f
f
f
f
f
f
f
INTRODUZIONE
In Java, tutte le classi derivano implicitamente da
una superclasse chiamata Object. (Link alla
documentazione)
Questa classe fornisce alcuni metodi fondamentali,
tra cui:
toString() —> Restituisce una descrizione testuale
della classe
equals(Object obj) —> Confronta due oggetti
della stessa classe
hashCode()
...altri ancora.
Tra i metodi che più spesso si ride iniscono sono
proprio toString() e equals()
f
TOSTRING()
Il metodo toString() è necessario per restituire una
rappresentazione testuale della dell’oggetto di una
speci ica classe.
Il comportamento di default del toString() presente nella
classe object, se non ride inito attraverso override, non
permette di eseguire operazioni di alcun genere. Dunque
se utilizzato, questo metodo su una classe custom, l’output
sarà il seguente:
Dove:
Auto : nome della classe
@4e25154f: identi icatore interno della classe scelto
arbitrariamente dal sistema.
f
f
f
A destra, l’implementazione base
(speci icata all’interno della classe object)
del metodo toString().
Ecco spiegato perché, richiamando il
metodo toString(), su un oggetto di una
classe che non ne speci ica un override
(polimor ismo dinamico), l’output è quello
speci icato nella slide precedente.
f
f
f
f
ESEMPIO PRATICO : TOSTRING()
Ci troviamo all’interno della
classe Auto (che come tutte le
classi, di default, eredita dalla
classe object i metodi di base).
Riquadrato in rosso, troviamo
l’ovveride (polimor ismo
dinamico) del metodo
toString(), speci ico per la
classe Auto.
Speci icando un
comportamento ad-hoc,
vediamo nella prossima slide
qual’è l’output della chiamata a
questo metodo dal main.
f
f
f
ESEMPIO PRATICO : TOSTRING()
In questo modo, il metodo toString(), viene richiamato sfruttando l’implementazione fornita all’interno
della classe Auto e NON quella di base presente nella classe object.
In questo modo, sappiamo a priori che possiamo richiamare tutti i metodi della classe object (dunque
riferirci ad ogni oggetto con la certezza che il metodo toString(), equals(), getHashCode() ecc..) senza
preoccuparci di eventuali errori di non implementazione.
EQUALS()
Il metodo equals() (link alla documentazione) serve per
confrontare due oggetti per veri icarne l'uguaglianza
logica.
Se per i tipi primitivi l’uguaglianza è data da formule
matematiche (es. due numeri sono uguali se la loro
differenza = 0), lo stesso può non valere per oggetti
complessi dove il concetto di uguaglianza può dipendere
da più proprietà.
Quand’è che due automobili sono considerate uguali?
Quando hanno lo stesso colore? Quando hanno lo stesso
numero di porte? Quando hanno la stessa marca?
A seconda del contesto, potrebbe avere senso de inire
l’uguaglianza tra automobili in modo diverso, come fare?
f
f
EQUALS()
Qualora il metodo equals() non venisse ride inito attraverso il metodo dell’override all’interno della
classe speci ica (nel nostro esempio la classe Auto) avremmo un comportamento di base,
probabilmente non corretto per la nostra idea di uguaglianza.
Poniamo per esempio che, due delle quali vengono speci icate solo le rispettive marche, vengano
ritenute logicamente uguali se queste stringhe (le marche in questione). Il comportamento di questo
codice allora non è coerente con la nostra scelta. Dobbiamo eseguirne l’ovveride nella classe Auto.
f
f
f
IMPLEMENTAZIONE EQUALS() : 1
Attenzione ad alcuni dettagli dell’override del metodo equals() speci icato all’interno della classe Auto.
Ricordiamo che dovendo sfruttare l’override, non possiamo cambiare la irma del metodo speci icato
nella classe Object.
Questo primo if (riquadrato in rosso
nell’immagine a sinistra) serve per
garantire che il codice non vada in
eccezione.
Se il parametro passato è nello
oppure è di una classe differente da
quello attuale, allora verrà
automaticamente restituito false.
Questo serve per evitare errori nella
fase di casting del parametro.
f
f
f
IMPLEMENTAZIONE EQUALS() : 2
Attenzione ad alcuni dettagli dell’override del metodo equals() speci icato all’interno della classe Auto.
Ricordiamo che dovendo sfruttare l’override, non possiamo cambiare la irma del metodo speci icato
nella classe Object.
Se il primo controllo non viene
superato, signi ica che il parametro è
di tipo coerente, si può quindi passare
ai controlli speci ici.
Riquadrato in rosso troviamo
dapprima un casting dell’oggetto
passato come parametro così da
garantire il richiamo di tutti i metodi
della classe auto.
In ine un ultimo if nel quale si
controlla che la marca sia uguale tra
entrambi gli oggetti Auto.
f
f
f
f
f
f
TIPS AND TRICKS EQUALS()
Per quanto sembri complesso, l’override del metodo equals() in realtà risulta quasi sempre uguale
tranne per la parte di controlli speci ici sulla logica di uguaglianza, vediamo perché.
La struttura rimane sempre
uguale ad esclusione della
porzione di codice dove devono
venire implementati i controlli di
uguaglianza logica (riquadrati di
rosso)
f