Pregunta

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

¿Debería ser siempre una interfaz si no quiero definir 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 mascota por mascota, pero no entiendo cuál usar para una mascota genérica.

¿Fue útil?

Solución

Tomemos su ejemplo de una clase Perro y Gato e ilustremos usando C#:

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

public abstract class Mammal

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

  • Alimentar
  • Compañero

Todos ellos 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 normalmente 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 puedas convertirlos en mascotas.Ahí es donde será útil una interfaz:

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

La ejecució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.

Tus definiciones de Perro y Gato ahora deberían verse así:

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

Teóricamente puedes anularlos desde una clase base superior, pero esencialmente una interfaz te permite agregar solo las cosas que necesitas a una clase sin necesidad de herencia.

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

Otros consejos

Bueno, el mismo Josh Bloch dijo en Java efectivo 2d:

Prefiero interfaces a clases abstractas

Algunos puntos principales:

  • Las clases existentes se pueden modificar fácilmente para implementar una nueva interfaz.Todo lo que tienes que hacer es agregar los métodos requeridos si aún no lo hacen exist y agregue una cláusula implements a la declaración de clase.

  • Las interfaces son ideales para definir mixins.En términos generales, un mixin es un tipo que una clase puede implementar además de su "primario type" para declarar que proporciona algún comportamiento opcional.Por ejemplo Comparable es una interfaz de mezcla que permite a una clase declarar que su Las instancias se ordenan con respecto a otros objetos mutuamente comparables.

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

  • Las interfaces permiten mejoras de funcionalidad potentes y seguras a través de la wrap- por modismo de clase.Si usas clases abstractas para definir tipos, Deje al programador que quiere agregar funcionalidad sin alternativa más que para usar la herencia.

Además, puede combinar las virtudes de las interfaces y las clases abstractas proporcionando una clase de implementación esquelética abstracta para ir 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, se romperán todas sus implementaciones.

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

El estilo moderno es definir IPet y Base de mascotas.

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

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

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

Herencia (clases base) representan una relación "es-a".P.ej.un perro o un gato "es una" mascota.Esta relación siempre representa el (único) objetivo de la clase (junto con el "principio de responsabilidad única").

Interfaces, por otra parte, representan características adicionales de una clase.Yo lo llamaría una relación "es", como en "Foo es desechable", de ahí la IDisposable interfaz en C#.

Interfaces

  • Define el contrato entre 2 módulos.No se puede tener ninguna implementación.
  • La mayoría de los lenguajes le permiten implementar múltiples interfaces.
  • Modificar una interfaz es un cambio radical.Todas las implementaciones deben recompilarse/modificarse.
  • Todos los miembros son públicos.Las implementaciones deben implementarlas todos los miembros.
  • Las interfaces ayudan en el desacoplamiento.Puedes 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 entre sí.

Clases base

  • Le permite agregar algunos por defecto implementación que obtienes gratis por derivación
  • Excepto C++, sólo puedes derivar de una clase.Incluso si pudiera ser de varias clases, suele ser 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 clases pueden llegar a depender de algún comportamiento base (tener un conocimiento complejo de la implementación principal).Las cosas pueden complicarse si realiza un cambio en la implementación básica de un tipo y rompe los demás.

En general, deberías preferir las interfaces a las clases abstractas.Una razón para utilizar una clase abstracta es si tiene una implementación común entre clases concretas.Por supuesto, aún debe declarar una interfaz (IPet) y tener una clase abstracta (PetBase) que implemente esa interfaz. Al utilizar interfaces pequeñas y distintas, puede usar varias para mejorar aún más la flexibilidad.Las interfaces permiten la máxima flexibilidad y portabilidad de tipos a través de fronteras.Al pasar referencias a través de límites, pase siempre 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 en modo TDD/BDD.

La Banda de los Cuatro declaró en su libro: "Debido a que la herencia expone una subclase a detalles de la implementación de su padre, a menudo se dice que 'la herencia rompe la encapsulación'".Yo creo que esto es cierto.

Esto es bastante específico de .NET, pero el libro Framework Design Pautas sostiene que, en general, las clases brindan más flexibilidad en un marco en evolución.Una vez que se envía una interfaz, no tienes la oportunidad de cambiarla sin romper el código que usaba esa interfaz.Sin embargo, con una clase, puedes modificarla y no romper el código que la vincula.Siempre que realice las modificaciones correctas, que incluyen agregar nuevas funciones, podrá ampliar y evolucionar su código.

Krzysztof Cwalina dice en la página 81:

A lo largo 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 se arrepienten de haber incluido alguna API como interfaz.No he oído hablar ni siquiera de un caso en el que alguien se haya arrepentido de haber enviado una clase.

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

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 sobre ella.

La ventaja de esto es que me permite, por ejemplo, reunir todos los IYippieDog y colocarlos en mi colección Ocean.Así que ahora puedo alcanzar un conjunto particular de objetos y encontrar aquellos que cumplan con los criterios que estoy mirando 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 se implementan, normalmente no es necesario que exista.No me dicen nada útil.

Sin embargo, este pensamiento va en contra de la idea de que cada clase debería tener una interfaz y usted debería codificar la interfaz.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 hacerlo realmente no cuesta nada y ahora puedes intercambiar cosas dentro y fuera con facilidad.Sin embargo, encuentro que rara vez hago eso.La mayoría de las veces simplemente estoy modificando la clase existente y tengo exactamente los mismos problemas que siempre tuve si es necesario cambiar la interfaz pública de esa clase, 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 coincide con ambos.

La otra parte de esto es ¿deberían tener la misma clase base?La pregunta es si en términos generales es necesario tratarlos como la misma cosa.Ciertamente ambos son animales, pero ¿se ajusta eso a cómo vamos a usarlos juntos?

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

¿O es necesario que sean mamíferos?¿Quizás necesitemos algún tipo de fábrica de ordeño de animales cruzados?

¿Es necesario siquiera que estén unidos?¿Es suficiente saber que ambos son IPettable?

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

También es posible que en algún momento descubras que no todos los gatos son tolerables (como ese sin pelo).Ahora puedes mover esa interfaz a todas las clases derivadas que encajen.Descubrirá que un cambio mucho menos radical es que, de repente, los gatos ya no se derivan de PettableBase.

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

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

salud

Recomiendo usar composición en lugar de herencia siempre que sea posible.Utilice interfaces pero utilice objetos miembro para la implementación básica.De esa manera, puedes definir una fábrica que construya tus objetos para que se comporten 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, es posible que sus objetos principales no necesiten ninguna interfaz, si todo el comportamiento mutable está definido en objetos auxiliares.

Entonces, en lugar de IPet o PetBase, podría terminar con una mascota que tenga un parámetro IFurBehavior.El parámetro IFurBehavior lo establece 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 se ocupan de comportamientos muy básicos en todo el sistema.

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

Bien explicado en este. Artículo del mundo Java

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 que implemente 1 o más interfaces.

Las clases abstractas las uso como base para otra cosa.

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


Interfaz vs.clase abstracta

Elegir interfaces y clases abstractas no es una cuestión de uno u otro.Si necesita cambiar su diseño, conviértalo en una interfaz.Sin embargo, es posible que tenga clases abstractas que proporcionen algún comportamiento predeterminado.Las clases abstractas son excelentes candidatas dentro de los marcos de aplicaciones.

Las clases abstractas te permiten definir algunos comportamientos;obligan a tus subclases a proporcionar otras.Por ejemplo, si tiene un marco de aplicación, una clase abstracta puede proporcionar servicios predeterminados, como manejo de eventos y mensajes.Esos servicios permiten que su aplicación se conecte al marco de su aplicación.Sin embargo, existen algunas funciones específicas de la aplicación que solo su aplicación puede realizar.Dicha funcionalidad podría incluir tareas de inicio y apagado, que a menudo dependen de la aplicación.Entonces, en lugar de intentar definir ese comportamiento en sí, la clase base abstracta puede declarar métodos abstractos de apagado e inicio.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;sólo sabe que debe iniciar las acciones.Cuando llega el momento de iniciar, 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 clases no hoja en jerarquías de clases.

Clase vs.interfaz

Algunos dicen que deberías 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 utilizan.Un reproductor multimedia puede saber cómo reproducir CD, MP3 y archivos wav.Por supuesto, no querrás codificar esos algoritmos de reproducción en el reproductor;eso hará que sea difícil agregar un nuevo formato como AVI.Además, su código estará lleno de declaraciones de casos inútiles.Y para colmo de males, deberá actualizar esas declaraciones de casos cada vez que agregue un nuevo algoritmo.Considerándolo todo, esta no es una forma de programar muy orientada a objetos.

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

Este es un excelente lugar para utilizar una interfaz.Hemos utilizado el patrón Estrategia, que indica claramente un lugar en el diseño que cambiará.Por lo tanto, debes definir la estrategia como una interfaz.Generalmente deberías preferir las interfaces a la herencia cuando quieras que un objeto tenga un tipo determinado;en este caso, MediaStrategy.Depender de la herencia para la identidad del tipo es peligroso;te encierra en una jerarquía de herencia particular.Java no permite la herencia múltiple, por lo que no puedes extender algo que te brinde una implementación útil o más identidad de tipos.

