Alla ricerca di un esempio del mondo reale che mostri che la composizione può essere superiore all'ereditarietà [chiusa]

6

Ho visto un sacco di conferenze su Clojure e programmazione funzionale di Rich Hickey e alcune delle conferenze di SICP, e sono venduto su molti concetti di programmazione funzionale. Ho incorporato alcuni di essi nel mio codice C # in un lavoro precedente, e per fortuna è stato facile scrivere codice C # in uno stile più funzionale.

Al mio nuovo lavoro usiamo l'ereditarietà di Python e multiple è di gran moda. I miei collaboratori sono molto intelligenti, ma devono produrre codice in fretta data la natura della compagnia. Sto imparando sia gli strumenti che il codice base, ma anche l'architettura stessa mi rallenta. Non ho scritto la gerarchia di classi esistente (né potrei ricordare tutto su di esso), e così, quando ho iniziato ad aggiungere una funzionalità abbastanza piccola, ho capito che dovevo leggere molto codice nel processo. In superficie il codice è ben organizzato e suddiviso in piccole funzioni / metodi e non copia-incolla-ripetitivo, ma il rovescio della medaglia non è ripetitivo è che c'è qualche funzionalità magica nascosta da qualche parte nella catena gerarchica che unisce magicamente le cose insieme e funziona a mio nome, ma è molto difficile da trovare e da seguire. Ho dovuto accendere un profiler ed eseguirlo attraverso diversi esempi e tracciare il grafico di esecuzione e passare un debugger un paio di volte, cercare il codice per alcune sottostringhe e solo leggere pagine al momento. Sono abbastanza sicuro che una volta fatto, il mio codice risultante sarà breve e ben organizzato, e tuttavia non molto leggibile.

Ciò che scrivo si sente dichiarativo, come se stessi scrivendo un file XML che guida un altro motore magico, tranne per il fatto che non esiste una documentazione chiara su come dovrebbe essere l'XML e su cosa fa il motore eccetto per gli esempi esistenti che io può leggere oltre al codice sorgente per il 'motore'.

Ci deve essere un modo migliore. L'IMO che usa la composizione sull'ereditarietà può aiutare un bel po '. In questo modo il calcolo sarà lineare anziché saltare su tutto l'albero della gerarchia. Ogni volta che la funzionalità non si adatta perfettamente a un modello di ereditarietà, dovrà essere deformata per adattarsi, o l'intera gerarchia di ereditarietà dovrà essere sottoposta a refactoring / ribilanciamento, una specie di albero binario sbilanciato che necessita di rimodellare di volta in volta in ordine per migliorare il tempo medio di ricerca.

Come ho detto prima, i miei collaboratori sono molto intelligenti; hanno semplicemente fatto le cose in un certo modo e probabilmente hanno la capacità di tenere un sacco di cazzate non legate nella loro testa in una volta.

Voglio convincerli a dare una composizione e un approccio funzionale a quello OOP. Per farlo, ho bisogno di trovare del materiale molto buono. Non credo che una conferenza SCIP o una di Rich Hickey lo farà - temo che verrà contrassegnato come troppo accademico. Quindi, semplici esempi di classi Dog and Frog e AddressBook non connivono in un modo o nell'altro: mostrano come l'ereditarietà possa essere convertita in composizione ma non perché sia realmente e obiettivamente migliore.

Quello che sto cercando è un esempio di codice reale che è stato scritto con molta ereditarietà, poi ha colpito un muro e riscritto in uno stile diverso che usa la composizione. Forse c'è un blog o un capitolo. Sto cercando qualcosa che possa riassumere e illustrare il tipo di dolore che sto attraversando.

Ho già lanciato l'espressione "composizione sull'eredità", ma non è stata accolta con entusiasmo come speravo. Non voglio essere percepito come un nuovo ragazzo a cui piace lamentarsi e colpire il codice esistente mentre cerca un approccio perfetto pur non contribuendo abbastanza velocemente. Allo stesso tempo, il mio istinto è convinto che l'eredità sia spesso lo strumento del male e voglio mostrare un modo migliore in un prossimo futuro.

Sei incappato in grandi risorse che possono aiutarmi?

    
posta Job 27.11.2012 - 01:48
fonte

3 risposte

5

Non è una dualità; la domanda non è "dovrei usare la composizione o l'ereditarietà", ma "in quali punti del mio grafico oggetto voglio avere gerarchie di ereditarietà".

Per quanto riguarda gli esempi, mi piace venire con metafore auto, quindi ecco qui.

