Interfaz vs clase base


767

¿Cuándo debo usar una interfaz y cuándo debo usar una clase base?

¿Debería ser siempre una interfaz si no quiero definir realmente una implementación base de los métodos?

Si tengo una clase de perros y gatos. ¿Por qué querría implementar IPet en lugar de PetBase? Puedo entender tener interfaces para ISheds o IBarks (IMakesNoise?), Porque se pueden colocar en una mascota por mascota, pero no entiendo cuál usar para una mascota genérica.


11
Solo un punto que creo que debe tener en cuenta: las interfaces pueden presentar varios límites que puede no conocer hasta etapas muy tardías. Por ejemplo, con .NET no puede serializar una variable de miembro de interfaz, por lo que si tiene una clase Zoo y una matriz de variables miembro de IAnimals no podrá serializar Zoo (y eso significa escribir WebServices u otras cosas que requieren una serialización sería un dolor).
synhershko

1
Esta pregunta podría ayudar a comprender el concepto de interfaces. stackoverflow.com/q/8531292/1055241
gprathour

Tengo curiosidad. Me encontré en el CLR a través de C # el siguiente fragmento: I tend to prefer using the interface technique over the base type technique because the base type technique doesn’t allow the developer to choose the base type that works best in a particular situation.. No puedo entender lo que se entiende en el extracto. Podemos crear algunos tipos base y crear un tipo derivado para cualquiera de ellos, de modo que un desarrollador pueda elegir un tipo base. ¿Podría alguien explicar, por favor, qué me estoy perdiendo? Creo que puede ser parte de esta pregunta. ¿O debería publicar otro sobre el extracto específico?
qqqqqqq

Respuestas:


501

Tomemos su ejemplo de una clase de perro y gato, e ilustremos usando C #:

Tanto un perro como un gato son animales, específicamente, mamíferos cuadrúpedos (los animales son muuuuy demasiado generales). Supongamos que tiene una clase abstracta Mamífero, para ambos:

public abstract class Mammal

Esta clase base probablemente tendrá métodos predeterminados como:

  • Alimentar
  • Compañero

Todos los cuales son comportamientos que tienen más o menos la misma implementación entre ambas especies. Para definir esto tendrás:

public class Dog : Mammal
public class Cat : Mammal

Ahora supongamos que hay otros mamíferos, que generalmente veremos en un zoológico:

public class Giraffe : Mammal
public class Rhinoceros : Mammal
public class Hippopotamus : Mammal

Esto seguirá siendo válido porque en el núcleo de la funcionalidad Feed()y Mate()seguirá siendo el mismo.

Sin embargo, las jirafas, los rinocerontes y los hipopótamos no son exactamente animales de los que puedes hacer mascotas. Ahí es donde una interfaz será útil:

public interface IPettable
{
    IList<Trick> Tricks{get; set;}
    void Bathe();
    void Train(Trick t);
}

La implementación del contrato anterior no será la misma entre un gato y un perro; poner sus implementaciones en una clase abstracta para heredar será una mala idea.

Sus definiciones de perro y gato ahora deberían verse así:

public class Dog : Mammal, IPettable
public class Cat : Mammal, IPettable

Teóricamente, puede anularlos desde una clase base más alta, pero esencialmente una interfaz le permite agregar solo las cosas que necesita en una clase sin necesidad de herencia.

En consecuencia, debido a que generalmente solo puede heredar de una clase abstracta (en la mayoría de los lenguajes OO estáticamente tipados, es decir, las excepciones incluyen C ++) pero puede implementar múltiples interfaces, le permite construir objetos estrictamente según sea necesario .


149
No creo que sea tan simple. Ha cambiado ligeramente la pregunta (requisitos) para que la interfaz tenga más sentido. Siempre debe preguntarse si está definiendo un contrato (interfaz) o una implementación compartida (clase base).
David Pokluda

18
La interfaz es un contrato. Solo está exponiendo la parte del contrato que requiere un servicio. Si tienes un 'PettingZoo', ¡ciertamente no quieres exponer 'Mate'-ing al usuario!
Anthony Mastrean

10
@David Touche, aunque lo hice para ilustrar mejor para qué sirve una interfaz y para qué es una clase abstracta en relación con su comprensión. ¡Un perro y un gato no parecen ser un requisito rígido!
Jon Limjap

2
Cabe señalar que en entornos interpretados, JIT (particularmente JVM), las llamadas a métodos virtuales son significativamente más rápidas que las llamadas a métodos de interfaz , dependiendo del contexto. Enfatizo el "contexto" porque la JVM a menudo puede optimizar las búsquedas de métodos lentos. (por ejemplo, si una lista de herederos de interfaz tiende a ser instancias de la misma clase. Esto lamentablemente hace que la evaluación comparativa sea un poco difícil). Si está tratando de optimizar algo sensible al rendimiento, y solo entonces, debe considerar esto.
Philip Guin

14
Además, una interfaz permite una roca mascota, lo que le permite bañarse y aprender trucos, pero la alimentación y el apareamiento no serían compatibles porque eso sería ridículo para una roca.
eclux

146

Bueno, Josh Bloch se dijo en Effective Java 2d :

Prefiere interfaces sobre clases abstractas

Algunos puntos principales:

  • Las clases existentes se pueden adaptar fácilmente para implementar una nueva interfaz . Todo lo que tiene que hacer es agregar los métodos requeridos si aún no existen y agregar una cláusula de implementos a la declaración de clase.

  • Las interfaces son ideales para definir mixins . Hablando en términos generales, un mixin es un tipo que una clase puede implementar además de su "tipo primario" para declarar que proporciona un comportamiento opcional. Por ejemplo, Comparable es una interfaz mixin que permite que una clase declare que sus instancias están ordenadas con respecto a otros objetos mutuamente comparables.

  • Las interfaces permiten la construcción de marcos de tipo no jerárquico . Las jerarquías de tipos son excelentes para organizar algunas cosas, pero otras no caen perfectamente en una jerarquía rígida.

  • Las interfaces permiten mejoras de funcionalidad seguras y potentes a través del idioma de clase envolvente. Si usa clases abstractas para definir tipos, deja al programador que desea agregar funcionalidad sin otra alternativa que usar la herencia.

