Perché la sottoclasse è troppo male (e quindi perché dovremmo usare i prototipi per eliminarlo)?


10

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?


dice chi? Avete un link ?
Camus

Stavo leggendo p.118 di questo libro. goo.gl/6JGw1
David Faux

Risposte:


19

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.


Sarei davvero interessato a vedere alcuni esempi di quando l'ereditarietà rende difficile il test (in particolare il deridere) e come la composizione aiuta in questo.
Armand

@alison: un metodo in una classe derivata che utilizza un altro metodo della classe base in cui l'implementazione nella classe base rende praticamente impossibile testare scenari di errore. Se fosse stata una composizione, sarebbe più facile iniettare un oggetto che provoca l'errore
Rune FS

@allison: esempi? Quando la base di codice è molto ampia, quando le classi di base vengono utilizzate direttamente da dozzine di classi e centinaia per ereditarietà. Perché il punto di ereditarietà profonda viene riutilizzato e quindi una classe di base viene generalizzata al punto che la traccia dello stack del debugger non può mostrare quali metodi vengono effettivamente chiamati. Infine, quando non esiste un'iniezione di dipendenza già integrata in tale Frankenstein. Una classe di alto livello può "essere una" mezza dozzina o più di altre cose che collettivamente "hanno" dozzine di oggetti interni, ognuno con la propria casa di divertimento ereditaria.
radarbob

8

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.


2

Due ragioni.

  1. 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.

  2. È 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.


6
-1: hai ragione su # 2; ma non sul n. 1. Numerosi livelli di ereditarietà non influiranno necessariamente sulle prestazioni di runtime.
Jim G.

Preoccupato per un paio di chiamate di procedura extra a livello di codice macchina e utilizzo di OP ed ereditarietà, e ti preoccupi della sanità mentale dei tuoi colleghi programmatori .....
mattnz

@JimG. Potrebbe non farlo, ma può. :) C'è anche la possibilità di preoccuparsi dell'utilizzo della memoria: se non riutilizzi tutte le variabili della base, potrebbero comunque essere allocate come parte della costruzione dell'oggetto.
Adam Lear

2

"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.


2

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.


1

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.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.