Supponiamo di avere una classe base astratta Vehicle e due classi che la implementano: Car e Truck . Hanno un sacco di metodi, ad es. get_num_wheels() e get_fuel_type() , get_num_seats() , ecc. Tutto va bene, ma ora è necessario dividere ulteriormente la classe Truck , perché devi supportare camion a due assi (con quattro ruote) e tre assi camion (sei ruote). Devi anche supportare le auto che funzionano a gasolio e le auto a benzina. Così ora hai TwoAxleTruck , ThreeAxleTruck , DieselCar e GasolineCar . Oh, ma ora la tua autovettura arriva in una versione pickup con solo due posti, due in più, perché quella macchina può anche essere diesel o benzina. E ora il cliente vuole che tu tenga traccia del tipo di carico che i tuoi veicoli possono supportare; ci sono otto diverse classi di carico, e tutti i tipi di camion e pickup dovrebbero essere in grado di supportarlo. Qualcuno sta contando? E abbiamo un altro problema: i pickup ei camion a due assi hanno molto in comune, ma poiché il pickup eredita da Car e non da Truck , dobbiamo duplicare questo comportamento comune su entrambe le classi.

È qui che entra in gioco la composizione: definiamo una classe Vehicle , che ha alcune proprietà, diciamo Seats , Wheels , Engine , CargoHold e ognuna di queste ha un tipo polimorfico. Il bello è che il veicolo stesso non deve più essere polimorfico: puoi semplicemente combinare le singole istanze delle tue classi base componenti per formare qualsiasi tipo di veicolo di cui hai bisogno. Tieni presente che stiamo ancora utilizzando l'ereditarietà per condividere funzionalità comuni: abbiamo suddiviso la funzionalità in gruppi correlati e creato alberi di ereditarietà individuali per ciascuno di essi. Ovviamente, questo funziona meglio se i componenti sono funzionalmente ortogonali, cioè che possono funzionare in modo del tutto indipendente - ad esempio, le ruote non hanno bisogno di sapere che tipo di carico stanno trasportando.

Nota a margine; Il modello OOP di Python affronta alcuni dei problemi riscontrati nella classica situazione di ereditarietà attraverso meccanismi come l'ereditarietà multipla avanzata (diversamente da, ad esempio, C ++, Python risolve i conflitti di classe base come il problema dei diamanti in modo ben definito e utile), mix- ins, costruzione di oggetti run-time, ecc. A causa di ciò, l'ereditarietà multipla a volte può essere la soluzione migliore in python anche se sarebbe in grado di creare codice disordinato in qualcosa come Java o C #.

    
risposta data 27.11.2012 - 10:37
fonte
3

Senza vedere un esempio specifico, è difficile dire se l'ereditarietà sia inappropriata o meno. A volte un problema è intrinsecamente complesso e il codice "magic glue" potrebbe essere solo un segno di un'astrazione buona (anche se scarsamente documentata).

Detto questo, se il codice può essere veramente migliorato applicando un determinato principio di progettazione, il modo migliore per dimostrarlo ai tuoi colleghi è farlo. Prendine una piccola parte, cambialo per usare la composizione invece dell'ereditarietà e presenta quel codice. Hai già scoperto che lanciare una frase in giro non aiuta. Questo perché stanno confrontando il codice concreto e familiare con il tuo concetto teorico. Devi rendere il tuo concetto altrettanto concreto. Un altro esempio accademico non aiuterà.

    
risposta data 27.11.2012 - 05:19
fonte
0

Tecnicamente ciò che stai chiedendo è impossibile da ottenere; la composizione e l'ereditarietà sono due concetti distinti quando si parla di modellazione di oggetti e uno non può essere sostituito da altro.

Durante la modellazione non c'è nulla di sbagliato nell'ereditarietà; il vero problema è quando in realtà non hai ereditarietà e nel codice hai ereditarietà. Quando si usa l'ereditarietà solo per il riutilizzo del codice, di solito si sbaglia; qui la composizione è un approccio migliore. In questo scenario, viene applicato il famoso principio GoF "Favorisci la composizione sull'ereditarietà" , ossia l'ereditarietà dell'utilizzo quando e solo quando si modella una relazione "is-a"; sarebbe una pessima idea progettuale modellare una relazione "è-a" attraverso la composizione. Inoltre, ci sono molte più relazioni "ha-un" nel mondo reale che relazioni "è-a" .

D'altra parte, quando non si sta veramente modellando un'entità del mondo reale (materiale software) per ottenere il riutilizzo del codice, la composizione è preferita nella maggior parte dei casi. Ci sono esempi che potrebbero essere forniti qui.

    
risposta data 27.11.2012 - 11:03
fonte