Además, puede combinar las virtudes de las interfaces y las clases abstractas al proporcionar una clase de implementación esquelética abstracta que vaya con cada interfaz no trivial que exporte.

Por otro lado, las interfaces son muy difíciles de evolucionar. Si agrega un método a una interfaz, romperá todas sus implementaciones.

PD .: Compra el libro. Es mucho más detallado.


71
Cada vez que se necesita hacer un cambio en una interfaz, la forma más innovadora de hacerlo es crear una nueva interfaz que herede de la anterior. Esto preserva las implementaciones existentes y le permite hacer lo que desee en la nueva.
Scott Lawrence

55
tenemos el "Principio de segregación de interfaz". Este principio nos enseña a tener cuidado de cómo escribimos nuestras interfaces. Cuando escribimos nuestras interfaces, debemos tener cuidado de agregar solo los métodos que deberían estar allí. Si agregamos métodos que no deberían estar allí, las clases que implementan la interfaz también deberán implementar esos métodos. Por ejemplo, si creamos una interfaz llamada Trabajador y agregamos un método para el almuerzo, todos los trabajadores tendrán que implementarlo. ¿Qué pasa si el trabajador es un robot? Como conclusión, las interfaces que contienen métodos que no son específicos para él se denominan interfaces contaminadas o gruesas.
Eklavyaa

Desde Java 8, los métodos predeterminados le permiten agregar nuevas funcionalidades a las interfaces y garantizar la compatibilidad con versiones anteriores para las clases existentes que implementan esa interfaz. Los métodos predeterminados se invocan de manera predeterminada, si no se anulan en la clase de implementación. Toda clase de implementación puede anular los métodos predeterminados o pueden llamarlos directamente usando instance.defaultMethod ()
Arpit Rastogi

118

Las interfaces y las clases base representan dos formas diferentes de relaciones.

La herencia (clases base) representa una relación "es-a". Por ejemplo, un perro o un gato "es una mascota". Esta relación siempre representa el propósito (único) de la clase (junto con el "principio de responsabilidad única" ).

Las interfaces , por otro lado, representan características adicionales de una clase. Yo lo llamaría una relación "es", como en " Fooes desechable", de ahí la IDisposableinterfaz en C #.


10
De todas las respuestas, esta ofrece la mejor mezcla de brevedad sin pérdida de claridad
Matt Wilko

Alguien me dijo una vez que usara una interfaz cuando hay una relación "tiene-a". Sin embargo, no estoy seguro de si eso siempre es cierto; Mi computadora portátil tiene una pantalla, entonces, ¿debería la computadora portátil implementar IScreen o tener una propiedad Screen? Esto último me parece más natural.
Berend

1
@berend divertido que escribes eso porque las pantallas se implementan a través de interfaces - VGA, HDMI, etc.
Konstantin

Solo porque es POO, tenemos que estirar nuestra imaginación para seguir los escenarios de la vida real. No siempre se aplica.
Crismograma

110

El estilo moderno es definir IPet y PetBase.

La ventaja de la interfaz es que otro código puede usarlo sin ningún tipo de vínculo con otro código ejecutable. Completamente "limpio". También las interfaces se pueden mezclar.

Pero las clases base son útiles para implementaciones simples y utilidades comunes. Proporcione también una clase base abstracta para ahorrar tiempo y código.


¡Ten tu pastel y cómelo también!
Darren Kopp

3
Una interfaz define cómo otras clases pueden usar su código. Una clase base ayuda a los implementadores a implementar su interfaz. Dos cosas diferentes para dos propósitos diferentes.
Bill Michell el

27
No hay nada "moderno" aquí. La clase base y la interfaz con la misma API son simplemente redundantes. Puede utilizar este enfoque en algunos casos, ¡pero no debe generalizar!
smentek

3
El comentario más apoyado a la segunda respuesta apoyada en realidad no está de acuerdo con la respuesta en sí, interesante. Debo decir que veo muchos ejemplos de interfaz y clase base coexistir. En ese sentido, es la forma "moderna". Por ejemplo, en el patrón MVVM, en realidad hay una clase ViewModelBase que implementa INotifyPropertyChanged. Pero cuando mi colega me preguntó por qué tenía la clase base, en lugar de implementar la interfaz en cada modelo de vista, no sé cómo convencerlo
Tete

1
Correcto. No se trata de uno u otro. Existen para abordar 2 problemas muy diferentes. Las interfaces son contratos que una clase implementadora debe abordar. Han encontrado recientemente favor (a veces fanáticamente) para los aspectos de IoC y TDD. Las clases abstractas / base sirven para agrupar propiedades y lógica jerárquicamente comunes. Reduce el código duplicado, lo que a su vez aumenta el mantenimiento de las soluciones y lo hace menos propenso a errores.
Venir

63

Interfaces

  • Define el contrato entre 2 módulos. No se puede tener ninguna implementación.
  • La mayoría de los idiomas le permiten implementar múltiples interfaces
  • Modificar una interfaz es un cambio radical. Todas las implementaciones deben ser recompiladas / modificadas.
  • Todos los miembros son públicos. Las implementaciones tienen que implementar a todos los miembros.
  • Las interfaces ayudan en el desacoplamiento. Puede usar marcos simulados para simular cualquier cosa detrás de una interfaz
  • Las interfaces normalmente indican un tipo de comportamiento
  • Las implementaciones de interfaz están desacopladas / aisladas unas de otras

Clases base

  • Le permite agregar alguna implementación predeterminada que obtiene de forma gratuita por derivación
  • Excepto C ++, solo puede derivar de una clase. Incluso si pudiera desde varias clases, generalmente es una mala idea.
  • Cambiar la clase base es relativamente fácil. Las derivaciones no necesitan hacer nada especial
  • Las clases base pueden declarar funciones públicas y protegidas a las que se puede acceder mediante derivaciones
  • Las clases base abstractas no se pueden burlar fácilmente como las interfaces
  • Las clases base normalmente indican jerarquía de tipos (IS A)
  • Las derivaciones de clase pueden depender de un comportamiento básico (tener un conocimiento complejo de la implementación de los padres). Las cosas pueden ser complicadas si haces un cambio en la implementación base para un chico y rompes a los demás.

