Polimorfismo Java spiegato: come funziona, esempi e migliori pratiche

目次

1. Cosa Imparerai in Questo Articolo

1.1 Polimorfismo in Java — Spiegato in Una Frase

In Java, polimorfismo significa:

“Trattare oggetti diversi attraverso lo stesso tipo, mentre il loro comportamento reale cambia a seconda dell’oggetto concreto.”

In termini più semplici, puoi scrivere codice usando una classe o interfaccia genitore, e in seguito sostituire l’implementazione reale senza modificare il codice chiamante.
Questa idea è una pietra miliare della programmazione orientata agli oggetti in Java.

1.2 Perché il Polimorfismo è Importante

Il polimorfismo non è solo un concetto teorico.
Aiuta direttamente a scrivere codice che è:

  • Più facile da estendere
  • Più facile da mantenere
  • Meno fragile quando i requisiti cambiano

Situazioni tipiche in cui il polimorfismo brilla includono:

  • Funzionalità che nel tempo avranno più varianti
  • Codice pieno di crescenti istruzioni if / switch
  • Logica di business che cambia indipendentemente dai suoi chiamanti

Nello sviluppo Java reale, il polimorfismo è uno degli strumenti più efficaci per controllare la complessità.

1.3 Perché i Principianti Spesso Faticano con il Polimorfismo

Molti principianti trovano il polimorfismo difficile all’inizio, principalmente perché:

  • Il concetto è astratto e non legato a una nuova sintassi
  • Viene spesso spiegato insieme a ereditarietà e interfacce
  • Si concentra sul pensiero di design, non solo sulla meccanica del codice

Di conseguenza, gli studenti possono “conoscere il termine” ma sentirsi incerti su quando e perché usarlo.

1.4 L’Obiettivo di Questo Articolo

Entro la fine di questo articolo, comprenderai:

  • Cosa significa realmente il polimorfismo in Java
  • Come il metodo di overriding e il comportamento a runtime lavorano insieme
  • Quando il polimorfismo migliora il design — e quando non lo fa
  • Come sostituisce la logica condizionale nelle applicazioni reali

L’obiettivo è aiutarti a vedere il polimorfismo non come un concetto difficile, ma come uno strumento di design naturale e pratico.

2. Cosa Significa il Polimorfismo in Java

2.1 Trattare gli Oggetti Attraverso un Tipo Genitore

Al centro del polimorfismo in Java c’è un’idea semplice:

Puoi trattare un oggetto concreto attraverso il suo tipo di classe genitore o interfaccia.

Considera l’esempio seguente:

Animal animal = new Dog();

Ecco cosa sta succedendo:

  • Il tipo della variabile è Animal
  • L’oggetto reale è un Dog

Anche se la variabile è dichiarata come Animal, il programma funziona comunque correttamente.
Non è un trucco — è una caratteristica fondamentale del sistema di tipi di Java.

2.2 La Stessa Chiamata di Metodo, Comportamento Differente

Ora guarda questa chiamata di metodo:

animal.speak();

Il codice stesso non cambia.
Tuttavia, il comportamento dipende dall’oggetto reale memorizzato in animal.

  • Se animal si riferisce a un Dog → viene eseguita l’implementazione del cane
  • Se animal si riferisce a un Cat → viene eseguita l’implementazione del gatto

Questo è il motivo per cui si chiama polimorfismo
un’interfaccia, molte forme di comportamento.

2.3 Perché Usare un Tipo Genitore è Così Importante

Ti potresti chiedere:

“Perché non usare semplicemente Dog invece di Animal ovunque?”

Usare il tipo genitore ti offre vantaggi potenti:

  • Il codice chiamante non dipende da classi concrete
  • Nuove implementazioni possono essere aggiunte senza modificare il codice esistente
  • Il codice diventa più facile da riutilizzare e testare

Ad esempio:

public void makeAnimalSpeak(Animal animal) {
    animal.speak();
}

Questo metodo funziona per:

  • Dog
  • Cat
  • Qualsiasi futura classe di animale

Il chiamante si preoccupa solo di ciò che l’oggetto può fare, non di cosa è.

2.4 Relazione tra Polimorfismo e Overriding di Metodo

Il polimorfismo è spesso confuso con l’overriding di metodo, quindi chiarifichiamo.

  • Overriding di metodo → Una sottoclasse fornisce la propria implementazione di un metodo della classe genitore
  • Polimorfismo → Chiamare il metodo overridden attraverso un riferimento di tipo genitore

L’overriding abilita il polimorfismo, ma il polimorfismo è il principio di design che lo utilizza.

2.5 Questo Non è Nuova Sintassi — È un Concetto di Design

Il polimorfismo non introduce nuove parole chiave Java né sintassi speciale.

  • Hai già usato class, extends e implements
  • Hai già chiamato i metodi allo stesso modo

Ciò che cambia è come pensi all’interazione tra gli oggetti.

Invece di scrivere codice che dipende da classi concrete,
progetti codice che dipende da astrazioni.

2.6 Punto chiave di questa sezione

Per riassumere:

  • Il polimorfismo consente di utilizzare gli oggetti tramite un tipo comune
  • Il comportamento reale è determinato a runtime
  • Il chiamante non ha bisogno di conoscere i dettagli dell’implementazione

Nella sezione successiva, esploreremo perché Java può fare questo,
esaminando in dettaglio l’override dei metodi e il binding dinamico.

3. Il meccanismo centrale: Override dei metodi e binding dinamico

3.1 Cosa viene deciso a tempo di compilazione vs tempo di esecuzione

Per comprendere davvero il polimorfismo in Java, devi separare il comportamento a tempo di compilazione dal comportamento a tempo di esecuzione.

Java prende due decisioni diverse in due fasi differenti:

  • Tempo di compilazione → Verifica se una chiamata a metodo è valida per il tipo della variabile
  • Tempo di esecuzione → Decide quale implementazione del metodo viene effettivamente eseguita

Questa separazione è la base del polimorfismo.

3.2 La disponibilità dei metodi è verificata a tempo di compilazione

Considera di nuovo questo codice:

Animal animal = new Dog();
animal.speak();

A tempo di compilazione, il compilatore Java guarda solo a:

  • Il tipo dichiarato : Animal

Se Animal definisce un metodo speak(), la chiamata è considerata valida.
Il compilatore non si preoccupa di quale oggetto concreto verrà assegnato in seguito.

Ciò significa:

  • Puoi chiamare solo i metodi che esistono nel tipo genitore
  • Il compilatore non “indovina” il comportamento della sottoclasse

3.3 Il metodo reale è scelto a tempo di esecuzione

Quando il programma viene eseguito, Java valuta:

  • A quale oggetto animal si riferisce realmente
  • Se quella classe sovrascrive il metodo chiamato

Se Dog sovrascrive speak(), allora viene eseguita l’implementazione di Dog, non quella di Animal.

Questa selezione del metodo a runtime è chiamata binding dinamico (o dispatch dinamico).

3.4 Perché il binding dinamico abilita il polimorfismo

Senza il binding dinamico, il polimorfismo non esisterebbe.

Se Java chiamasse sempre i metodi basandosi sul tipo dichiarato della variabile,
questo codice sarebbe privo di senso:

Animal animal = new Dog();

Il binding dinamico permette a Java di:

  • Ritardare la decisione del metodo fino al runtime
  • Abbinare il comportamento all’oggetto reale

In breve:

  • L’override definisce la variazione
  • Il binding dinamico la attiva

Insieme, rendono possibile il polimorfismo.

3.5 Perché i metodi e i campi static sono diversi

Una fonte comune di confusione sono i membri static.

Regola importante:

  • I metodi e i campi statici NON partecipano al polimorfismo

