Menu
Coddy logo textTech

Classi astratte e interfacce a confronto

Fa parte della sezione Programmazione orientata agli oggetti del percorso Java di Coddy. Lezione 38 di 87.

Ora che hai compreso sia le classi astratte sia le interfacce, sorge una domanda comune: quando dovresti usare una piuttosto che l’altra? La scelta dipende da ciò che stai cercando di modellare.

Usa una classe astratta quando le classi condividono una base comune con stato o comportamento condivisi. Le classi astratte possono avere variabili di istanza, costruttori e una combinazione di metodi astratti e concreti:

public abstract class Animal {
    protected String name;  // stato condiviso
    
    public Animal(String name) {
        this.name = name;
    }
    
    public void sleep() {  // comportamento condiviso
        System.out.println(name + " is sleeping");
    }
    
    public abstract void makeSound();  // deve essere implementato
}

Usa un’interfaccia quando vuoi definire una capacità che classi non correlate possono condividere. Le interfacce si concentrano su ciò che un oggetto può fare, non su ciò che è:

public interface Flyable {
    void fly();
}

// Classi non correlate possono condividere questa capacità
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }

Ecco un rapido confronto:

CaratteristicaClasse astrattaInterfaccia
Variabili di istanzaSìSolo costanti
CostruttoriSìNo
Ereditarietà multiplaNo (extends singolo)Sì (implements multipli)
Modificatori di accessoQualsiasiSolo public (per i metodi astratti)

Una linea guida pratica: se ti ritrovi a creare una classe astratta con solo metodi astratti e senza stato, probabilmente un'interfaccia è la scelta migliore.

challenge icon

Sfida

Facile

Creiamo un sistema di veicoli che mostri quando usare le classi astratte rispetto alle interfacce. Modellerai veicoli che condividono stato e comportamento comuni tramite una classe astratta, aggiungendo al contempo capacità opzionali tramite interfacce.

Organizzerai il codice in cinque file:

  • Vehicle.java: crea una classe astratta che funga da base per tutti i veicoli. Ogni veicolo ha un campo brand (String) e un campo year (int). Includi un costruttore per inizializzare entrambi i campi, metodi getter per ciascuno e un metodo astratto startEngine() che restituisce una String. Aggiungi anche un metodo concreto getInfo() che restituisce: [brand] ([year]). Questo è un caso d'uso perfetto per una classe astratta: i veicoli condividono lo stato (brand, year) e parte del comportamento (getInfo), ma ciascuno avvia il motore in modo diverso.
  • Convertible.java: definisci un'interfaccia per i veicoli il cui tetto può essere aperto e chiuso. Questa capacità non è legata a ciò che un veicolo è: è qualcosa che certi veicoli possono fare. Dichiara due metodi: openRoof() e closeRoof(), entrambi con valore di ritorno String.
  • Car.java: crea una classe che estende Vehicle e implementa Convertible. Un Car ha un campo aggiuntivo numDoors (int). Usa super per inizializzare i campi ereditati. Implementa startEngine() in modo che restituisca: [brand] car engine started. Implementa openRoof() in modo che restituisca: [brand] roof opening e closeRoof() in modo che restituisca: [brand] roof closing.
  • Motorcycle.java: crea una classe che estende Vehicle ma NON implementa Convertible: le motociclette non hanno il tetto! Una Motorcycle ha un campo hasSidecar (boolean). Implementa startEngine() in modo che restituisca: [brand] motorcycle engine roaring.
  • Main.java: metti insieme tutti gli elementi per mostrare la differenza tra classi astratte e interfacce. Riceverai quattro input: una marca di auto, un anno dell'auto, una marca di motocicletta e un anno della motocicletta.

    Crea una Car (con 4 porte) e una Motorcycle (senza sidecar). Per prima cosa, mostra il comportamento condiviso della classe astratta stampando getInfo() per entrambi i veicoli. Lo ereditano entrambi da Vehicle. Poi stampa il risultato di startEngine() per ciascuno: nota come ogni tipo di veicolo lo implementa in modo diverso.

    Infine, mostra la capacità dell'interfaccia: poiché solo la Car implementa Convertible, chiama e stampa openRoof() seguito da closeRoof() solo sulla Car.

Riceverai quattro input: marca dell'auto (String), anno dell'auto (int), marca della motocicletta (String) e anno della motocicletta (int).

L'output dovrà contenere sei righe in totale. Nota come entrambi i veicoli condividono lo stato della classe astratta e il metodo getInfo(), ma solo la Car dispone della capacità di convertibile. Questo illustra quando usare ciascun approccio!

Provalo tu

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Leggi i dati di input
        String carBrand = scanner.nextLine();
        int carYear = scanner.nextInt();
        scanner.nextLine(); // consuma il carattere di nuova riga
        String motorcycleBrand = scanner.nextLine();
        int motorcycleYear = scanner.nextInt();
        
        // TODO: Crea una Car con 4 porte
        
        // TODO: Crea una Motorcycle senza sidecar (false)
        
        // TODO: Stampa getInfo() per entrambi i veicoli (mostra il comportamento condiviso della classe astratta)
        
        // TODO: Stampa startEngine() per entrambi i veicoli (mostra le diverse implementazioni)
        
        // TODO: Stampa openRoof() e closeRoof() solo per l'auto (mostra una funzionalità dell'interfaccia)
    }
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore Java online