Nota: las pautas de diseño del marco recomiendan el uso de clases base (a diferencia de las interfaces) porque su versión es mejor. Agregar un nuevo método a una clase base abstracta en vNext es un cambio sin interrupciones ..
Gishu

59

En general, debe favorecer las interfaces sobre las clases abstractas. Una razón para usar una clase abstracta es si tiene una implementación común entre clases concretas. Por supuesto, aún debe declarar una interfaz (IPet) y hacer que una clase abstracta (PetBase) implemente esa interfaz. Usando interfaces pequeñas y distintas, puede usar múltiples para mejorar aún más la flexibilidad. Las interfaces permiten la máxima flexibilidad y portabilidad de los tipos a través de los límites. Al pasar referencias a través de límites, siempre pase la interfaz y no el tipo concreto. Esto permite que el extremo receptor determine la implementación concreta y proporciona la máxima flexibilidad. Esto es absolutamente cierto cuando se programa de forma TDD / BDD.

The Gang of Four declaró en su libro "Debido a que la herencia expone una subclase a los detalles de la implementación de sus padres, a menudo se dice que la herencia rompe la encapsulación". Yo creo que esto es cierto.


Yeesh Personalmente, creo que esto es un revés. Las interfaces deben tener la funcionalidad mínima para un tipo, y las clases base deben proporcionar un marco rico sobre el cual se pueda construir la personalización. Póngalo en una interfaz y dificultará su implementación.

Nadie dijo que sus interfaces deben ser enormes. Pequeñas, múltiples interfaces y clases base más ricas hacen una gran API.
Kilhoffer

3
¿Soy solo yo, o la mayoría de las clases de "trabajadores comunes" comparten una implementación común? En ese contexto, va en contra de su regla general de favorecer las interfaces. Replantearía su generalización en 2 generalizaciones: aquellas clases comunes que contienen poca o ninguna lógica deberían implementar una interfaz. Esas clases comunes que contienen una cantidad de lógica "decente" deben derivarse de una clase base (ya que lo más probable es que compartan funcionalidad).
goku_da_master

@Kilhoffer "Las interfaces permiten la máxima flexibilidad y portabilidad de los tipos a través de los límites", por favor elabore esta declaración.
JAVA

49

Esto es bastante específico de .NET, pero el libro de Pautas de diseño de marcos argumenta que, en general, las clases dan más flexibilidad en un marco en evolución. Una vez que se envía una interfaz, no tiene la oportunidad de cambiarla sin romper el código que usó esa interfaz. Sin embargo, con una clase, puede modificarla y no romper el código que la vincula. Mientras realice las modificaciones correctas, lo que incluye agregar nuevas funcionalidades, podrá ampliar y desarrollar su código.

Krzysztof Cwalina dice en la página 81:

En el transcurso de las tres versiones de .NET Framework, he hablado sobre esta guía con bastantes desarrolladores de nuestro equipo. Muchos de ellos, incluidos aquellos que inicialmente no estaban de acuerdo con las pautas, han dicho que lamentan haber enviado alguna API como interfaz. Ni siquiera he oído hablar de un caso en el que alguien haya lamentado haber enviado una clase.

Dicho esto, ciertamente hay un lugar para las interfaces. Como pauta general, siempre proporcione una implementación de clase base abstracta de una interfaz, si no es para nada más, como un ejemplo de una forma de implementar la interfaz. En el mejor de los casos, esa clase base ahorrará mucho trabajo.


19

Juan

Me gusta pensar en las interfaces como una forma de caracterizar una clase. Una clase de raza de perro en particular, digamos un YorkshireTerrier, puede ser descendiente de la clase de perro padre, pero también implementa IFurry, IStubby e IYippieDog. Entonces, la clase define qué es la clase, pero la interfaz nos dice cosas al respecto.

La ventaja de esto es que me permite, por ejemplo, reunir todos los IYippieDog y lanzarlos a mi colección Ocean. Así que ahora puedo alcanzar un conjunto particular de objetos y encontrar unos que cumplan con los criterios que estoy viendo sin inspeccionar la clase demasiado de cerca.

Encuentro que las interfaces realmente deberían definir un subconjunto del comportamiento público de una clase. Si define todo el comportamiento público para todas las clases que implementan, generalmente no es necesario que exista. No me dicen nada útil.

Sin embargo, este pensamiento va en contra de la idea de que cada clase debe tener una interfaz y que debe codificar la interfaz. Eso está bien, pero terminas con muchas interfaces uno a uno para las clases y eso hace que las cosas sean confusas. Entiendo que la idea es que realmente no cuesta nada hacer y ahora puede cambiar las cosas con facilidad. Sin embargo, encuentro que rara vez hago eso. La mayoría de las veces solo estoy modificando la clase existente en el lugar y tengo exactamente los mismos problemas que siempre tuve si la interfaz pública de esa clase necesita un cambio, excepto que ahora tengo que cambiarla en dos lugares.

Entonces, si piensas como yo, definitivamente dirías que Cat and Dog son IPettable. Es una caracterización que los une a ambos.

Sin embargo, la otra parte de esto es si deberían tener la misma clase base. La pregunta es si necesitan ser tratados ampliamente como la misma cosa. Ciertamente, ambos son animales, pero eso encaja en cómo los vamos a usar juntos.

Digamos que quiero reunir todas las clases de animales y ponerlas en mi contenedor Ark.

¿O necesitan ser mamíferos? ¿Quizás necesitamos algún tipo de fábrica de ordeño de animales cruzados?

¿Necesitan siquiera estar unidos entre sí? ¿Es suficiente saber que ambos son IPettable?

A menudo siento el deseo de derivar una jerarquía de clases completa cuando realmente solo necesito una clase. Lo hago con anticipación, algún día podría necesitarlo y generalmente nunca lo hago. Incluso cuando lo hago, generalmente encuentro que tengo que hacer mucho para arreglarlo. Eso es porque la primera clase que estoy creando no es el Perro, no tengo tanta suerte, es el Ornitorrinco. Ahora toda mi jerarquía de clases se basa en un caso extraño y tengo mucho código desperdiciado.