Perché?

  • Appartengono alla classe, non all’oggetto
  • Vengono risolti a tempo di compilazione, non a runtime

Ciò significa:

Animal animal = new Dog();
animal.staticMethod(); // resolved using Animal, not Dog

La selezione del metodo è fissa e non cambia in base all’oggetto reale.

3.6 Confusione comune dei principianti — Chiarita

Riassumiamo chiaramente le regole chiave:

  • Posso chiamare questo metodo? → Verificato usando il tipo dichiarato (tempo di compilazione)
  • Quale implementazione viene eseguita? → Decisa dall’oggetto reale (runtime)
  • Cosa supporta il polimorfismo? → Solo i metodi di istanza sovrascritti

Una volta che questa distinzione è chiara, il polimorfismo smette di sembrare misterioso.

3.7 Riepilogo della sezione

  • Java valida le chiamate ai metodi usando il tipo della variabile
  • Il runtime sceglie il metodo sovrascritto in base all’oggetto
  • Questo meccanismo è chiamato binding dinamico
  • I membri statici non sono polimorfici

Nella sezione successiva, vedremo come scrivere codice polimorfico usando l’ereditarietà (extends), con esempi concreti.

4. Scrivere codice polimorfico usando l’ereditarietà (extends)

4.1 Il modello base di ereditarietà

Iniziamo con il modo più semplice per implementare il polimorfismo in Java: ereditarietà di classe.

class Animal {
    public void speak() {
        System.out.println("Some sound");
    }
}

class Dog extends Animal {
    @Override
    public void speak() {
        System.out.println("Woof");
    }
}

class Cat extends Animal {
    @Override
    public void speak() {
        System.out.println("Meow");
    }
}

Qui:

  • Animal definisce un comportamento comune
  • Ogni sottoclasse sovrascrive quel comportamento con la propria implementazione

Questa struttura è la base del polimorfismo tramite ereditarietà.

4.2 Usare il Tipo Padre è la Chiave

Ora osserva il codice chiamante:

Animal a1 = new Dog();
Animal a2 = new Cat();

a1.speak();
a2.speak();

Anche se entrambe le variabili sono di tipo Animal,
Java esegue l’implementazione corretta in base all’oggetto reale.

  • Dog"Woof"
  • Cat"Meow"

Il codice chiamante non ha bisogno di conoscere — o preoccuparsi — della classe concreta.

4.3 Perché Non Usare Direttamente il Tipo della Sottoclasse?

I principianti spesso scrivono codice così:

Dog dog = new Dog();
dog.speak();

Questo funziona, ma limita la flessibilità.

Se in seguito introduci un altro tipo di animale, devi:

  • Modificare le dichiarazioni delle variabili
  • Aggiornare i parametri dei metodi
  • Modificare le collezioni

Usare il tipo padre evita questi cambiamenti:

List<Animal> animals = List.of(new Dog(), new Cat());

La struttura rimane la stessa anche quando vengono aggiunte nuove sottoclassi.

4.4 Cosa Appartiene alla Classe Padre?

Quando si progetta il polimorfismo basato sull’ereditarietà, la classe padre dovrebbe contenere:

  • Comportamenti condivisi da tutte le sottoclassi
  • Metodi che hanno senso indipendentemente dal tipo concreto

Evita di inserire comportamenti nella classe padre che si applicano solo a qualche sottoclasse.
Questo di solito indica un problema di progettazione.

Una buona regola pratica:

Se trattare un oggetto come tipo padre sembra “sbagliato”, l’astrazione è errata.

4.5 Uso delle Classi Astratte

A volte la classe padre non dovrebbe avere alcuna implementazione predefinita.
In questi casi, usa una classe astratta.

abstract class Animal {
    public abstract void speak();
}

Questo impone regole:

  • Le sottoclassi devono implementare speak()
  • La classe padre non può essere istanziata

