Sia l'ereditarietà delle classi che le interfacce hanno entrambi il loro posto. L'ereditarietà significa "è un" mentre un'interfaccia fornisce un contratto che definisce ciò che "si comporta come".
Direi che usare le interfacce più spesso non è affatto una cattiva pratica. Attualmente sto leggendo "Effective C # - 50 Ways specifici per migliorare il tuo C #" di Bill Wagner. L'articolo numero 22 afferma, e io cito, "Preferisci la definizione e l'implementazione delle interfacce all'ereditarietà".
Generalmente utilizzo le classi base quando devo definire un'implementazione specifica del comportamento comune tra i tipi concettualmente correlati. Più spesso uso interfacce. In effetti, di solito inizio definendo un'interfaccia per una classe quando inizio a crearne una ... anche se alla fine non compilo l'interfaccia, trovo che sia d'aiuto definire l'API pubblica della classe dal get go. Se trovo che ho più classi che implementano l'interfaccia e la logica di implementazione è identica, solo allora mi chiederò se avrebbe senso implementare una classe base comune tra i tipi.
Un paio di citazioni dal libro di Bill Wagners ...
"Le classi base astratte possono fornire alcune implementazioni per i tipi derivati, oltre a descrivere il comportamento comune.È possibile specificare membri dei dati, metodi concreti, implementazione per metodi virtuali, proprietà, eventi e indicizzatori. per alcuni dei metodi, fornendo in tal modo un riutilizzo di implementazione comune.Tutti gli elementi possono essere virtuali, astratti o non virtuali.Una classe base astratta può fornire un'implementazione per qualsiasi comportamento concreto, le interfacce non possono.Un riutilizzo dell'implementazione offre un altro vantaggio: aggiungere un metodo alla classe base, tutte le classi derivate vengono automaticamente e implicitamente migliorate, in questo senso le classi base forniscono un modo per estendere il comportamento di diversi tipi in modo efficiente nel tempo: aggiungendo e implementando funzionalità nella classe base, tutte le classi derivate incorporare immediatamente questo comportamento: l'aggiunta di un membro all'interfaccia interrompe tutte le classi che implementano tale interfaccia mantenere il nuovo metodo e non verrà più compilato. Ogni implementatore deve aggiornare quel tipo per includere il nuovo membro. Scegliere tra una classe base astratta e un'interfaccia è una questione su come meglio supportare le tue astrazioni nel tempo. Le interfacce sono corrette: rilasci un'interfaccia come contratto per un insieme di funzionalità che qualsiasi tipo può implementare. Le classi base possono essere estese nel tempo. Quelle estensioni diventano parte di ogni classe derivata. I due modelli possono essere mescolati per riutilizzare il codice di implementazione mentre supportano più interfacce. "
"Le interfacce di codifica offrono maggiore flessibilità agli altri sviluppatori rispetto alla codifica per tipi di classi base."
"L'utilizzo delle interfacce per definire le API per una classe offre una maggiore flessibilità."
"Quando il tuo tipo espone le proprietà come tipi di classe, espone l'intera interfaccia a quella classe.Usando le interfacce, puoi scegliere di esporre solo i metodi e le proprietà che i client devono utilizzare."
"Le classi base descrivono e implementano comportamenti comuni attraverso tipi concreti correlati: le interfacce descrivono parti atomiche di funzionalità che possono essere implementate da tipi concreti non collegati, entrambe hanno il loro posto e le classi definiscono i tipi creati. Se capisci le differenze, creerai più progetti espressivi che sono più resilienti di fronte alle modifiche.Usilizzi le gerarchie di classi per definire i tipi correlati.Scopri la funzionalità utilizzando le interfacce implementate in questi tipi. "