Sia l'eredità di classe che le interfacce hanno entrambe il loro posto. Eredità significa "è un" mentre un'interfaccia fornisce un contratto che definisce come "qualcosa si comporta".
Direi che usare le interfacce più spesso non è affatto una cattiva pratica. Attualmente sto leggendo "Efficace C # - 50 modi specifici per migliorare il tuo C #" di Bill Wagner. L'articolo numero 22 indica, e una citazione I, "Preferire definire e implementare le interfacce all'ereditarietà".
In genere utilizzo le classi di base quando devo definire un'implementazione specifica del comportamento comune tra tipi concettualmente correlati. Più spesso uso interfacce. In effetti, normalmente inizio definendo un'interfaccia per una classe quando inizio a crearne una ... anche se alla fine non compilo l'interfaccia, trovo che aiuti iniziare iniziando definendo l'API pubblica del classe dall'inizio. Se trovo che ho più classi sia che implementano l'interfaccia, sia che 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 ...
tutte le classi derivate incorporano immediatamente quel comportamento. L'aggiunta di un membro a un'interfaccia interrompe tutte le classi che implementano tale interfaccia. Non conterranno il nuovo metodo e non verranno più compilati. Ogni implementatore deve aggiornare quel tipo per includere il nuovo membro. Scegliere tra una classe base astratta e un'interfaccia è una domanda sul modo migliore di supportare le tue astrazioni nel tempo. Le interfacce sono fisse: rilasci un'interfaccia come contratto per un set di funzionalità che qualsiasi tipo può implementare. Le classi di base possono essere estese nel tempo. Tali estensioni diventano parte di ogni classe derivata. I due modelli possono essere combinati per riutilizzare il codice di implementazione supportando più interfacce. " Non conterranno il nuovo metodo e non verranno più compilati. Ogni implementatore deve aggiornare quel tipo per includere il nuovo membro. Scegliere tra una classe base astratta e un'interfaccia è una domanda sul modo migliore di supportare le tue astrazioni nel tempo. Le interfacce sono fisse: rilasci un'interfaccia come contratto per un set di funzionalità che qualsiasi tipo può implementare. Le classi di base possono essere estese nel tempo. Tali estensioni diventano parte di ogni classe derivata. I due modelli possono essere combinati per riutilizzare il codice di implementazione supportando più interfacce. " Non conterranno il nuovo metodo e non verranno più compilati. Ogni implementatore deve aggiornare quel tipo per includere il nuovo membro. Scegliere tra una classe base astratta e un'interfaccia è una domanda sul modo migliore di supportare le tue astrazioni nel tempo. Le interfacce sono fisse: rilasci un'interfaccia come contratto per un set di funzionalità che qualsiasi tipo può implementare. Le classi di base possono essere estese nel tempo. Tali estensioni diventano parte di ogni classe derivata. I due modelli possono essere combinati per riutilizzare il codice di implementazione supportando più interfacce. " Rilasci un'interfaccia come contratto per un set di funzionalità che qualsiasi tipo può implementare. Le classi di base possono essere estese nel tempo. Tali estensioni diventano parte di ogni classe derivata. I due modelli possono essere combinati per riutilizzare il codice di implementazione supportando più interfacce. " Rilasci un'interfaccia come contratto per un set di funzionalità che qualsiasi tipo può implementare. Le classi di base possono essere estese nel tempo. Tali estensioni diventano parte di ogni classe derivata. I due modelli possono essere combinati per riutilizzare il codice di implementazione supportando più interfacce. "
"Le interfacce di codifica offrono una maggiore flessibilità agli altri sviluppatori rispetto alla codifica per tipi di classe base."
"L'uso 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 desideri che i client utilizzino."
"Le classi di base descrivono e implementano comportamenti comuni tra tipi concreti correlati. Le interfacce descrivono elementi atomici di funzionalità che i tipi concreti non correlati possono implementare. Entrambi hanno il loro posto. Le classi definiscono i tipi che crei. Le interfacce descrivono il comportamento di quei tipi come elementi di funzionalità. Se capisci le differenze, creerai progetti più espressivi che sono più resistenti di fronte al cambiamento. Usa le gerarchie di classi per definire i tipi correlati. Esponi la funzionalità usando le interfacce implementate in quei tipi. "