Le classi astratte sono utili quando vuoi imporre un contratto, non fornire un comportamento.

4.6 Gli Svantaggi dell’Ereditarietà

L’ereditarietà è potente, ma ha dei compromessi:

  • Accoppiamento forte tra padre e figlio
  • Gerarchie di classi rigide
  • Refactoring più difficile in seguito

Per questi motivi, molti design Java moderni favoriscono le interfacce rispetto all’ereditarietà.

4.7 Riepilogo della Sezione

  • L’ereditarietà consente il polimorfismo tramite l’override dei metodi
  • Interagisci sempre attraverso il tipo padre
  • Le classi astratte impongono il comportamento richiesto
  • L’ereditarietà dovrebbe essere usata con cautela

Successivamente, esploreremo il polimorfismo usando le interfacce, che è spesso l’approccio preferito nei progetti Java del mondo reale.

5. Scrivere Codice Polimorfico Usando le Interfacce (implements)

5.1 Le Interfacce Rappresentano “Cosa un Oggetto Può Fare”

Nello sviluppo Java reale, le interfacce sono il modo più comune per implementare il polimorfismo.

Un’interfaccia rappresenta una capacità o ruolo, non un’identità.

interface Speaker {
    void speak();
}

A questo punto, non c’è implementazione — solo un contratto.
Qualsiasi classe che implementa questa interfaccia promette di fornire questo comportamento.

5.2 Definire il Comportamento nelle Classi che Implementano

Ora implementiamo l’interfaccia:

class Dog implements Speaker {
    @Override
    public void speak() {
        System.out.println("Woof");
    }
}

class Cat implements Speaker {
    @Override
    public void speak() {
        System.out.println("Meow");
    }
}

Queste classi non condividono una relazione padre‑figlio.
Tuttavia, possono essere trattate uniformemente tramite l’interfaccia Speaker.

answer.### 5.3 Utilizzare il Tipo Interfaccia nel Codice Chiamante

Il potere delle interfacce diventa chiaro dal lato chiamante:

Speaker s1 = new Dog();
Speaker s2 = new Cat();

s1.speak();
s2.speak();

Il codice chiamante:

  • Dipende solo dall’interfaccia
  • Non ha conoscenza delle implementazioni concrete
  • Funziona invariato quando vengono aggiunte nuove implementazioni

Questo è vero polimorfismo nella pratica.

5.4 Perché le Interfacce Sono Preferite nella Pratica

Le interfacce sono spesso preferite all’ereditarietà perché forniscono:

  • Accoppiamento debole
  • Flessibilità tra classi non correlate
  • Supporto per più implementazioni

Una classe può implementare più interfacce, ma può estendere una sola classe.
Ciò rende le interfacce ideali per progettare sistemi estensibili.

5.5 Esempio Reale: Comportamento Sostituibile

Le interfacce brillano in scenari in cui il comportamento può cambiare o espandersi:

  • Metodi di pagamento
  • Canali di notifica
  • Strategie di archiviazione dei dati
  • Meccanismi di logging

Esempio:

public void notifyUser(Notifier notifier) {
    notifier.send();
}

Puoi aggiungere nuovi metodi di notifica senza modificare questo metodo.

5.6 Interfaccia vs Classe Astratta — Come Scegliere

Se non sei sicuro di quale usare, segui questa linea guida:

  • Usa un’interfaccia quando ti interessa il comportamento
  • Usa una classe astratta quando vuoi uno stato o un’implementazione condivisa

Nella maggior parte dei progetti Java moderni, iniziare con un’interfaccia è la scelta più sicura.

5.7 Riepilogo della Sezione

  • Le interfacce definiscono contratti di comportamento
  • Consentono un polimorfismo flessibile e accoppiato debolmente
  • Il codice chiamante dipende da astrazioni, non da implementazioni
  • Le interfacce sono la scelta predefinita nel design Java professionale