También puede encontrar en algún momento que no todos los gatos son IPettable (como ese sin pelo). Ahora puede mover esa interfaz a todas las clases derivadas que se ajusten. Encontrará un cambio mucho menos importante que, de repente, los gatos ya no se derivan de PettableBase.


18

Aquí está la definición básica y simple de interfaz y clase base:

  • Clase base = herencia de objetos.
  • Interfaz = herencia funcional.

salud


12

Recomiendo usar composición en lugar de herencia siempre que sea posible. Use interfaces pero use objetos miembros para la implementación base. De esa manera, puede definir una fábrica que construya sus objetos para comportarse de cierta manera. Si desea cambiar el comportamiento, cree un nuevo método de fábrica (o fábrica abstracta) que cree diferentes tipos de subobjetos.

En algunos casos, puede encontrar que sus objetos primarios no necesitan interfaces en absoluto, si todo el comportamiento mutable se define en objetos auxiliares.

Entonces, en lugar de IPet o PetBase, puede terminar con una mascota que tiene un parámetro IFurBehavior. El parámetro IFurBehavior se establece mediante el método CreateDog () de PetFactory. Es este parámetro el que se llama para el método shed ().

Si hace esto, encontrará que su código es mucho más flexible y la mayoría de sus objetos simples tratan comportamientos muy básicos en todo el sistema.

Recomiendo este patrón incluso en lenguajes de herencia múltiple.


12

Se explica bien en este artículo de Java World .

Personalmente, tiendo a usar interfaces para definir interfaces, es decir, partes del diseño del sistema que especifican cómo se debe acceder a algo.

No es raro que tenga una clase implementando una o más interfaces.

Clases abstractas que uso como base para otra cosa.

El siguiente es un extracto del artículo mencionado en JavaWorld.com, autor Tony Sintes, 20/04/01


Interfaz versus clase abstracta

La elección de interfaces y clases abstractas no es una proposición de uno u otro. Si necesita cambiar su diseño, conviértalo en una interfaz. Sin embargo, puede tener clases abstractas que proporcionan un comportamiento predeterminado. Las clases abstractas son excelentes candidatos dentro de los marcos de aplicación.

Las clases abstractas te permiten definir algunos comportamientos; obligan a sus subclases a proporcionar otros. Por ejemplo, si tiene un marco de aplicación, una clase abstracta puede proporcionar servicios predeterminados como el manejo de eventos y mensajes. Esos servicios permiten que su aplicación se conecte a su marco de aplicación. Sin embargo, hay algunas funcionalidades específicas de la aplicación que solo su aplicación puede realizar. Dicha funcionalidad puede incluir tareas de inicio y apagado, que a menudo dependen de la aplicación. Entonces, en lugar de tratar de definir ese comportamiento en sí mismo, la clase base abstracta puede declarar métodos abstractos de apagado y arranque. La clase base sabe que necesita esos métodos, pero una clase abstracta le permite a su clase admitir que no sabe cómo realizar esas acciones; solo sabe que debe iniciar las acciones. Cuando es hora de comenzar, la clase abstracta puede llamar al método de inicio. Cuando la clase base llama a este método, Java llama al método definido por la clase secundaria.

Muchos desarrolladores olvidan que una clase que define un método abstracto también puede llamar a ese método. Las clases abstractas son una excelente manera de crear jerarquías de herencia planificadas. También son una buena opción para las clases que no son hojas en las jerarquías de clases.

Clase contra interfaz

Algunos dicen que debería definir todas las clases en términos de interfaces, pero creo que la recomendación parece un poco extrema. Utilizo interfaces cuando veo que algo en mi diseño cambiará con frecuencia.

Por ejemplo, el patrón Estrategia le permite intercambiar nuevos algoritmos y procesos en su programa sin alterar los objetos que los usan. Un reproductor multimedia puede saber cómo reproducir CD, MP3 y archivos wav. Por supuesto, no desea codificar esos algoritmos de reproducción en el reproductor; eso dificultará agregar un nuevo formato como AVI. Además, su código estará lleno de declaraciones de casos inútiles. Y para agregar insulto a la lesión, deberá actualizar esas declaraciones de casos cada vez que agregue un nuevo algoritmo. En general, esta no es una forma de programación muy orientada a objetos.

Con el patrón de Estrategia, simplemente puede encapsular el algoritmo detrás de un objeto. Si lo hace, puede proporcionar nuevos complementos de medios en cualquier momento. Llamemos a la clase de complemento MediaStrategy. Ese objeto tendría un método: playStream (Stream s). Entonces, para agregar un nuevo algoritmo, simplemente ampliamos nuestra clase de algoritmo. Ahora, cuando el programa encuentra el nuevo tipo de medios, simplemente delega la reproducción de la transmisión a nuestra estrategia de medios. Por supuesto, necesitará algunas tuberías para crear una instancia adecuada de las estrategias de algoritmo que necesitará.

Este es un excelente lugar para usar una interfaz. Hemos utilizado el patrón de Estrategia, que indica claramente un lugar en el diseño que cambiará. Por lo tanto, debe definir la estrategia como una interfaz. En general, debe favorecer las interfaces sobre la herencia cuando desea que un objeto tenga un cierto tipo; en este caso, MediaStrategy. Confiar en la herencia para la identidad de tipo es peligroso; te encierra en una jerarquía de herencia particular. Java no permite la herencia múltiple, por lo que no puede extender algo que le brinde una implementación útil o más identidad de tipo.


2
+1. "Confiar en la herencia para la identidad de tipo es peligroso; te encierra en una jerarquía de herencia particular". Esa oración describe perfectamente mis razones para preferir las interfaces.
Ingeniero

Y más allá de eso, en lugar de extender, componga la implementación detrás de cada método de su interfaz.
Ingeniero

10

También tenga en cuenta que no debe dejarse llevar por el OO ( vea el blog ) y siempre modele objetos según el comportamiento requerido, si estaba diseñando una aplicación donde el único comportamiento que requería era un nombre genérico y una especie para un animal, entonces solo necesitaría Una clase de animal con una propiedad para el nombre, en lugar de millones de clases para cada animal posible en el mundo.


10

Tengo una regla general aproximada

Funcionalidad: es probable que sea diferente en todas las partes: interfaz.

Los datos y la funcionalidad, las partes serán en su mayoría iguales, partes diferentes: clase abstracta.

