¿Cuándo usar interfaces o clases abstractas? Cuándo usar ambos?

Si bien ciertas pautas establecen que debe usar una interfaz cuando quiere definir un contrato para una clase donde la herencia no es clara ( IDomesticated ) y la herencia cuando la clase es una extensión de otra ( Cat : Mammal , Snake : Reptile ), hay Casos en los que (en mi opinión) estas pautas entran en un área gris.

Por ejemplo, supongamos que mi implementación fue Cat : Pet . Pet es una clase abstracta. ¿Debería expandirse a Cat : Mammal, IDomesticated donde Mammal es una clase abstracta e IDomesticated es una interfaz? ¿O estoy en conflicto con los principios KISS / YAGNI (aunque no estoy seguro de si habrá una clase Wolf en el futuro, que no podría heredar de Pet )?

Al alejarnos de las metafóricas Cat y Pet s, digamos que tengo algunas clases que representan fonts de datos entrantes. Todos necesitan implementar la misma base de alguna manera. Podría implementar algún código genérico en una clase de Source abstracta y heredar de él. También podría hacer una interfaz de ISource (que se siente más “correcta” para mí) y volver a implementar el código genérico en cada clase (que es menos intuitivo). Finalmente, pude “tener el pastel y comérselo” haciendo tanto la clase abstracta como la interfaz. ¿Qué es mejor?

Estos dos casos muestran puntos por usar solo una clase abstracta, solo una interfaz y el uso de una clase abstracta y una interfaz. ¿Son todas estas elecciones válidas, o hay “reglas” para cuando una debería usarse sobre otra?


Me gustaría aclarar eso al “usar una clase abstracta y una interfaz” que incluye el caso cuando esencialmente representan lo mismo ( Source e ISource tienen los mismos miembros), pero la clase agrega funcionalidad genérica mientras que la interfaz especifica el contrato.

También vale la pena señalar que esta pregunta es principalmente para los idiomas que no son compatibles con la herencia múltiple (como .NET y Java).

Como primera regla general, prefiero las clases abstractas sobre las interfaces, basadas en las Pautas de diseño de .NET . El razonamiento es mucho más amplio que .NET, pero se explica mejor en el libro Pautas de diseño de marcos .

El razonamiento principal detrás de la preferencia de las clases base abstractas es el control de versiones, porque siempre puede agregar un nuevo miembro virtual a una clase base abstracta sin romper los clientes existentes. Eso no es posible con las interfaces.

Hay escenarios en los que una interfaz sigue siendo la opción correcta (especialmente cuando no le interesan las versiones), pero conocer las ventajas y desventajas le permite tomar la decisión correcta.

Entonces, como respuesta parcial antes de continuar: Tener una interfaz y una clase base solo tiene sentido si decides codificar contra una interfaz en primer lugar. Si permite una interfaz, debe codificar solo contra esa interfaz, ya que de lo contrario estaría violando el Principio de Sustitución de Liskov. En otras palabras, incluso si proporciona una clase base que implementa la interfaz, no puede permitir que su código consum esa clase base.

Si decide codificar contra una clase base, tener una interfaz no tiene sentido.

Si decide codificar contra una interfaz, tener una clase base que proporcione funcionalidad predeterminada es opcional. No es necesario, pero puede acelerar las cosas para los implementadores, por lo que puede proporcionar uno como cortesía.

Un ejemplo que me viene a la mente es en ASP.NET MVC. La canalización de solicitudes funciona en IController, pero hay una clase base de Controlador que normalmente usa para implementar el comportamiento.

Respuesta final: si usa una clase base abstracta, use solo eso. Si se usa una interfaz, una clase base es una cortesía opcional para los implementadores.


Actualización: ya no prefiero las clases abstractas sobre las interfaces, y no lo hago desde hace mucho tiempo; en cambio, prefiero la composición sobre la herencia, usando SOLID como guía.

(Si bien podría editar el texto anterior directamente, cambiaría radicalmente la naturaleza de la publicación, y como algunas personas lo han encontrado lo suficientemente valioso como para votarlo, prefiero dejar el texto original en pie, y en su lugar agregar esto Nota: la última parte de la publicación sigue siendo significativa, por lo que sería una pena eliminarla también).

Tiendo a usar clases base (abstractas o no) para describir qué es algo, mientras uso interfaces para describir las capacidades de un objeto.

Un gato es un mamífero, pero una de sus capacidades es que es comestible.

O, para decirlo de otra manera, las clases son sustantivos, mientras que las interfaces se aproximan a los adjetivos.