Successivamente, esamineremo una trappola comune: usare instanceof e downcasting, e perché spesso indicano un problema di design.

6. Trappole Comuni: instanceof e Downcasting

6.1 Perché gli Sviluppatori Usano instanceof

Quando si impara il polimorfismo, molti sviluppatori alla fine scrivono codice come questo:

if (speaker instanceof Dog) {
    Dog dog = (Dog) speaker;
    dog.fetch();
}

Questo di solito accade perché:

  • Una sottoclasse ha un metodo non dichiarato nell’interfaccia
  • Il comportamento deve differire in base alla classe concreta
  • I requisiti sono stati aggiunti dopo il design originale

Voler “controllare il tipo reale” è un istinto naturale — ma spesso segnala un problema più profondo.

6.2 Cosa Va Storto Quando instanceof Si Diffonde

Usare instanceof occasionalmente non è intrinsecamente sbagliato.
Il problema nasce quando diventa il principale meccanismo di controllo.

if (speaker instanceof Dog) {
    ...
} else if (speaker instanceof Cat) {
    ...
} else if (speaker instanceof Bird) {
    ...
}

Questo schema porta a:

  • Codice che deve cambiare ogni volta che viene aggiunta una nuova classe
  • Logica centralizzata nel chiamante invece che nell’oggetto
  • Perdita del beneficio principale del polimorfismo

A quel punto, il polimorfismo è effettivamente aggirato.

6.3 Il Rischio del Downcasting

Il downcasting converte un tipo genitore in un sottotipo specifico:

Animal animal = new Dog();
Dog dog = (Dog) animal;

Questo funziona solo se l’assunzione è corretta.

Se l’oggetto non è effettivamente un Dog, il codice fallisce a runtime con una ClassCastException.

Il downcasting:

  • Sposta gli errori dal tempo di compilazione al runtime
  • Fa assunzioni sull’identità dell’oggetto
  • Aumenta la fragilità

6.4 Si Può Risolvere Con il Polimorfismo?

Prima di usare instanceof, chiediti:

  • Questo comportamento può essere espresso come metodo?
  • L’interfaccia può essere invece ampliata?
  • La responsabilità può essere spostata nella classe stessa?

Ad esempio, invece di controllare i tipi:

speaker.performAction();

Lascia che ogni classe decida come eseguire quell’azione.

6.5 Quando instanceof È Accettabile

Ci sono casi in cui instanceof è ragionevole:

  • Integrazione con librerie esterne
  • Codice legacy che non puoi ridisegnare
  • Strati di confine (adapter, serializer)

Italian.La regola chiave:

Mantieni instanceof ai margini, non al centro della logica.

6.6 Linee Guida Pratiche

  • Evitare instanceof nella logica di business
  • Evitare progetti che richiedono frequenti downcasting
  • Se ti senti costretto a usarli, riconsidera l’astrazione

Successivamente vedremo come il polimorfismo può sostituire la logica condizionale (if / switch) in modo pulito e scalabile.

7. Sostituire le Istruzioni if / switch Con il Polimorfismo

7.1 Un Odore di Codice Condizionale Comune

Considera questo esempio tipico:

public void processPayment(String type) {
    if ("credit".equals(type)) {
        // Credit card payment
    } else if ("bank".equals(type)) {
        // Bank transfer
    } else if ("paypal".equals(type)) {
        // PayPal payment
    }
}

A prima vista, questo codice sembra corretto.
Tuttavia, man mano che il numero di tipi di pagamento aumenta, aumenta anche la complessità.

7.2 Applicare il Polimorfismo Invece

Possiamo rifattorizzare questo usando il polimorfismo.

interface Payment {
    void pay();
}
class CreditPayment implements Payment {
    @Override
    public void pay() {
        // Credit card payment
    }
}

class BankPayment implements Payment {
    @Override
    public void pay() {
        // Bank transfer
    }
}

