Tanto la herencia de clase como las interfaces tienen su lugar. Herencia significa "es un", mientras que una interfaz proporciona un contrato que define cómo se comporta "algo".
Yo diría que usar interfaces con más frecuencia no es una mala práctica en absoluto. Actualmente estoy leyendo "Efectivo C # - 50 formas específicas para mejorar su C #" por Bill Wagner. El artículo número 22 establece, y una cita, "Prefiero definir e implementar interfaces a la herencia".
En general, uso clases base cuando necesito definir una implementación específica de comportamiento común entre tipos relacionados conceptualmente. Más a menudo uso interfaces. De hecho, normalmente empiezo definiendo una interfaz para una clase cuando empiezo a crear una ... incluso si al final no compilo la interfaz, encuentro que ayuda comenzar definiendo la API pública de la clase clase desde el principio. Si descubro que tengo varias clases implementando la interfaz, y la lógica de implementación es idéntica, solo entonces me preguntaré si tendría sentido implementar una clase base común entre los tipos.
Un par de citas del libro de Bill Wagners ...
todas las clases derivadas incorporan inmediatamente ese comportamiento. Agregar un miembro a una interfaz rompe todas las clases que implementan esa interfaz. No contendrán el nuevo método y ya no se compilarán. Cada implementador debe actualizar ese tipo para incluir al nuevo miembro. Elegir entre una clase base abstracta y una interfaz es una cuestión de cómo apoyar mejor sus abstracciones a lo largo del tiempo. Las interfaces son fijas: libera una interfaz como un contrato para un conjunto de funcionalidades que cualquier tipo puede implementar. Las clases base se pueden extender con el tiempo. Esas extensiones se convierten en parte de cada clase derivada. Los dos modelos se pueden mezclar para reutilizar el código de implementación mientras se admiten múltiples interfaces ". No contendrán el nuevo método y ya no se compilarán. Cada implementador debe actualizar ese tipo para incluir al nuevo miembro. Elegir entre una clase base abstracta y una interfaz es una cuestión de cómo apoyar mejor sus abstracciones a lo largo del tiempo. Las interfaces son fijas: libera una interfaz como un contrato para un conjunto de funcionalidades que cualquier tipo puede implementar. Las clases base se pueden extender con el tiempo. Esas extensiones se convierten en parte de cada clase derivada. Los dos modelos se pueden mezclar para reutilizar el código de implementación mientras se admiten múltiples interfaces ". No contendrán el nuevo método y ya no se compilarán. Cada implementador debe actualizar ese tipo para incluir al nuevo miembro. Elegir entre una clase base abstracta y una interfaz es una cuestión de cómo apoyar mejor sus abstracciones a lo largo del tiempo. Las interfaces son fijas: libera una interfaz como un contrato para un conjunto de funcionalidades que cualquier tipo puede implementar. Las clases base se pueden extender con el tiempo. Esas extensiones se convierten en parte de cada clase derivada. Los dos modelos se pueden mezclar para reutilizar el código de implementación mientras se admiten múltiples interfaces ". Libera una interfaz como un contrato para un conjunto de funcionalidades que cualquier tipo puede implementar. Las clases base se pueden extender con el tiempo. Esas extensiones se convierten en parte de cada clase derivada. Los dos modelos se pueden mezclar para reutilizar el código de implementación mientras se admiten varias interfaces ". Libera una interfaz como un contrato para un conjunto de funcionalidades que cualquier tipo puede implementar. Las clases base se pueden extender con el tiempo. Esas extensiones se convierten en parte de cada clase derivada. Los dos modelos se pueden mezclar para reutilizar el código de implementación mientras se admiten múltiples interfaces ".
"Las interfaces de codificación proporcionan una mayor flexibilidad a otros desarrolladores que la codificación a tipos de clase base".
"El uso de interfaces para definir API para una clase proporciona una mayor flexibilidad".
"Cuando su tipo expone propiedades como tipos de clase, expone toda la interfaz a esa clase. Usando interfaces, puede elegir exponer solo los métodos y propiedades que desea que los clientes usen".
"Las clases base describen e implementan comportamientos comunes a través de tipos concretos relacionados. Las interfaces describen piezas atómicas de funcionalidad que los tipos concretos no relacionados pueden implementar. Ambas tienen su lugar. Las clases definen los tipos que usted crea. Las interfaces describen el comportamiento de esos tipos como piezas funcionales. Si comprende las diferencias, creará diseños más expresivos que sean más resistentes frente al cambio. Utilice jerarquías de clase para definir tipos relacionados. Exponga la funcionalidad utilizando interfaces implementadas en esos tipos ".