También tenga en cuenta no dejarse llevar por OO (ver blog) y siempre modele objetos según el comportamiento requerido; si estuviera diseñando una aplicación donde el único comportamiento que necesitaba era un nombre genérico y una especie para un animal, entonces solo necesitaría una clase Animal con una propiedad para el nombre, en lugar de millones de clases para todos los animales posibles en el mundo.

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 prácticamente iguales, las partes serán diferentes: clase abstracta.

Datos y funcionalidad que realmente funcionan, si se amplían solo con ligeros cambios: clase ordinaria (concreta)

Datos y funcionalidad, no hay cambios previstos: clase ordinaria (concreta) con modificador final.

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

Esto es muy aproximado y no está estrictamente definido, pero hay un espectro que va desde interfaces donde todo debe cambiarse hasta enumeraciones donde todo está arreglado un poco como un archivo de solo lectura.

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 puedas codificar una vez y terminar?En caso afirmativo, entonces es hora de una clase abstracta.

Las clases abstractas también son limitantes.Si bien le brindan un excelente atajo para producir objetos secundarios, cualquier objeto determinado solo puede implementar una clase abstracta.Muchas veces encuentro 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 hacerlo).Sin embargo, PetBase probablemente no implementará IMammal, ya que no todas las mascotas son mamíferos ni todos los mamíferos son mascotas.Puede agregar MammalPetBase que extienda 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 ser un ejemplo simple, pero eso es parte de lo bueno de cómo las interfaces y las clases abstractas funcionan juntas.

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

Clases base vs.Interfaces Un tipo de interfaz es un tipo parcial descripción de un valor, potencialmente Soportado por muchos tipos de objetos.Uso Clases base en lugar de interfaces siempre que sea posible.A partir de un control de versiones perspectiva, las clases son más flexibles que las interfaces.Con una clase, puedes: Versión 1.0 y luego en Versión 2.0 Agregue un nuevo método a la clase.Siempre y cuando el método no sea abstracto, Las clases derivadas existentes continúan para que funcione sin cambios.

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

  1. Varias clases no relacionadas quieren soportar el protocolo.
  2. Estas clases ya tienen clases base establecidas (para ejemplo algunos son controles de interfaz de usuario (UI), y algunos son servicios Web XML).
  3. La agregación no es apropiada ni practicable.En todos los demás Situaciones La herencia de clases es un mejor modelo.

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 durante los últimos 14 años.Esto es fantástico para nosotros, los desarrolladores, porque un lenguaje maduro nos proporciona una gran cantidad de funciones de lenguaje que están a nuestra disposición.

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

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

Todo esto está muy bien y proporciona una excelente reutilización de código y se adhiere al principio DRY (No repetirse) de 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.También lo es un caniche.Ambos pueden ladrar, como todos los perros.Sin embargo, es posible que desee afirmar que el parque para caniches es significativamente diferente al ladrido de perro "predeterminado".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 pueda confiar en el Bark predeterminado en lugar de una implementación de caso especial.Las clases como GoldenRetriever, Boxer, Lab podrían heredar el Bark “predeterminado” (clase de bajo) sin costo alguno solo porque implementan la clase abstracta Dog.

Pero estoy seguro de que ya lo sabías.

Está aquí porque quiere entender por qué es posible que desee elegir una interfaz en lugar de una clase abstracta o viceversa.Bueno, una de las razones por las que quizás quieras elegir una interfaz en lugar de una clase abstracta es cuando no tienes o no quieres 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 por qué estar relacionados en absoluto 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 aprobó sus lecciones de natación en 1er grado :) ).Además, dado que un pato no es un humano o viceversa, esta no es una relación de "es un", sino una relación de "es capaz" y podemos usar una interfaz para ilustrar eso:

// 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 "puede" 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 adoptará en tiempo de ejecución según su tipo.

Una vez más, esto ayuda a que su código permanezca SECO para que no tenga que escribir múltiples métodos que llamen al objeto para realizar la misma función principal (ShowHowHumanSwims(human), ShowHowDuckSwims(duck), etc.)

El uso de una interfaz aquí permite que los métodos de llamada no tengan que preocuparse por qué tipo es cuál o cómo se implementa el comportamiento.Simplemente sabe que, dada la interfaz, cada objeto deberá 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:

Entonces, mi regla general principal es usar una clase abstracta cuando quieras implementar una funcionalidad "predeterminada" para una jerarquía de clases y/y las clases o tipos con los que estás trabajando comparten una relación "es un" (ej.el caniche “es un” tipo de perro).

Por otro lado, utilice una interfaz cuando no tenga una relación "es un" pero tenga tipos que compartan "la capacidad" de hacer algo o tener algo (ej.El pato "no es" un humano.Sin embargo, el pato y el humano comparten “la habilidad” de nadar).

Otra diferencia a tener en cuenta entre clases abstractas e interfaces es que una clase puede implementar de una a muchas interfaces, pero una clase solo puede heredar de UNA clase abstracta (o de cualquier clase).Sí, puedes anidar clases y tener una jerarquía de herencia (que muchos programas tienen y deberían tener), pero no puedes heredar dos clases en una definición de clase derivada (esta regla se aplica a C#.En algunos otros idiomas es posible hacer esto, normalmente sólo debido a la falta de interfaces en estos idiomas).

Recuerde también que cuando utilice interfaces se adhiera al principio de segregación de interfaces (ISP).El ISP afirma que ningún cliente debe verse obligado a depender de métodos que no utiliza.Por esta razón las interfaces deben centrarse en tareas específicas y suelen ser muy pequeñas (ej.IDesechable, IComparable).

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

¡Espero que esto aclare las cosas para algunas personas!

Además, si se te ocurre algún ejemplo mejor o quieres señalar algo, ¡hazlo en los comentarios a continuación!

Una diferencia importante es que sólo puedes heredar uno clase base, pero puedes implementar muchos interfaces.Entonces sólo querrás usar una clase base si eres absolutamente cierto que no necesitarás heredar también una clase base diferente.Además, si descubre que su interfaz se está haciendo grande, entonces debería comenzar a buscar dividirla en algunas partes lógicas que definan una funcionalidad independiente, ya que no existe ninguna regla que indique que su clase no pueda implementarlas todas (o que pueda definir una interfaz diferente). interfaz que simplemente los hereda todos para agruparlos).

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 lotes de cosas que se pueden acariciar: novias, coches, mantas peludas...- entonces podría haber tenido una clase Petable que proporcionara este comportamiento común y varias clases heredadas de él.

Sin embargo, ser petable no forma 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 coche 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 grupo grande 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, una vez que comprendas el diseño orientado a objetos lo suficientemente mejor que yo al principio, normalmente lo harás automáticamente sin siquiera pensar en ello.Entonces, la pura verdad de la afirmación "código para una interfaz, no para una clase abstracta" se vuelve tan obvia que cuesta creer que alguien se molestaría en decirlo y comenzaría a intentar leer otros significados en ella.

Otra cosa que agregaría es que si una clase es puramente abstracto (sin miembros ni métodos no abstractos ni heredados expuestos al niño, al padre o al cliente), entonces ¿por qué es una clase?Podría ser reemplazado, en algunos casos por una interfaz y en otros casos por Null.

Prefiero interfaces a clases abstractas

Fundamento Los principales puntos a tener en cuenta [dos ya mencionados aquí] son:

  • Las interfaces son más flexibles, ya que una clase puede implementar varios Interfaces.Dado que Java no tiene herencia múltiple, el uso de abstract impide que los usuarios usen cualquier otra clase jerarquía. En general, prefiera interfaces cuando no hay interfaces predeterminadas implementaciones o estado. Las colecciones de Java ofrecen buenos ejemplos de esto (Mapa, Conjunto, etc.).
  • Las clases abstractas tienen la ventaja de permitir un mejor avance compatibilidad.Una vez que los clientes usan una interfaz, no puedes cambiarla;Si usan una clase abstracta, aún puede agregar comportamiento sin rompiendo el código existente. Si la compatibilidad es un problema, considere la posibilidad de 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 permitirá una mayor libertad si deseado [1].
    Por supuesto, el tema ha sido discutido extensamente en otros lugares [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, artículos 16-18.

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

Los comentarios anteriores sobre el uso de clases abstractas para una implementación común definitivamente son acertados.Un beneficio que aún no he visto mencionado es que el uso de interfaces hace que sea mucho más fácil implementar objetos simulados para realizar pruebas unitarias.Definir IPet y PetBase como lo describió Jason Cohen le permite simular 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).

No utilice 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 interfaces pequeñas.