Codice chiamante:

public void processPayment(Payment payment) {
    payment.pay();
}

Ora, aggiungere un nuovo tipo di pagamento non richiede alcuna modifica a questo metodo.

7.3 Perché Questo Approccio È Migliore

Questo design offre diversi vantaggi:

  • La logica condizionale scompare
  • Ogni classe possiede il proprio comportamento
  • Nuove implementazioni possono essere aggiunte in modo sicuro

Il sistema diventa aperto per estensione, chiuso per modifica.

7.4 Quando Non Sostituire le Condizionali

Il polimorfismo non è sempre la scelta giusta.

Evita di abusarne quando:

  • Il numero di casi è piccolo e fisso
  • Le differenze di comportamento sono banali
  • Classi aggiuntive riducono la chiarezza

Le condizionali semplici sono spesso più chiare per una logica semplice.

7.5 Come Decidere nella Pratica

Chiediti:

  • Questo ramo crescerà nel tempo?
  • Altri aggiungeranno nuovi casi?
  • Le modifiche influenzeranno molti punti?

Se la risposta è “sì”, il polimorfismo è probabilmente la scelta migliore.

7.6 Il Refactoring Incrementale È il Migliore

Non è necessario avere un design perfetto fin dall’inizio.

  • Inizia con le condizionali
  • Rifattorizza quando la complessità aumenta
  • Lascia che il codice evolva naturalmente

Questo approccio mantiene lo sviluppo pratico e manutenibile.

Successivamente, discuteremo quando il polimorfismo dovrebbe essere usato — e quando non dovrebbe — nei progetti reali.

8. Linee Guida Pratiche: Quando Usare il Polimorfismo — e Quando Non Farlo

8.1 Segnali Che il Polimorfismo È Adatto

Il polimorfismo è più utile quando si prevede un cambiamento.
Dovresti considerarlo seriamente quando:

  • Il numero di variazioni è probabile che aumenti
  • Il comportamento cambia indipendentemente dal chiamante
  • Vuoi mantenere stabile il codice chiamante
  • Diverse implementazioni condividono lo stesso ruolo

In questi casi, il polimorfismo ti aiuta a localizzare il cambiamento e a ridurre gli effetti a catena.

8.2 Segnali Che il Polimorfismo È Eccessivo

Il polimorfismo non è gratuito. Introduce più tipi e indirezioni.

Evitalo quando:

  • Il numero di casi è fisso e piccolo
  • La logica è breve e improbabile che cambi
  • Classi extra comprometterebbero la leggibilità

8.3 Evita di Progettare per un Futuro Immaginario

Un errore comune dei principianti è aggiungere il polimorfismo in modo preventivo:

“Potremmo averne bisogno più tardi.”

Nella pratica:

  • I requisiti spesso cambiano in modi inaspettati
  • Molte estensioni previste non si verificano mai

Di solito è meglio iniziare in modo semplice e rifattorizzare quando emergono esigenze reali.

8.4 Una Visione Pratica del Principio di Sostituzione di Liskov (LSP)

Potresti incontrare il Principio di Sostituzione di Liskov (LSP) studiando la programmazione orientata agli oggetti.

Un modo pratico per capirlo è:

“Se sostituisco un oggetto con uno dei suoi sottotipi, nulla dovrebbe rompersi.”

Se l’uso di un sottotipo causa sorprese, eccezioni o gestione speciale,
l’astrazione è probabilmente sbagliata.

8.5 Poni la Domanda di Progettazione Giusta

Quando non sei sicuro, chiedi:

  • Il chiamante ha bisogno di sapere quale implementazione è questa?
  • O solo che comportamento fornisce?

Se solo il comportamento è sufficiente, il polimorfismo è di solito la scelta giusta.

8.6 Riepilogo della Sezione

  • Il polimorfismo è uno strumento per gestire il cambiamento
  • Usalo dove ci si aspetta variazione
  • Evita astrazioni premature
  • Rifattorizza verso il polimorfismo quando necessario