Los datos y la funcionalidad realmente funcionan, si se extienden solo con ligeros cambios: clase ordinaria (concreta)

Datos y funcionalidad, sin cambios planeados: clase ordinaria (concreta) con modificador final.

Datos, y tal vez funcionalidad: solo lectura: miembros de enumeración.

Esto es muy aproximado y está listo y no está estrictamente definido, pero hay un espectro de interfaces donde todo está destinado a ser cambiado a enumeraciones donde todo está arreglado un poco como un archivo de solo lectura.


7

Las interfaces deben ser pequeñas. Realmente pequeño. Si realmente está desglosando sus objetos, entonces sus interfaces probablemente solo contendrán algunos métodos y propiedades muy específicos.

Las clases abstractas son atajos. ¿Hay cosas que comparten todos los derivados de PetBase que pueda codificar una vez y terminar? Si es así, entonces es hora de una clase abstracta.

Las clases abstractas también son limitantes. Si bien le brindan un excelente acceso directo para producir objetos secundarios, cualquier objeto dado solo puede implementar una clase abstracta. Muchas veces, considero que esto es una limitación de las clases abstractas, y es por eso que uso muchas interfaces.

Las clases abstractas pueden contener varias interfaces. Su clase abstracta PetBase puede implementar IPet (las mascotas tienen dueños) e IDigestion (las mascotas comen, o al menos deberían). Sin embargo, PetBase probablemente no implementará IMammal, ya que no todas las mascotas son mamíferos y no todos los mamíferos son mascotas. Puede agregar un MammalPetBase que extiende PetBase y agregar IMammal. FishBase podría tener PetBase y agregar IFish. IFish tendría ISwim e IUnderwaterBreather como interfaces.

Sí, mi ejemplo es demasiado complicado para el ejemplo simple, pero eso es parte de la gran cosa acerca de cómo las interfaces y las clases abstractas funcionan juntas.


7

Fuente : http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/

C # es un lenguaje maravilloso que ha madurado y evolucionado en los últimos 14 años. Esto es genial para nosotros los desarrolladores porque un lenguaje maduro nos proporciona una gran cantidad de características de lenguaje que están a nuestra disposición.

Sin embargo, con mucho poder se convierte en mucha responsabilidad. Algunas de estas funciones pueden ser mal utilizadas, o algunas veces es difícil entender por qué elegiría usar una función sobre otra. Con los años, una característica con la que he visto luchar a muchos desarrolladores es cuándo elegir usar una interfaz o elegir usar una clase abstracta. Ambos tienen sus ventajas y desventajas y el momento y lugar correctos para usar cada uno. ¿Pero cómo decidimos?

Ambos prevén la reutilización de funcionalidades comunes entre tipos. La diferencia más obvia de inmediato es que las interfaces no proporcionan implementación para su funcionalidad, mientras que las clases abstractas le permiten implementar algún comportamiento "base" o "predeterminado" y luego tienen la capacidad de "anular" este comportamiento predeterminado con los tipos derivados de clases si es necesario .

Todo esto está muy bien y proporciona una gran reutilización del código y se adhiere al principio DRY (No repetir) del desarrollo de software. Las clases abstractas son excelentes para usar cuando tienes una relación "es un".

Por ejemplo: un golden retriever "es un" tipo de perro. Entonces es un caniche. Ambos pueden ladrar, como todos los perros. Sin embargo, es posible que desee indicar que el parque de caniches es significativamente diferente de la corteza del perro "por defecto". Por lo tanto, podría tener sentido implementar algo de la siguiente manera:

public abstract class Dog
{
      public virtual void Bark()
      {
        Console.WriteLine("Base Class implementation of Bark");
      }
}

public class GoldenRetriever : Dog
{
   // the Bark method is inherited from the Dog class
}

public class Poodle : Dog
{
  // here we are overriding the base functionality of Bark with our new implementation
  // specific to the Poodle class
  public override void Bark()
  {
     Console.WriteLine("Poodle's implementation of Bark");
  }
}

// Add a list of dogs to a collection and call the bark method.

void Main()
{
    var poodle = new Poodle();
    var goldenRetriever = new GoldenRetriever();

    var dogs = new List<Dog>();
    dogs.Add(poodle);
    dogs.Add(goldenRetriever);

    foreach (var dog in dogs)
    {
       dog.Bark();
    }
}

// Output will be:
// Poodle's implementation of Bark
// Base Class implementation of Bark

// 

Como puede ver, esta sería una excelente manera de mantener su código SECO y permitir que se llame a la implementación de la clase base cuando cualquiera de los tipos puede confiar en el Ladrido predeterminado en lugar de una implementación de caso especial. Las clases como GoldenRetriever, Boxer, Lab podrían heredar la corteza "predeterminada" (clase de bajo) sin cargo solo porque implementan la clase abstracta Dog.

Pero estoy seguro de que ya lo sabías.

Estás aquí porque quieres entender por qué quieres elegir una interfaz en lugar de una clase abstracta o viceversa. Bueno, una razón por la que es posible que desee elegir una interfaz en lugar de una clase abstracta es cuando no tiene o desea evitar una implementación predeterminada. Esto generalmente se debe a que los tipos que implementan la interfaz no están relacionados en una relación "es un". En realidad, no tienen que estar relacionados, excepto por el hecho de que cada tipo "es capaz" o tiene "la habilidad" de hacer algo o tener algo.

¿Qué diablos significa eso? Bueno, por ejemplo: un humano no es un pato ... y un pato no es un humano. Bastante obvio. Sin embargo, tanto un pato como un humano tienen "la habilidad" de nadar (dado que el humano pasó sus lecciones de natación en 1er grado :)). Además, dado que un pato no es humano o viceversa, esta no es una relación "es una", sino una relación "es capaz" y podemos usar una interfaz para ilustrar que:

// Create ISwimable interface
public interface ISwimable
{
      public void Swim();
}

// Have Human implement ISwimable Interface
public class Human : ISwimable

     public void Swim()
     {
        //Human's implementation of Swim
        Console.WriteLine("I'm a human swimming!");
     }