De MSDN, Recomendaciones para Clases abstractas vs. Interfaces

  • Si prevé crear varias versiones de su componente, cree una clase abstracta. Las clases abstractas proporcionan una manera simple y fácil de versionar sus componentes. Al actualizar la clase base, todas las clases heredadas se actualizan automáticamente con el cambio. Las interfaces, por otro lado, no se pueden cambiar una vez creadas. Si se requiere una nueva versión de una interfaz, debe crear una interfaz completamente nueva.

  • Si la funcionalidad que está creando será útil en una amplia gama de objetos dispares, use una interfaz. Las clases abstractas deberían usarse principalmente para objetos que están estrechamente relacionados, mientras que las interfaces son las más adecuadas para proporcionar funcionalidad común a clases no relacionadas.

  • Si está diseñando partes pequeñas y concisas de funcionalidad, use interfaces. Si está diseñando unidades funcionales grandes, use una clase abstracta.

  • Si desea proporcionar funcionalidad común implementada entre todas las implementaciones de su componente, use una clase abstracta. Las clases abstractas le permiten implementar parcialmente su clase, mientras que las interfaces no contienen implementación para ningún miembro.

Si desea proporcionar la opción de reemplazar completamente su implementación, use una interfaz. Esto se aplica especialmente para interacciones entre componentes principales, estos siempre deben estar desacoplados por las interfaces.

También puede haber razones técnicas para preferir una interfaz, por ejemplo, para permitir la burla en las pruebas unitarias.

Internamente en un componente, puede estar bien usar una clase abstracta directamente para acceder a una jerarquía de clases.

Si utiliza una interfaz y tiene una jerarquía de clases de implementación, es una buena práctica tener una clase abstracta que contenga las partes comunes de la implementación. P.ej

 interface Foo abstract class FooBase implements Foo class FunnyFoo extends FooBase class SeriousFoo extends FooBase 

También podría tener más clases abstractas heredadas entre sí para una jerarquía más complicada.

Siempre uso estas pautas:

  • Use interfaces para múltiples TIPOS de herencia (ya que .NET / Java no usa herencia múltiple)
  • Usar clases abstractas para una implementación reutilizable de un tipo

La regla de la preocupación dominante dicta que una clase siempre tiene una preocupación principal y 0 o más (consulte http://citeseer.ist.psu.edu/tarr99degrees.html ). Esos 0 o más otros luego los implementa a través de interfaces, ya que la clase implementa todos los tipos que tiene que implementar (la suya propia y todas las interfaces que implementa).

En un mundo de herencia de implementación múltiple (por ejemplo, C ++ / Eiffel), uno heredaría de las clases que implementan las interfaces. (En teoría, en la práctica podría no funcionar tan bien).

También hay algo que se llama el principio SECO – No repetir.

En su ejemplo de fonts de datos, usted dice que hay algún código genérico que es común entre diferentes implementaciones. Para mí, parece que la mejor manera de manejar eso sería tener una clase abstracta con el código genérico y algunas clases concretas que lo amplíen.

La ventaja es que cada corrección de errores en el código genérico beneficia a todas las implementaciones concretas.

Si solo va a la interfaz, tendrá que mantener varias copias del mismo código que está buscando problemas.

En cuanto a la interfaz abstracta + si no hay una justificación inmediata para ello, no lo haría. Extraer la interfaz de la clase abstracta es una fácil refactorización, por lo que lo haría solo cuando realmente se necesita.

Consulte a continuación la pregunta SE para las pautas genéricas:

Interfaz vs clase abstracta (OO general)

Caso de uso práctico para la interfaz:

  1. Implementación de Strategy_pattern : defina su estrategia como una interfaz. Cambie la implementación dinámicamente con una implementación concreta de la estrategia en tiempo de ejecución.

  2. Definir una capacidad entre múltiples clases no relacionadas.

Caso de uso práctico para la clase abstracta:

  1. Implementación de Template_method_pattern : Define un esqueleto de un algoritmo. Las clases secundarias no pueden cambiar la estructura del algortihm pero pueden redefinir una parte de la implementación en clases secundarias.

  2. Cuando desee compartir variables no estáticas y no finales entre múltiples clases relacionadas con la relación ” tiene una “.

Uso de la clase abstradt y la interfaz:

Si va a una clase abstracta, puede mover los métodos abstractos a la interfaz y la clase abstracta puede simplemente implementar esa interfaz. Todos los casos de uso de clases abstractas pueden caer en esta categoría.