☕ Lezione 9

Classi Astratte - Modelli da Completare

Classi Astratte: Definire Contratti

🎯 Obiettivi della lezione

💡 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

MEDIO

Obiettivo: 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

MEDIO

Obiettivo: 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

FACILE

Crea classe astratta Strumento con metodo disegna():

  • Matita, Pennello, Gomma
  • Ogni strumento disegna in modo diverso

Esercizio 4: Elettrodomestici

MEDIO

Crea classe astratta Elettrodomestico con:

  • Metodo astratto: calcolaConsumo()
  • Frigorifero, Lavatrice, TV
  • Calcola consumo totale

Esercizio 5: Sistema di Gioco

DIFFICILE

Crea 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