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:
| Caratteristica | Classe astratta | Interfaccia |
|---|---|---|
| Variabili di istanza | Sì | Solo costanti |
| Costruttori | Sì | No |
| Ereditarietà multipla | No (extends singolo) | Sì (implements multipli) |
| Modificatori di accesso | Qualsiasi | Solo 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.
Sfida
FacileCreiamo 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 campobrand(String) e un campoyear(int). Includi un costruttore per inizializzare entrambi i campi, metodi getter per ciascuno e un metodo astrattostartEngine()che restituisce una String. Aggiungi anche un metodo concretogetInfo()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()ecloseRoof(), entrambi con valore di ritorno String.Car.java: crea una classe che estendeVehiclee implementaConvertible. Un Car ha un campo aggiuntivonumDoors(int). Usasuperper inizializzare i campi ereditati. ImplementastartEngine()in modo che restituisca:[brand] car engine started. ImplementaopenRoof()in modo che restituisca:[brand] roof openingecloseRoof()in modo che restituisca:[brand] roof closing.Motorcycle.java: crea una classe che estendeVehiclema NON implementaConvertible: le motociclette non hanno il tetto! Una Motorcycle ha un campohasSidecar(boolean). ImplementastartEngine()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 distartEngine()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 stampaopenRoof()seguito dacloseRoof()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)
}
}
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
1Fondamenti della programmazione a oggetti
File esterniIntroduzione alla programmazione a oggettiClassi e oggettiLa parola chiave thisMetodiCampi (attributi)Metodo costruttoreSovraccarico dei costruttoriRipasso - Calcolatrice semplice4Ereditarietà
Ereditarietà di base (extends)La parola chiave superSovrascrittura dei metodi (@Override)Concatenamento dei costruttoriLa classe ObjectEreditarietà singola e multilivelloPerché non esiste l'ereditarietà multipla delle classiRiepilogo: gerarchia dei dipendenti7Metodi speciali e classe Object
Metodo toString()equals() e hashCode()Metodo clone()compareTo() e ComparableInterfaccia ComparatorRiepilogo - Ordinamento personalizzato2Modificatori di accesso e incapsulamento
Panoramica dei livelli di accessoMetodi getter e setterNascondimento delle informazioniLa parola chiave finalRiepilogo - Gestore di conti bancari5Polimorfismo
Nozioni di base sull'overloading dei metodiOverride dei metodi (a runtime)Upcasting e downcastingL'operatore instanceofClassi e metodi astrattiRiepilogo - Calcolatore di forme8Concetti avanzati di OOP
Composizione vs ereditarietàAggregazione vs composizioneClassi interne, annidate e anonimeEnum e metodi degli EnumRecord (Java 16+)Classi sealed (Java 17+)11Modelli di progettazione Parte 1
Introduzione ai modelli di progettazionePattern SingletonPattern FactoryPattern BuilderPattern ObserverPattern Strategy3Proprietà di classe e membri statici
Variabili di istanza e staticheMetodi staticiBlocchi staticiCostanti (static final)Riepilogo - Contatore e utility6Interfacce e classi astratte
Introduzione alle interfacceImplementazione delle interfacceImplementazione di più interfacceMetodi default e static nelle interfacceClassi astratte e interfacce a confrontoInterfacce funzionaliRiepilogo: sistema di pagamento9Tipi generici
Introduzione ai tipi genericiClassi genericheMetodi genericiParametri di tipo con limitiCaratteri jolly (?, extends, super)Riepilogo - Contenitore generico12Modelli di progettazione – Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositePattern IteratorEsercitati da solo: Compilatore Java online