// Have Duck implement ISwimable interface
public class Duck: ISwimable
{
     public void Swim()
     {
          // Duck's implementation of Swim
          Console.WriteLine("Quack! Quack! I'm a Duck swimming!")
     }
}

//Now they can both be used in places where you just need an object that has the ability "to swim"

public void ShowHowYouSwim(ISwimable somethingThatCanSwim)
{
     somethingThatCanSwim.Swim();
}

public void Main()
{
      var human = new Human();
      var duck = new Duck();

      var listOfThingsThatCanSwim = new List<ISwimable>();

      listOfThingsThatCanSwim.Add(duck);
      listOfThingsThatCanSwim.Add(human);

      foreach (var something in listOfThingsThatCanSwim)
      {
           ShowHowYouSwim(something);
      }
}

 // So at runtime the correct implementation of something.Swim() will be called
 // Output:
 // Quack! Quack! I'm a Duck swimming!
 // I'm a human swimming!

El uso de interfaces como el código anterior le permitirá pasar un objeto a un método que "pueda" hacer algo. Al código no le importa cómo lo hace ... Todo lo que sabe es que puede llamar al método Swim en ese objeto y ese objeto sabrá qué comportamiento tomar en tiempo de ejecución en función de su tipo.

Una vez más, esto ayuda a que su código permanezca SECO para que no tenga que escribir varios métodos que están llamando al objeto para preformar la misma función central (ShowHowHumanSwims (humano), ShowHowDuckSwims (pato), etc.)

El uso de una interfaz aquí permite que los métodos de llamada no tengan que preocuparse sobre qué tipo es cuál o cómo se implementa el comportamiento. Simplemente sabe que dada la interfaz, cada objeto tendrá que haber implementado el método Swim para que sea seguro llamarlo en su propio código y permitir que el comportamiento del método Swim se maneje dentro de su propia clase.

Resumen:

Por lo tanto, mi regla general es usar una clase abstracta cuando desee implementar una funcionalidad "predeterminada" para una jerarquía de clases y / o las clases o tipos con los que está trabajando comparten una relación "es una" (por ejemplo, poodle "es un "Tipo de perro).

Por otro lado, use una interfaz cuando no tenga una relación "es un" pero tenga tipos que compartan "la capacidad" de hacer o tener algo (por ejemplo, Duck "no es" un humano. Sin embargo, el pato y el humano comparten "La capacidad" de nadar).

