Stavo leggendo i modelli di progettazione e ho letto che il modello di progettazione del prototipo elimina l'eccessiva sottoclasse.
Perché la sottoclasse è male? Quale vantaggio porterebbe l'uso di un prototipo rispetto alla sottoclasse?
Stavo leggendo i modelli di progettazione e ho letto che il modello di progettazione del prototipo elimina l'eccessiva sottoclasse.
Perché la sottoclasse è male? Quale vantaggio porterebbe l'uso di un prototipo rispetto alla sottoclasse?
Risposte:
Perché la sottoclasse è troppo male
"Troppo" è un appello di giudizio; ma prendilo da me è brutto. Il codice con cui lavoro ha eredità DEEP e il codice è dannatamente difficile da leggere, comprendere, seguire, tracciare, eseguire il debug, ecc. È effettivamente impossibile scrivere il codice di prova per questa roba.
Il modello prototipo elimina la sottoclasse
O la domanda significa "elimina troppe sottoclassi?". Questo modello richiede la clonazione di un oggetto "modello" per evitare la sottoclasse, almeno a quel punto. Non esiste una regola che dice che il "modello" non può essere una sottoclasse.
Favorire la composizione rispetto all'eredità
Questa idea qui include anche la delega e l'aggregazione. Seguire questa euristica significa che il tuo software tende ad essere più flessibile, più facile da mantenere, estendere e riutilizzare.
Quando una classe è composta da parti, è possibile sostituirle in fase di esecuzione . Questo ha un profondo effetto sulla testabilità.
Il test è più semplice . È possibile utilizzare parti false (ad es. "Beffe", "doppie" e altri test-talk). La profonda eredità del nostro codice significa che dobbiamo istanziare l'intera gerarchia per testarne una parte. Nel nostro caso ciò non è possibile senza eseguire il codice nel suo ambiente reale. Ad esempio, abbiamo bisogno di un database per creare un'istanza di oggetti business.
I cambiamenti derivano da effetti collaterali e incertezza : più "base" è la classe, più gli effetti sono diffusi, nel bene e nel male. Ci possono essere cambiamenti desiderati che non osi apportare a causa dell'incertezza degli effetti collaterali. O un cambiamento che è positivo per un posto nella nostra catena di eredità è negativo per un altro. Questa è sicuramente la mia esperienza.
Penso che uno dei motivi per cui questa è stata considerata una cattiva pratica sia che nel tempo ogni sottoclasse può contenere attributi e metodi che non hanno senso per quell'oggetto più si procede lungo la catena (in una gerarchia poco sviluppata). Puoi finire con una classe gonfia che contiene elementi che non hanno senso per quella classe.
Sono agnostico a riguardo. Sembra dogmatico solo dire che è male. Tutto è male se usato in modo improprio.
Due ragioni.
Sono le prestazioni di runtime. Ogni volta che invochi una sottoclasse da una superclasse stai effettuando una chiamata al metodo, quindi se hai nidificato cinque classi e invochi un metodo nella classe base, eseguirai cinque invocazioni di metodo. La maggior parte dei compilatori / runtime proverà a riconoscerlo e a ottimizzare le chiamate extra, ma è sicuro farlo solo per casi molto semplici.
È la sanità mentale dei tuoi colleghi programmatori. Se annidate cinque classi, devo esaminare tutte e quattro le classi genitore per stabilire che state davvero chiamando un metodo nella classe base, diventa noioso. Potrei anche dover passare attraverso cinque invocazioni di metodo quando eseguo il debug del programma.
"Troppo" è una chiamata di giudizio.
Come per la maggior parte delle cose nella programmazione, la sottoclasse non è intrinsecamente cattiva quando ha senso. Quando il modello della soluzione del problema ha più senso come gerarchia piuttosto che come composizione di parti, la sottoclasse può essere l'opzione preferita.
Detto questo, favorire la composizione rispetto all'eredità è una buona regola empirica.
Quando si esegue una sottoclasse, i test possono essere più difficili (come notato da radarbob ). In una gerarchia profonda, isolare il codice problematico è più difficile perché l'istanza di una sottoclasse richiede l'inserimento dell'intera gerarchia di superclassi e un mucchio di codice aggiuntivo. Con un approccio compositivo, puoi isolare e testare ogni componente del tuo oggetto aggregato con unit test, oggetti finti, ecc.
La sottoclasse può anche farti scoprire dettagli dell'implementazione che potresti non voler. Ciò rende difficile controllare l'accesso alla rappresentazione sottostante dei tuoi dati e ti lega a una particolare implementazione del tuo modello di supporto.
Un esempio che mi viene in mente è java.util.Properties, che eredita da Hashtable. La classe Properties è destinata esclusivamente all'archiviazione di coppie String-String (questo è indicato nei Javadocs). A causa dell'ereditarietà, i metodi put e putAll di Hashtable sono stati esposti agli utenti di Proprietà. Mentre il modo "giusto" per memorizzare una proprietà è con setProperty (), non c'è assolutamente nulla che impedisce a un utente di chiamare put () e passare una stringa e un oggetto.
Fondamentalmente, la semantica delle Proprietà è mal definita a causa dell'eredità. Inoltre, se qualcuno dovesse mai voler cambiare l'oggetto di supporto per Proprietà, l'impatto avrà un impatto molto maggiore sul codice che utilizza Proprietà rispetto a se Proprietà avesse adottato un approccio più compositivo.
L'ereditarietà può rompere l'incapsulamento in alcuni casi e, se ciò accade, è un male. Leggi di più.
Ai vecchi tempi senza eredità, una libreria pubblicava un'API e un'applicazione che la utilizza utilizza ciò che è pubblicamente visibile, e la libreria ora ha il suo modo di gestire gli ingranaggi interni che continuano a cambiare senza interrompere l'app.
Man mano che le esigenze si evolvono, la biblioteca può evolversi e può avere più clienti; oppure può fornire funzionalità estese ereditando dalla propria classe con altri gusti. Fin qui tutto bene.
Tuttavia, la cosa fondamentale è che se un'applicazione prende la classe e inizia a sottoclassarla, ora una sottoclasse è in realtà un client piuttosto che un partner interno. Tuttavia, a differenza di un modo chiaro e inequivocabile di definire l'API pubblica, la sottoclasse è ora molto più profonda all'interno della libreria (accesso a tutte le variabili private e così via). Non è ancora male, ma un brutto personaggio può colmare un'API che è troppo dentro l'APP e nella libreria-
In sostanza, mentre questo potrebbe non essere sempre il caso, ma
È facile abusare dell'eredità per interrompere l'incapsulamento
Consentire la sottoclasse può essere errato se quel punto.
Risposta rapida breve:
Dipende da cosa stai facendo.
Risposta noiosa estesa:
Uno sviluppatore può creare un'app. utilizzando modelli di sottoclasse o (prototipo). A volte una tecnica funziona meglio. A volte l'altra tecnica funziona meglio.
La scelta con la tecnica funziona meglio, inoltre, richiede di considerare se uno scenario di "Eredità multipla" sia presente. Anche se il linguaggio di programmazione supporta solo singole eredità, modelli o prototipi.
Nota complementare:
Alcune risposte si riferiscono alla "eredità multipla". Tuttavia, non la domanda principale, è legata all'argomento. Esistono diversi post simili su "ereditarietà multipla vs composizione". Ho avuto un caso in cui ho mescolato entrambi.