Ora concluderemo l’articolo con un riassunto chiaro e una sezione FAQ.

9. Riepilogo: Principali Insegnamenti sul Polimorfismo in Java

9.1 L’Idea Principale

Nel suo cuore, il polimorfismo in Java riguarda un principio semplice:

Il codice dovrebbe dipendere dalle astrazioni, non dalle implementazioni concrete.

Interagendo con gli oggetti tramite classi genitore o interfacce,
permetti al comportamento di cambiare senza riscrivere il codice chiamante.

9.2 Cosa Dovresti Ricordare

Ecco i punti più importanti di questo articolo:

  • Il polimorfismo è un concetto di progettazione, non una nuova sintassi
  • È implementato tramite l’override dei metodi e il binding dinamico
  • I tipi genitore definiscono cosa può essere chiamato
  • Il comportamento reale è deciso a runtime
  • Le interfacce sono spesso l’approccio preferito
  • instanceof e il downcasting dovrebbero essere usati con parsimonia
  • Il polimorfismo aiuta a sostituire la logica condizionale in crescita

9.3 Un Percorso di Apprendimento per Principianti

Se stai ancora costruendo l’intuizione, segui questa progressione:

  1. Familiarizzati con l’uso dei tipi interfaccia
  2. Osserva come i metodi sovrascritti si comportano a runtime
  3. Comprendi perché le condizioni diventano più difficili da mantenere
  4. Rifattorizza verso il polimorfismo quando la complessità aumenta

Con la pratica, il polimorfismo diventa una scelta di progettazione naturale piuttosto che un “concetto difficile”.

10. FAQ: Domande Comuni sul Polimorfismo in Java

10.1 Qual è la differenza tra polimorfismo e overriding di metodo?

L’overriding di metodo è un meccanismo — ridefinire un metodo in una sottoclasse.
Il polimorfismo è il principio che permette ai metodi sovrascritti di essere chiamati tramite un riferimento di tipo genitore.

10.2 L’overloading di metodo è considerato polimorfismo in Java?

Nella maggior parte dei contesti Java, no.
L’overloading è risolto a tempo di compilazione, mentre il polimorfismo dipende dal comportamento a runtime.

10.3 Perché dovrei usare interfacce o tipi genitore?

Perché lo fanno:

  • Ridurre l’accoppiamento
  • Migliorare l’estensibilità
  • Stabilizzare il codice chiamante

Il tuo codice diventa più facile da mantenere man mano che i requisiti evolvono.

10.4 L’uso di instanceof è sempre negativo?

No, ma dovrebbe essere limitato.

È accettabile in:

  • Strati di confine
  • Sistemi legacy
  • Punti di integrazione

Evita di usarlo nella logica di business centrale.

10.5 Quando dovrei scegliere una classe astratta invece di un’interfaccia?

Usa una classe astratta quando:

  • Hai bisogno di stato o implementazione condivisi
  • Esiste una forte relazione “è-un”

Usa le interfacce quando il comportamento e la flessibilità sono più importanti.

10.6 Il polimorfismo penalizza le prestazioni?

Nelle tipiche applicazioni aziendali, le differenze di prestazioni sono trascurabili.

Leggibilità, manutenibilità e correttezza sono molto più importanti.

10.7 Dovrei sostituire ogni if o switch con il polimorfismo?

No.

Usa il polimorfismo quando la variazione è attesa e in crescita.
Mantieni le condizioni quando la logica è semplice e stabile.

10.8 Quali sono esempi di buone pratiche?

Ottimi scenari di pratica includono:

  • Elaborazione dei pagamenti
  • Sistemi di notifica
  • Esportatori di formati di file
  • Strategie di logging

Ovunque il comportamento debba essere sostituibile, il polimorfismo si adatta naturalmente.