Credo che molte persone implementino l'ereditarietà troppo velocemente. Usandolo come strumento per ridurre il codice. Questo non è lo scopo dell'eredità. Usato in questo modo, avrai sempre questi problemi perché l'ereditarietà non è implementata correttamente.
Sulle basi, le eredità spiegano una relazione tra 2 classi. La relazione è chiamata una relazione "IS A". Nel tuo caso, SonX come descrivi, non è conforme a questa relazione. Non è un padre. Lasciami spiegare ...
I principi base SOLID di cui in particolare attiro la tua attenzione sulla parte L (< a href="https://en.wikipedia.org/wiki/Liskov_substitution_principle"> LSP per il principio di sostituzione di Liskov ). Dichiara che qualsiasi classe di figlio dovrebbe essere in grado di sostituire direttamente il suo genitore. Se non può farlo, non è conforme all'eredità corretta.
Ti trovi a infrangere questa regola ogni volta che sovrascrivi direttamente i metodi padre nelle classi figlie. I metodi principali dovrebbero essere overloaded utilizzando il polimorfismo aggiungendo un altro metodo con lo stesso nome con parametri diversi, non sovrascritto direttamente . Ad esempio ...
La classe del Padre ha un metodo Foo. Pertanto, ereditando questa classe dal Figlio, stai dicendo che il Figlio è un Padre. Pertanto, anche Son dovrebbe avere il metodo Foo.
Se dichiari il metodo Foo come virtuale in padre, così può essere sovrascritto direttamente in Son e quindi sovrascriverlo, quindi Son non è più conforme alla relazione IS A. Poiché il metodo Foo in Son fa qualcosa di diverso da come era stato originariamente definito in padre.
Tuttavia, se sovraccarichi Foo nella classe Son e gli dai una firma diversa per permetterle di fare il suo lavoro. Quindi, tecnicamente, Son può ancora fare tutte le cose che il Padre può fare insieme a qualche suo extra nel overload Foo. Questo perché il Foo originale ereditato dal Padre, è ancora intatto e pienamente funzionante.
Se davvero devi avere un modello di progettazione per disaccoppiare le astrazioni (che è anche una buona pratica in generale), esiste un modello di design chiamato il pattern del bridge e il pattern Visitor tra gli altri (come raccomandato nei commenti di Christopher e Jordao). Ma io incoraggio strongmente un pieno funzionamento (nel senso che hai fatto alcuni esempi di ciascuno nel codice reale quindi non è una comprensione teorica di tutti i principi SOLIDI.
Suggerirei anche un modello più semplice. Introduci oggetto parametro . In genere si applica se hai metodi con molti parametri (personalmente mi sento più di 3 o 4), dove fai una lezione e la passi invece.
Non dimenticare inoltre che puoi ereditare ed estendere le interfacce e le classi astratte e quindi ereditare da quelle classi o interfacce figlio. Ma il pattern del bridge è probabilmente una scelta migliore a causa del disaccoppiamento che offre.
Spero che questo aiuti.