Classi Astratte: Definire Contratti
🎯 Obiettivi della lezione
- Comprendere cosa sono le classi astratte
- Capire quando e perché usarle
- Creare classi e metodi astratti
- Differenze tra classi concrete e astratte
- Esempi pratici di progettazione
💡 Cos'è una Classe Astratta?
Una classe astratta è una classe che non può essere istanziata direttamente.
Serve come modello per le sottoclassi, definendo metodi che DEVONO essere implementati dalle classi figlie.
Si usa la parola chiave abstract.
🎯 Quando usare le classi astratte?
- Quando vuoi definire un comportamento comune, ma l'implementazione varia
- Quando alcune parti della logica sono condivise, altre specifiche
- Per creare una "famiglia" di classi correlate
- Per forzare le sottoclassi a implementare certi metodi
📝 Sintassi Base
// Classe astratta
public abstract class Forma {
protected String colore;
// Costruttore (le classi astratte possono avere costruttori)
public Forma(String colore) {
this.colore = colore;
}
// Metodo concreto (con implementazione)
public void mostraColore() {
System.out.println("Colore: " + colore);
}
// Metodo astratto (SENZA implementazione)
public abstract double calcolaArea();
public abstract double calcolaPerimetro();
}
// ERRORE! Non puoi fare questo:
// Forma f = new Forma("rosso"); // Classe astratta non istanziabile!
// Devi creare una sottoclasse concreta
public class Cerchio extends Forma {
private double raggio;
public Cerchio(String colore, double raggio) {
super(colore);
this.raggio = raggio;
}
// DEVI implementare i metodi astratti
@Override
public double calcolaArea() {
return Math.PI * raggio * raggio;
}
@Override
public double calcolaPerimetro() {
return 2 * Math.PI * raggio;
}
}
// Ora puoi istanziare il Cerchio
Cerchio c = new Cerchio("rosso", 5);
c.calcolaArea(); // OK!
⚠️ Regole importanti:
- Una classe astratta NON può essere istanziata
- Può contenere sia metodi astratti che concreti
- Una sottoclasse DEVE implementare TUTTI i metodi astratti (o essere anch'essa astratta)
- Può avere costruttori, attributi, metodi normali
- Un metodo astratto non ha corpo (no {...})
🎨 Esempio Completo: Forme Geometriche
// Classe astratta base
public abstract class Forma {
protected String colore;
protected String nome;
public Forma(String nome, String colore) {
this.nome = nome;
this.colore = colore;
}
// Metodi concreti
public void mostraInfo() {
System.out.println("--- " + nome + " ---");
System.out.println("Colore: " + colore);
}
public String getColore() {
return colore;
}
// Metodi astratti - ogni sottoclasse decide come calcolarli
public abstract double calcolaArea();
public abstract double calcolaPerimetro();
}
// Sottoclasse: Rettangolo
public class Rettangolo extends Forma {
private double base;
private double altezza;
public Rettangolo(String colore, double base, double altezza) {
super("Rettangolo", colore);
this.base = base;
this.altezza = altezza;
}
@Override
public double calcolaArea() {
return base * altezza;
}
@Override
public double calcolaPerimetro() {
return 2 * (base + altezza);
}
public boolean eQuadrato() {
return base == altezza;
}
}
// Sottoclasse: Cerchio
public class Cerchio extends Forma {
private double raggio;
public Cerchio(String colore, double raggio) {
super("Cerchio", colore);
this.raggio = raggio;
}
@Override
public double calcolaArea() {
return Math.PI * raggio * raggio;
}
@Override
public double calcolaPerimetro() {
return 2 * Math.PI * raggio;
}
public double getDiametro() {
return 2 * raggio;
}
}
// Sottoclasse: Triangolo
public class Triangolo extends Forma {
private double lato1, lato2, lato3;
public Triangolo(String colore, double lato1, double lato2, double lato3) {
super("Triangolo", colore);
this.lato1 = lato1;
this.lato2 = lato2;
this.lato3 = lato3;
}
@Override
public double calcolaArea() {
// Formula di Erone
double s = calcolaPerimetro() / 2;
return Math.sqrt(s * (s - lato1) * (s - lato2) * (s - lato3));
}
@Override
public double calcolaPerimetro() {
return lato1 + lato2 + lato3;
}
public boolean eEquilatero() {
return lato1 == lato2 && lato2 == lato3;
}
}
🧪 Test delle Forme
public class TestForme {
public static void main(String[] args) {
// Array polimorfico di Forma (anche se Forma è astratta!)
Forma[] forme = new Forma[4];
forme[0] = new Rettangolo("rosso", 5, 3);
forme[1] = new Cerchio("blu", 4);
forme[2] = new Triangolo("verde", 3, 4, 5);
forme[3] = new Rettangolo("giallo", 4, 4); // Quadrato
// Elaboro tutte le forme allo stesso modo!
double areaTotle = 0;
for (Forma forma : forme) {
forma.mostraInfo();
System.out.println("Area: " + forma.calcolaArea());
System.out.println("Perimetro: " + forma.calcolaPerimetro());
System.out.println();
areaTotale += forma.calcolaArea();
}
System.out.println("=== Riepilogo ===");
System.out.println("Numero forme: " + forme.length);
System.out.println("Area totale: " + areaTotale);
// Usando instanceof e cast per metodi specifici
System.out.println("\n=== Controlli specifici ===");
for (Forma forma : forme) {
if (forma instanceof Rettangolo) {
Rettangolo r = (Rettangolo) forma;
if (r.eQuadrato()) {
System.out.println("Trovato un quadrato " + r.getColore() + "!");
}
} else if (forma instanceof Triangolo) {
Triangolo t = (Triangolo) forma;
if (t.eEquilatero()) {
System.out.println("Trovato un triangolo equilatero!");
}
}
}
}
}
--- Rettangolo ---
Colore: rosso
Area: 15.0
Perimetro: 16.0
--- Cerchio ---
Colore: blu
Area: 50.26548245743669
Perimetro: 25.132741228718345
--- Triangolo ---
Colore: verde
Area: 6.0
Perimetro: 12.0
--- Rettangolo ---
Colore: giallo
Area: 16.0
Perimetro: 16.0
=== Riepilogo ===
Numero forme: 4
Area totale: 87.26548245743669
=== Controlli specifici ===
Trovato un quadrato giallo!
🏦 Esempio: Sistema Bancario
// Classe astratta per conti bancari
public abstract class ContoBancario {
protected String numeroConto;
protected String intestatario;
protected double saldo;
public ContoBancario(String numeroConto, String intestatario, double saldoIniziale) {
this.numeroConto = numeroConto;
this.intestatario = intestatario;
this.saldo = saldoIniziale;
}
// Metodi concreti comuni
public void deposita(double importo) {
if (importo > 0) {
saldo += importo;
System.out.println("Depositati €" + importo);
}
}
public double getSaldo() {
return saldo;
}
public void mostraInfo() {
System.out.println("=== Conto " + numeroConto + " ===");
System.out.println("Intestatario: " + intestatario);
System.out.println("Saldo: €" + saldo);
}
// Metodi astratti - implementazione diversa per ogni tipo di conto
public abstract boolean preleva(double importo);
public abstract void calcolaInteressi();
public abstract String getTipoConto();
}
// Conto Corrente
public class ContoCorrente extends ContoBancario {
private double fido; // Scoperto permesso
public ContoCorrente(String numeroConto, String intestatario,
double saldoIniziale, double fido) {
super(numeroConto, intestatario, saldoIniziale);
this.fido = fido;
}
@Override
public boolean preleva(double importo) {
// Può andare in negativo fino al fido
if (saldo - importo >= -fido) {
saldo -= importo;
System.out.println("Prelevati €" + importo);
return true;
} else {
System.out.println("Fondo insufficiente! Fido: €" + fido);
return false;
}
}
@Override
public void calcolaInteressi() {
// Interessi solo se in positivo
if (saldo > 0) {
double interessi = saldo * 0.01; // 1%
saldo += interessi;
System.out.println("Interessi maturati: €" + interessi);
}
}
@Override
public String getTipoConto() {
return "Conto Corrente";
}
}
// Conto Risparmio
public class ContoRisparmio extends ContoBancario {
private static final double TASSO_INTERESSE = 0.03; // 3%
private int numPrelievi;
public ContoRisparmio(String numeroConto, String intestatario, double saldoIniziale) {
super(numeroConto, intestatario, saldoIniziale);
this.numPrelievi = 0;
}
@Override
public boolean preleva(double importo) {
// Non può andare in negativo
if (saldo >= importo) {
saldo -= importo;
numPrelievi++;
System.out.println("Prelevati €" + importo + " (prelievo n." + numPrelievi + ")");
// Penale dopo 3 prelievi
if (numPrelievi > 3) {
double penale = 2.0;
saldo -= penale;
System.out.println("Applicata penale: €" + penale);
}
return true;
} else {
System.out.println("Saldo insufficiente!");
return false;
}
}
@Override
public void calcolaInteressi() {
double interessi = saldo * TASSO_INTERESSE;
saldo += interessi;
System.out.println("Interessi maturati: €" + interessi);
System.out.println("Nuovo saldo: €" + saldo);
}
@Override
public String getTipoConto() {
return "Conto Risparmio";
}
}
public class TestBanca {
public static void main(String[] args) {
ContoBancario[] conti = new ContoBancario[2];
conti[0] = new ContoCorrente("CC001", "Mario Rossi", 1000, 500);
conti[1] = new ContoRisparmio("CR001", "Giulia Bianchi", 5000);
for (ContoBancario conto : conti) {
System.out.println("\n=== " + conto.getTipoConto() + " ===");
conto.mostraInfo();
conto.deposita(200);
conto.preleva(300);
conto.calcolaInteressi();
System.out.println("Saldo finale: €" + conto.getSaldo());
}
}
}
🆚 Classi Astratte vs Classi Concrete
| Caratteristica | Classe Astratta | Classe Concreta |
|---|---|---|
| Istanziabile | ❌ No | ✅ Sì |
| Metodi astratti | ✅ Può averne | ❌ No |
| Metodi concreti | ✅ Può averne | ✅ Sì |
| Costruttori | ✅ Sì | ✅ Sì |
| Attributi | ✅ Sì | ✅ Sì |
| Uso | Modello per sottoclassi | Oggetti utilizzabili |
✏️ Esercizi Guidati
Esercizio 1: Veicoli Astratti
MEDIOObiettivo: Creare una gerarchia di veicoli con classe astratta.
Requisiti:
- Classe astratta Veicolo con marca, modello
- Metodo astratto: calcolaCostoViaggio(double km)
- Metodo concreto: mostraInfo()
- Sottoclassi: Auto (0.20€/km), Moto (0.10€/km), Camion (0.50€/km)
- Test con array di veicoli
Esercizio 2: Dipendenti e Stipendi
MEDIOObiettivo: Sistema di calcolo stipendi.
Requisiti:
- Classe astratta Dipendente (nome, cognome)
- Metodo astratto: calcolaStipendio()
- Impiegato: stipendio fisso
- Manager: stipendio + bonus
- Consulente: tariffa oraria * ore lavorate
💪 Esercizi da Svolgere
Esercizio 3: Strumenti di Disegno
FACILECrea classe astratta Strumento con metodo disegna():
- Matita, Pennello, Gomma
- Ogni strumento disegna in modo diverso
Esercizio 4: Elettrodomestici
MEDIOCrea classe astratta Elettrodomestico con:
- Metodo astratto: calcolaConsumo()
- Frigorifero, Lavatrice, TV
- Calcola consumo totale
Esercizio 5: Sistema di Gioco
DIFFICILECrea classe astratta Personaggio con:
- Attributi: nome, salute, forza
- Metodo astratto: attacca(Personaggio nemico)
- Guerriero, Mago, Arciere
- Simula combattimenti
🎯 Concetti Chiave da Ricordare
- Classe astratta: abstract class NomeClasse
- Metodo astratto: public abstract tipo metodo();
- NON può essere istanziata direttamente
- Sottoclassi DEVONO implementare metodi astratti
- Può avere sia metodi astratti che concreti
- Usa per definire un contratto che le sottoclassi devono rispettare
- Perfetta con polimorfismo per codice flessibile