La herencia pública se usa en exceso en OOD y expresa mucho más de lo que la mayoría de los desarrolladores creen 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 entrega no menos que B, para cada método que expone.

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 de métodos y firmas, semiformalmente significa documentación legible por humanos asociada con esos métodos.Las interfaces son sólo descripciones de una API (después de todo, API significa Programador de Aplicaciones). Interfaz), no pueden contener ninguna implementación y no es posible utilizar ni ejecutar una interfaz.Sólo hacen explícito el contrato de cómo debes interactuar con un objeto.

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

Existe una distinción entre una clase base y una clase base abstracta (ABC).Los ABC combinan interfaz e implementación.Resumen fuera de la programación informática significa "resumen", es decir, "Resumen == Interfaz".Una clase base abstracta puede entonces 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 según lo que esté desarrollando y el idioma en el que esté desarrollando.Las interfaces a menudo se asocian solo con lenguajes de tipo estático como Java o C#, pero los lenguajes de tipo dinámico también pueden tener interfaces y clases base abstractas.En Python, por ejemplo, se hace una distinción clara entre una Clase, que declara que implementos una Interfaz y un objeto, que es una instancia de una Clase, y se dice que proporcionar esa interfaz.Es posible en un lenguaje dinámico que dos objetos que son instancias de la misma Clase puedan declarar que proporcionan completamente diferente interfaces.En Python, esto solo es posible para los atributos de los objetos, mientras que los métodos comparten el estado 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 el programador desee (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 introspeccionando un objeto y preguntándole qué métodos proporciona (Mira antes de saltar) o preferiblemente simplemente intentando usar la interfaz deseada en un objeto y detectando excepciones si el objeto. no proporciona esa interfaz (es más fácil pedir perdón que permiso).Esto puede llevar 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 demasiado desde el principio para anticipar todos los usos posibles. de tu código.

Otra opción a tener en cuenta es utilizar la relación "tiene-un", 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 usar la herencia "es-un".

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 la 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 al hacer las cosas de esta manera.Pero también se debe apreciar que esto ayuda a mantener a Perro y Gato desacoplados de la clase Mascota (en el sentido de que Perro y Gato no tienen acceso a los miembros privados de Mascota), y deja espacio para que Perro y 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 hacer referencia a Perro y Gato utilizando referencias/indicadores genéricos de 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 las cosas cuando están mal organizadas.La herencia es útil cuando necesita acceso privado para miembros y, al usarla, se compromete a combinar en gran medida sus clases de Perro y Gato con su clase de Mascota, lo cual es un costo elevado de pagar.

Entre herencia, composición e interfaces no existe una única forma que sea siempre correcta, y es útil considerar cómo se pueden utilizar las tres opciones en armonía.De las tres, la herencia suele ser la opción que debería utilizarse con menos frecuencia.

Depende de tus requisitos.Si IPet es lo suficientemente simple, preferiría implementarlo.De lo contrario, si PetBase implementa un montón de funciones que no deseas duplicar, entonces hazlo.

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

Por último, la herencia única de .NET me mata.Un ejemplo ingenuo:Digamos que estás creando un control de usuario, por lo que heredas UserControl.Pero ahora no puedes heredar también PetBase.Esto le obliga a reorganizarse, como por ejemplo a hacer una PetBase miembro de la clase, en cambio.

@Joel:Algunos lenguajes (por ejemplo, C++) permiten la herencia múltiple.

Por lo general, no implemento ninguno de los dos hasta que necesito uno.Prefiero las interfaces a 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 subo y creo 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 a código (en mi humilde opinión) de que la solución ha sido diseñada en exceso.

En cuanto a C#, en cierto sentido 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 a subclases más arriba en la pila;y iii) solo se pueden heredar clases abstractas en una clase, mientras que se pueden implementar múltiples interfaces en una clase.

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 de forma predeterminada una interfaz implícita.También podemos definir una interfaz como un rol, cuando alguna clase necesita desempeñar ese rol, tiene que implementarlo dándole diferentes formas de implementación dependiendo de la clase que lo implemente.Por lo tanto, cuando hablas de interfaz, estás hablando de polimorfismo y cuando hablas de clase base, estás hablando de herencia.Dos conceptos de ¡¡¡ups!!!

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 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 pelo y pezones (AFAIK, no soy zoólogo), podemos incorporar esto a 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 bueno cuando hay muchas clases concretas y no desea mantener un texto estándar 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, lo abstracto también podría ser concreto;sin embargo, la designación abstracta ayuda a enfatizar que se está empleando este patrón.

Un heredero de una clase base debe tener una relación "es un".La interfaz representa una relación "implementa una".Por lo tanto, utilice una clase base únicamente cuando sus herederos mantengan la relación.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top