Perché evitare l'ereditarietà di Java "Estende"

40

Jame Gosling ha detto

“You should avoid implementation inheritance whenever possible.”

e invece, usa l'ereditarietà dell'interfaccia.

Ma perché? Come possiamo evitare di ereditare la struttura di un oggetto usando la parola chiave "extends", e allo stesso tempo rendere il nostro codice orientato agli oggetti?

Qualcuno potrebbe per esempio fornire un esempio orientato agli oggetti che illustri questo concetto in uno scenario come "ordinare un libro in un negozio di libri?"

    
posta newbie 10.05.2011 - 16:00
fonte

6 risposte

37

Gosling non ha detto di evitare l'uso della parola chiave extends ... ha semplicemente detto di non usare l'ereditarietà come mezzo per ottenere il riutilizzo del codice.

L'ereditarietà non è di per sé negativa ed è uno strumento molto potente (essenziale) da utilizzare nella creazione di strutture OO. Tuttavia, quando non viene usato correttamente (cioè quando viene usato per qualcosa di altro rispetto alla creazione di strutture Object) crea un codice strettamente accoppiato e molto difficile da mantenere.

Poiché l'ereditarietà deve essere utilizzata per la creazione di strutture polimorfiche, può essere eseguita altrettanto facilmente con le interfacce, da cui l'istruzione di utilizzare le interfacce anziché le classi durante la progettazione delle strutture a oggetti. Se ti ritrovi a usare extends come mezzo per evitare di copiare il codice da un oggetto a un altro, forse lo stai usando male e sarebbe meglio servire con una classe specializzata per gestire questa funzionalità e condividere invece quel codice attraverso la composizione.

Leggi il Principio di sostituzione di Liskov e capiscilo. Ogni volta che stai per estendere una classe (o un'interfaccia per quella materia) chiediti se stai violando il principio, se sì, allora sei più probabile (ab) usare l'ereditarietà in modi innaturali. È facile da fare e spesso sembra la cosa giusta, ma renderà il tuo codice più difficile da mantenere, testare ed evolvere.

--- Modifica Questa risposta rappresenta una buona argomentazione per rispondere alla domanda in termini più generali.

    
risposta data 10.05.2011 - 16:26
fonte
9

All'inizio della programmazione orientata agli oggetti, l'ereditarietà dell'implementazione era il martello d'oro del riutilizzo del codice. Se in qualche classe esisteva una funzionalità, la cosa da fare era ereditare pubblicamente da quella classe (erano i giorni in cui il C ++ era più diffuso di Java), e si otteneva quella funzionalità "gratuitamente".

Tranne che non era veramente gratuito. Il risultato erano gerarchie ereditarie estremamente complesse che erano quasi impossibili da comprendere, compresi gli alberi ereditari a forma di diamante che erano difficili da gestire. Questo fa parte del motivo per cui i progettisti di Java hanno scelto di non supportare l'ereditarietà multipla delle classi.

Il consiglio di Gosling (e altre dichiarazioni) come se fosse una medicina strong per una malattia abbastanza grave, ed è possibile che alcuni bambini siano stati buttati fuori con l'acqua sporca. Non c'è niente di sbagliato nell'usare l'ereditarietà per modellare una relazione tra classi che è veramente is-a . Ma se stai solo cercando di utilizzare una funzionalità di un'altra classe, farai meglio a indagare prima su altre opzioni.

    
risposta data 10.05.2011 - 16:33
fonte
6

L'ereditarietà ha raccolto la reputazione piuttosto spaventosa ultimamente. Una ragione per evitarlo va di pari passo con il principio di progettazione della "composizione sull'ereditarietà", che fondamentalmente incoraggia le persone a non usare l'ereditarietà laddove quella non è la vera relazione, solo per salvare un po 'di scrittura del codice. Questo tipo di ereditarietà può causare alcuni bug difficili da trovare e difficili da risolvere. La ragione per cui probabilmente il tuo amico suggerisce di usare un'interfaccia è perché le interfacce forniscono una soluzione più flessibile e manutenibile alle API distribuite. Più spesso consentono di apportare modifiche a un'api senza rompere il codice dell'utente, in cui l'esposizione delle classi spesso infrange il codice dell'utente. Il miglior esempio di questo in. Net è la differenza di utilizzo tra List e IList.

    
risposta data 10.05.2011 - 16:14
fonte
5

Perché puoi estendere solo una classe, mentre puoi implementare molte interfacce.

Una volta ho sentito che l'ereditarietà definisce ciò che sei, ma le interfacce definiscono un ruolo che puoi riprodurre.

Le interfacce consentono anche un migliore utilizzo del polimorfismo.

Questo potrebbe essere parte del motivo.

    
risposta data 10.05.2011 - 16:12
fonte
1

Mi è stato insegnato anche questo e preferisco le interfacce dove possibile (ovviamente uso ancora l'ereditarietà in cui è ragionevole).

Una cosa penso che sia disaccoppiare il tuo codice da implementazioni specifiche. Diciamo che ho una classe chiamata ConsoleWriter e che viene passata in un metodo per scrivere qualcosa e la scrive sulla console. Ora diciamo che voglio passare alla stampa su una finestra della GUI. Bene, ora devo modificare il metodo o scriverne uno nuovo che consideri GUIWriter come un parametro. Se avessi iniziato definendo un'interfaccia IWriter e avessi preso il metodo in un IWriter, avrei potuto iniziare con ConsoleWriter (che implementava l'interfaccia IWriter) e poi scrivere una nuova classe chiamata GUIWriter (che implementa anche l'interfaccia IWriter) e poi I Dovrebbe solo cambiare la classe che viene passata.

Un'altra cosa (che è vera per C #, non è sicuro di Java) è che si può estendere solo 1 classe ma implementare molte interfacce. Diciamo che ho avuto un corso chiamato Teacher, MathTeacher e HistoryTeacher. Ora MathTeacher e HistoryTeacher si estendono da Insegnante ma cosa succede se vogliamo una classe che rappresenti qualcuno che è sia un MathTeacher che un HistoryTeacher. Può diventare piuttosto confuso quando provi ad ereditare da più classi quando puoi farlo solo una alla volta (ci sono modi, ma non sono esattamente ottimali). Con le interfacce puoi avere 2 interfacce chiamate IMathTeacher e IHistoryTeacher e quindi avere una classe che si estende da Insegnante e implementa quelle 2 interfacce.

Uno svantaggio che con l'uso delle interfacce è che a volte vedo persone duplicate codice (dal momento che devi creare l'implementazione per ogni classe implementa l'interfaccia) tuttavia esiste una soluzione pulita a questo problema, ad esempio l'uso di cose come i delegati (non sono sicuro di quale sia l'equivalente Java).

Il motivo principale per utilizzare le interfacce sulle eredità è il disaccoppiamento del codice di implementazione, ma non pensare che l'ereditarietà sia malvagia, poiché è ancora molto utile.

    
risposta data 10.05.2011 - 16:30
fonte
0

Se si eredita da una classe, si prende una dipendenza non solo da quella classe, ma si deve anche onorare qualsiasi contratto implicito creato dai client di quella classe. Uncle Bob fornisce un ottimo esempio di come sia facile violare in modo sottile il Principio di sostituzione di Liskov utilizzando il Square estende il rettangolo dilemma. Le interfacce, poiché non hanno implementazione, non hanno nemmeno contratti nascosti.

    
risposta data 10.05.2011 - 17:10
fonte

Leggi altre domande sui tag