Otra diferencia a tener en cuenta entre las clases abstractas y las interfaces es que una clase puede implementar una o varias interfaces, pero una clase solo puede heredar de UNA clase abstracta (o de cualquier clase). Sí, puede anidar clases y tener una jerarquía de herencia (que muchos programas hacen y deberían tener) pero no puede heredar dos clases en una definición de clase derivada (esta regla se aplica a C #. En algunos otros idiomas puede hacerlo, generalmente solo por la falta de interfaces en estos idiomas).

Recuerde también cuando utilice interfaces para cumplir con el Principio de segregación de interfaz (ISP). El ISP afirma que ningún cliente debería verse obligado a depender de métodos que no utiliza. Por esta razón, las interfaces deben centrarse en tareas específicas y, por lo general, son muy pequeñas (por ejemplo, IDisposable, IComparable).

Otro consejo es que si está desarrollando funcionalidades pequeñas y concisas, use interfaces. Si está diseñando unidades funcionales grandes, use una clase abstracta.

¡Espero que esto aclare las cosas para algunas personas!

Además, si puede pensar en mejores ejemplos o desea señalar algo, ¡hágalo en los comentarios a continuación!


6

El caso de las clases base sobre las interfaces se explicó bien en las Pautas de codificación de .NET principales:

Clases base frente a interfaces Un tipo de interfaz es una descripción parcial de un valor, potencialmente compatible con muchos tipos de objetos. Use clases base en lugar de interfaces siempre que sea posible. Desde una perspectiva de versiones, las clases son más flexibles que las interfaces. Con una clase, puede enviar la Versión 1.0 y luego, en la Versión 2.0, agregar un nuevo método a la clase. Mientras el método no sea abstracto, las clases derivadas existentes seguirán funcionando sin cambios.

Como las interfaces no admiten la herencia de implementación, el patrón que se aplica a las clases no se aplica a las interfaces. Agregar un método a una interfaz es equivalente a agregar un método abstracto a una clase base; cualquier clase que implemente la interfaz se interrumpirá porque la clase no implementa el nuevo método. Las interfaces son apropiadas en las siguientes situaciones:

  1. Varias clases no relacionadas quieren soportar el protocolo.
  2. Estas clases ya tienen clases base establecidas (por ejemplo, algunas son controles de interfaz de usuario (UI) y otras son servicios web XML).
  3. La agregación no es apropiada o practicable. En todas las demás situaciones, la herencia de clase es un mejor modelo.

Siento que esta respuesta debería llamar más la atención. Va contra la corriente de muchas respuestas, aquí. No diría que estoy completamente de acuerdo, pero hay grandes puntos aquí.
kayleeFrye_onDeck

5

Una diferencia importante es que solo puede heredar una clase base, pero puede implementar muchas interfaces. Por lo tanto, solo desea usar una clase base si está absolutamente seguro de que no necesitará heredar también una clase base diferente. Además, si encuentra que su interfaz se está agrandando, debe comenzar a dividirla en algunas piezas lógicas que definan la funcionalidad independiente, ya que no hay una regla que indique que su clase no puede implementarlas todas (o que puede definir una diferente interfaz que simplemente los hereda a todos para agruparlos).


4

Cuando comencé a aprender sobre programación orientada a objetos, cometí el error fácil y probablemente común de usar la herencia para compartir un comportamiento común, incluso cuando ese comportamiento no era esencial para la naturaleza del objeto.

Para seguir desarrollando un ejemplo muy utilizado en esta pregunta en particular, hay muchas cosas que son petables: novias, automóviles, mantas borrosas ..., por lo que podría haber tenido una clase Petable que proporcionó este comportamiento común, y varias clases heredadas de eso.

Sin embargo, ser petable no es parte de la naturaleza de ninguno de estos objetos. Hay conceptos mucho más importantes que son esenciales para su naturaleza: la novia es una persona, el automóvil es un vehículo terrestre, el gato es un mamífero ...

Los comportamientos deben asignarse primero a las interfaces (incluida la interfaz predeterminada de la clase) y promoverse a una clase base solo si son (a) comunes a un gran grupo de clases que son subconjuntos de una clase más grande, en el mismo sentido que "gato" y "persona" son subconjuntos de "mamífero".

El problema es que, después de comprender el diseño orientado a objetos lo suficientemente mejor que yo al principio, normalmente lo hará automáticamente sin siquiera pensarlo. Por lo tanto, la verdad de la afirmación "código para una interfaz, no una clase abstracta" se vuelve tan obvia que tiene dificultades para creer que alguien se molestaría en decirlo, y comenzar a tratar de leer otros significados en él.

Otra cosa que agregaría es que si una clase es puramente abstracta, sin miembros o métodos no abstractos y no heredados expuestos a hijos, padres o clientes, ¿por qué es una clase? Podría ser reemplazado, en algunos casos por una interfaz y en otros casos por Null.


Una clase puramente abstracta puede proporcionar comportamientos predeterminados para los métodos. Esto es útil cuando sus clases concretas compartirán métodos comunes que serían redundantes para volver a implementar una y otra vez.
Adam Hughes

4

Prefiere interfaces sobre clases abstractas

Justificación, los puntos principales a considerar [dos ya mencionados aquí] son:

  • Las interfaces son más flexibles, porque una clase puede implementar múltiples interfaces. Como Java no tiene herencia múltiple, el uso de clases abstractas evita que sus usuarios usen cualquier otra jerarquía de clases. En general, prefiera las interfaces cuando no hay implementaciones o estados predeterminados. Las colecciones de Java ofrecen buenos ejemplos de esto (Mapa, Conjunto, etc.).
  • Las clases abstractas tienen la ventaja de permitir una mejor compatibilidad hacia adelante. Una vez que los clientes usan una interfaz, no puede cambiarla; si usan una clase abstracta, aún puede agregar comportamiento sin romper el código existente. Si la compatibilidad es una preocupación, considere usar clases abstractas.
  • Incluso si tiene implementaciones predeterminadas o estado interno, considere ofrecer una interfaz y una implementación abstracta de la misma . Esto ayudará a los clientes, pero aún les dará mayor libertad si lo desea [1].
    Por supuesto, el tema se ha discutido extensamente en otra parte [2,3].

[1] Agrega más código, por supuesto, pero si la brevedad es su principal preocupación, ¡probablemente debería haber evitado Java en primer lugar!

[2] Joshua Bloch, Java efectivo, ítems 16-18.

[3] http://www.codeproject.com/KB/ar ...


3

Los comentarios anteriores sobre el uso de clases abstractas para una implementación común definitivamente están en la marca. Un beneficio que no he visto mencionado aún es que el uso de interfaces hace que sea mucho más fácil implementar objetos simulados con el propósito de realizar pruebas unitarias. Definir IPet y PetBase como lo describió Jason Cohen le permite burlarse de diferentes condiciones de datos fácilmente, sin la sobrecarga de una base de datos física (hasta que decida que es hora de probar la realidad).


3

No use una clase base a menos que sepa lo que significa y que se aplica en este caso. Si corresponde, úselo; de lo contrario, use interfaces. Pero tenga en cuenta la respuesta sobre pequeñas interfaces.

La herencia pública se usa en exceso en OOD y expresa mucho más de lo que la mayoría de los desarrolladores se dan cuenta o están dispuestos a cumplir. Ver el Principio de sustituibilidad de Liskov

En resumen, si A "es un" B, entonces A no requiere más que B y ofrece no menos de B, por cada método que expone.


3

Otra opción a tener en cuenta es usar la relación "tiene-a", también conocida como "se implementa en términos de" o "composición". A veces, esta es una forma más limpia y flexible de estructurar las cosas que el uso de la herencia "is-a".

Puede que no tenga tanto sentido lógico decir que tanto el perro como el gato "tienen" una mascota, pero evita los errores comunes de herencia múltiple:

public class Pet
{
    void Bathe();
    void Train(Trick t);
}

public class Dog
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

public class Cat
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

Sí, este ejemplo muestra que hay mucha duplicación de código y falta de elegancia para hacer las cosas de esta manera. Pero también se debe apreciar que esto ayuda a mantener al Perro y al Gato desconectados de la clase Mascota (en el sentido de que el Perro y el Gato no tienen acceso a los miembros privados de la Mascota), y deja espacio para que el Perro y el Gato hereden de otra cosa. -posiblemente la clase Mamífero.

La composición es preferible cuando no se requiere acceso privado y no es necesario referirse a Dog and Cat utilizando referencias / punteros genéricos para mascotas. Las interfaces le brindan esa capacidad de referencia genérica y pueden ayudar a reducir la verbosidad de su código, pero también pueden ofuscar cosas cuando están mal organizadas. La herencia es útil cuando necesita acceso privado para miembros, y al usarlo se compromete a acoplar sus clases de perros y gatos a su clase de mascotas, lo cual es un costo elevado.

Entre la herencia, la composición y las interfaces, no hay una sola forma que siempre sea correcta, y ayuda a considerar cómo se pueden utilizar las tres opciones en armonía. De las tres, la herencia suele ser la opción que debe usarse con menos frecuencia.


3

Conceptualmente, una interfaz se utiliza para definir formal y semiformalmente un conjunto de métodos que proporcionará un objeto. Formalmente significa un conjunto de nombres y firmas de métodos, y semi formalmente significa documentación legible por humanos asociada con esos métodos.

Las interfaces son solo descripciones de una API (después de todo, API significa interfaz de programación de aplicaciones ), no pueden contener ninguna implementación y no es posible usar o ejecutar una interfaz. Solo hacen explícito el contrato de cómo debe interactuar con un objeto.

Las clases proporcionan una implementación y pueden declarar que implementan cero, una o más interfaces. Si se pretende que una clase se herede, la convención es prefijar el nombre de la clase con "Base".

Hay una distinción entre una clase base y una clase base abstracta (ABC). ABC combina interfaz e implementación juntas. Resumen fuera de la programación de computadoras significa "resumen", es decir, "abstract == interfaz". Una clase base abstracta puede describir tanto una interfaz como una implementación vacía, parcial o completa que se pretende heredar.

Las opiniones sobre cuándo usar interfaces versus clases base abstractas versus solo clases variarán enormemente en función de lo que está desarrollando y en qué lenguaje se está desarrollando. Las interfaces a menudo se asocian solo con lenguajes estáticamente tipados como Java o C #, pero Los lenguajes de tipo dinámico también pueden tener interfaces y clases base abstractas . En Python, por ejemplo, la distinción se hace clara entre una Clase, que declara que implementa una interfaz , y un objeto, que es una instancia de una clase , y se dice que proporciona esa interfaz. En un lenguaje dinámico, es posible que dos objetos que son instancias de la misma clase puedan declarar que proporcionan interfaces completamente diferentes . En Python esto solo es posible para atributos de objeto, mientras que los métodos son estado compartido entre todos los objetos de una clase . Sin embargo, en Ruby, los objetos pueden tener métodos por instancia, por lo que es posible que la interfaz entre dos objetos de la misma clase pueda variar tanto como lo desee el programador (sin embargo, Ruby no tiene ninguna forma explícita de declarar Interfaces).

En los lenguajes dinámicos, la interfaz con un objeto a menudo se asume implícitamente, ya sea introspectando un objeto y preguntándole qué métodos proporciona ( mire antes de saltar ) o preferiblemente simplemente intentando usar la interfaz deseada en un objeto y capturando excepciones si el objeto no proporciona esa interfaz (es más fácil pedir perdón que permiso ). Esto puede conducir a "falsos positivos" donde dos interfaces tienen el mismo nombre de método, pero son semánticamente diferentes. Sin embargo, la compensación es que su código es más flexible, ya que no necesita especificar de antemano para anticipar todos los usos posibles de su código.


2

Depende de tus requerimientos. Si IPet es lo suficientemente simple, preferiría implementar eso. De lo contrario, si PetBase implementa una tonelada de funcionalidad que no desea duplicar, entonces hágalo.

La desventaja de implementar una clase base es el requisito de override(o new) los métodos existentes. Esto los convierte en métodos virtuales, lo que significa que debe tener cuidado con la forma en que usa la instancia del objeto.

Por último, la herencia única de .NET me mata. Un ejemplo ingenuo: supongamos que está haciendo un control de usuario, por lo que hereda UserControl. Pero, ahora estás bloqueado de heredar también PetBase. Esto te obliga a reorganizar, como hacer un PetBasemiembro de la clase, en su lugar.


2

Por lo general, tampoco lo implemento hasta que lo necesito. Estoy a favor de las interfaces sobre las clases abstractas porque eso da un poco más de flexibilidad. Si hay un comportamiento común en algunas de las clases heredadas, lo muevo hacia arriba y hago una clase base abstracta. No veo la necesidad de ambos, ya que esencialmente sirven para el mismo propósito, y tener ambos es un mal olor de código (en mi humilde opinión) de que la solución ha sido sobredimensionada.


2

Con respecto a C #, en algunos sentidos, las interfaces y las clases abstractas pueden ser intercambiables. Sin embargo, las diferencias son: i) las interfaces no pueden implementar código; ii) debido a esto, las interfaces no pueden llamar más arriba en la pila a la subclase; y iii) solo se puede heredar una clase abstracta en una clase, mientras que se pueden implementar múltiples interfaces en una clase.


2

Por definición, la interfaz proporciona una capa para comunicarse con otro código. Todas las propiedades y métodos públicos de una clase implementan por defecto una interfaz implícita. También podemos definir una interfaz como un rol, cuando alguna clase necesita desempeñar ese rol, debe implementarlo dándole diferentes formas de implementación dependiendo de la clase que lo implemente. Por lo tanto, cuando habla de interfaz, habla de polimorfismo y cuando habla de clase base, habla de herencia. Dos conceptos de ¡Uy!


2

Descubrí que un patrón de Interfaz> Resumen> Concreto funciona en el siguiente caso de uso:

1.  You have a general interface (eg IPet)
2.  You have a implementation that is less general (eg Mammal)
3.  You have many concrete members (eg Cat, Dog, Ape)

La clase abstracta define los atributos compartidos predeterminados de las clases concretas, pero impone la interfaz. Por ejemplo:

public interface IPet{

    public boolean hasHair();

    public boolean walksUprights();

    public boolean hasNipples();
}

Ahora, dado que todos los mamíferos tienen cabello y pezones (AFAIK, no soy zoólogo), podemos incluir esto en la clase base abstracta

public abstract class Mammal() implements IPet{

     @override
     public walksUpright(){
         throw new NotSupportedException("Walks Upright not implemented");
     }

     @override
     public hasNipples(){return true}

     @override
     public hasHair(){return true}

Y luego las clases concretas simplemente definen que caminan erguidos.

public class Ape extends Mammal(){

    @override
    public walksUpright(return true)
}

public class Catextends Mammal(){

    @override
    public walksUpright(return false)
}

Este diseño es agradable cuando hay muchas clases concretas, y no desea mantener la repetitiva solo para programar en una interfaz. Si se agregaran nuevos métodos a la interfaz, se romperían todas las clases resultantes, por lo que aún obtendrá las ventajas del enfoque de la interfaz.

En este caso, el resumen también podría ser concreto; sin embargo, la designación abstracta ayuda a enfatizar que este patrón está siendo empleado.


1

Un heredero de una clase base debe tener una relación "es una". La interfaz representa una relación "implementa una". Así que solo use una clase base cuando sus herederos mantendrán una relación.


1

Use interfaces para hacer cumplir un contrato a través de familias de clases no relacionadas. Por ejemplo, puede tener métodos de acceso comunes para las clases que representan colecciones, pero contienen datos radicalmente diferentes, es decir, una clase puede representar un conjunto de resultados de una consulta, mientras que la otra puede representar las imágenes en una galería. Además, puede implementar múltiples interfaces, lo que le permite combinar (y significar) las capacidades de la clase.

Use la herencia cuando las clases tengan una relación común y, por lo tanto, tengan una firma estructural y de comportamiento similar, es decir, el automóvil, la motocicleta, el camión y el SUV son todos los tipos de vehículos de carretera que pueden contener varias ruedas, una velocidad máxima

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.