¿Es la mejor práctica extraer una interfaz para cada clase?

He visto un código donde cada clase tiene una interfaz que implementa.

A veces no hay una interfaz común para todos ellos.

Simplemente están allí y se usan en lugar de objetos concretos.

No ofrecen una interfaz genérica para dos clases y son específicas del dominio del problema que la clase resuelve.

¿Hay alguna razón para hacer eso?

Después de revisar esta respuesta, he decidido enmendarla un poco.

No, no es una buena práctica extraer interfaces para cada clase. Esto puede ser contraproducente. Sin embargo, las interfaces son útiles por varias razones:

  • Soporte de prueba (simulacros, talones).
  • Implementación de abstracción (fomentando IoC / DI).
  • Cosas auxiliares como el soporte de co y contra-varianza en C #.

Para lograr estos objectives, las interfaces se consideran una buena práctica (y en realidad se requieren para el último punto). Dependiendo del tamaño del proyecto, encontrará que nunca necesitará hablar con una interfaz o que está extrayendo interfaces constantemente por una de las razones anteriores.

Mantenemos una gran aplicación, algunas partes son geniales y algunas sufren la falta de atención. Con frecuencia nos encontramos refactorizando para sacar una interfaz de un tipo para que sea verificable o para que podamos cambiar las implementaciones al tiempo que disminuimos el impacto de ese cambio. También hacemos esto para reducir el efecto de “acoplamiento” que los tipos concretos pueden imponer accidentalmente si usted no es estricto en su API pública (las interfaces solo pueden representar una API pública, por lo que para nosotros es muy estricto).

Dicho esto, es posible abstraer el comportamiento sin interfaces y es posible probar tipos sin necesidad de interfaces, por lo que no son un requisito de lo anterior. Es solo que la mayoría de los marcos / bibliotecas que puede usar para apoyarlo en esas tareas funcionarán de manera efectiva contra las interfaces.


Dejaré mi vieja respuesta por contexto.

Las interfaces definen un contrato público. Las personas que implementan interfaces tienen que implementar este contrato. Los consumidores solo ven el contrato público. Esto significa que los detalles de la implementación se han abstraído del consumidor.

Un uso inmediato para esto en estos días es la prueba unitaria . Las interfaces son fáciles de burlar, astackr, falsificar, lo que sea.

Otro uso inmediato es la dependency injection . Un tipo concreto registrado para una interfaz dada se proporciona a un tipo que consume una interfaz. El tipo no se preocupa específicamente por la implementación, por lo que puede solicitar la interfaz de manera abstracta. Esto le permite cambiar las implementaciones sin afectar un montón de código (el área de impacto es muy pequeña siempre que el contrato se mantenga igual).

Para proyectos muy pequeños no tiendo a molestar, para proyectos medianos, tiendo a preocuparme por elementos centrales importantes, y para proyectos grandes tiende a haber una interfaz para casi todas las clases. Esto casi siempre es compatible con las pruebas, pero en algunos casos de comportamiento inyectado o abstracción de comportamiento para reducir la duplicación de código.

No.

Las interfaces son buenas para las clases con un comportamiento complejo, y son especialmente útiles si desea poder crear una clase de implementación simulada o falsa de esa interfaz para usar en pruebas unitarias.

Sin embargo, algunas clases no tienen mucho comportamiento y pueden tratarse más como valores y generalmente consisten en un conjunto de campos de datos. No tiene mucho sentido crear interfaces para este tipo de clases, ya que hacerlo implicaría una sobrecarga innecesaria cuando no tiene mucho sentido en burlarse o proporcionar implementaciones alternativas de la interfaz. Por ejemplo, considere una clase:

class Coordinate { public Coordinate( int x, int y); public int X { get; } public int y { get; } } 

Es poco probable que desee que una interfaz ICoordinate vaya con esta clase, porque no tiene mucho sentido implementarla de otra manera que no sea simplemente obtener y establecer los valores de X e Y

Sin embargo, la clase

 class RoutePlanner { // Return a new list of coordinates ordered to be the shortest route that // can be taken through all of the passed in coordinates. public List GetShortestRoute( List waypoints ); } 

probablemente querrá una interfaz IRoutePlanner para RoutePlanner porque hay muchos algoritmos diferentes que podrían usarse para planificar una ruta.

Además, si tuvieras una tercera clase:

 class RobotTank { public RobotTank( IRoutePlanner ); public void DriveRoute( List points ); } 

Al darle a RoutePlanner una interfaz, puede escribir un método de prueba para RobotTank que crea uno con un RoutePlanner simulado que solo devuelve una lista de coordenadas sin ningún orden en particular. Esto permitiría que el método de prueba compruebe que el tanque navega correctamente entre las coordenadas sin probar también el planificador de rutas. Esto significa que puede escribir una prueba que solo pruebe una unidad (el tanque), sin probar también el planificador de rutas.

Sin embargo, verá que es bastante fácil ICoordinate Coordenadas reales en una prueba como esta sin necesidad de ocultarlas detrás de una interfaz de ICoordinate .

Permítanme citar al gurú de OO, Martin Fowler, para agregar una justificación sólida a la respuesta más común en este hilo.

Este extracto proviene de los “Patrones de la architecture de la aplicación empresarial” (incluido en los “clásicos de la progtwigción” y \ o la categoría de libro “todo desarrollador debe leer”).

[Patrón] Interfaz separada

(…)

Cuando usarlo

Usted usa la Interfaz Separada cuando necesita romper una dependencia entre dos partes del sistema.

(…)

Me encuentro con muchos desarrolladores que tienen interfaces separadas para cada clase que escriben. Creo que esto es excesivo, especialmente para el desarrollo de aplicaciones. Mantener interfaces e implementaciones separadas es un trabajo adicional, especialmente porque a menudo también se necesitan clases de fábrica (con interfaces e implementaciones). Para las aplicaciones, recomiendo usar una interfaz separada solo si desea romper una dependencia o si desea tener varias implementaciones independientes. Si reúne la interfaz y la implementación y necesita separarlas más adelante, esta es una refactorización simple que puede retrasarse hasta que necesite hacerlo.

Respondiendo a tu pregunta: no

He visto algo del código “sofisticado” de este tipo, donde el desarrollador cree que es SÓLIDO, pero en cambio es ininteligible, difícil de extender y demasiado complejo.

No hay ninguna razón práctica detrás de la extracción de interfaces para cada clase en su proyecto. Eso sería una matanza excesiva. La razón por la que deben extraer interfaces sería el hecho de que parecen implementar un principio de OOAD ” Progtwig a interfaz, no a implementación “. Puede encontrar más información sobre este principio con un ejemplo aquí .

Tener la interfaz y la encoding de la interfaz hace que sea mucho más fácil intercambiar implementaciones. Esto también se aplica con la prueba de la unidad. Si está probando algún código que usa la interfaz, puede (en teoría) usar un objeto simulado en lugar de un objeto concreto. Esto permite que su prueba esté más enfocada y sea más precisa.

Es más común por lo que he visto cambiar las implementaciones para las pruebas (simulacros) que en el código de producción real. Y sí, está enojado por pruebas unitarias.

Puede parecer una tontería, pero el beneficio potencial de hacerlo de esta manera es que si en algún momento te das cuenta de que hay una mejor manera de implementar cierta funcionalidad, puedes escribir una nueva clase que implemente la misma interfaz y cambiar una línea para haga que todo su código use esa clase: la línea donde se asigna la variable de interfaz.

Hacerlo de esta manera (escribir una nueva clase que implemente la misma interfaz) también significa que siempre puede alternar entre las implementaciones antiguas y nuevas para compararlas.

Es posible que nunca aproveches esta conveniencia y que tu producto final realmente use la clase original que se escribió para cada interfaz. Si ese es el caso, ¡genial! Pero realmente no tomó mucho tiempo escribir esas interfaces, y si las necesitaras, te habrían ahorrado mucho tiempo.

No creo que sea razonable para cada clase.

Es una cuestión de la cantidad de reutilización que espera de qué tipo de componente. Por supuesto, tiene que planificar más reutilización (sin la necesidad de hacer una refactorización importante más adelante) de lo que realmente va a usar en este momento, pero extraer una interfaz abstracta para cada clase en un progtwig significaría que tiene menos clases que necesario.

Me gustan las interfaces en cosas que podrían implementarse de dos maneras diferentes, ya sea en el tiempo o en el espacio, es decir, se podría implementar de manera diferente en el futuro, o hay 2 clientes de código diferentes en diferentes partes del código que pueden querer una implementación diferente.

El escritor original de su código podría haber sido simplemente encoding robo, o estaban siendo inteligentes y preparándose para la resistencia de la versión, o preparándose para pruebas de unidad. Lo más probable es que sea la primera porque la resistencia de la versión es una necesidad poco común (es decir, donde el cliente se implementa y no se puede cambiar y se implementará un componente que debe ser compatible con el cliente existente)

Me gustan las interfaces en cosas que son dependencias dignas de aislamiento de algún otro código que planeo probar. Si estas interfaces no fueron creadas para soportar pruebas de unidad tampoco, entonces no estoy seguro de que sean una buena idea. La interfaz tiene un costo que mantener y cuando llega el momento de hacer que un objeto sea intercambiable con otro, es posible que desee que la interfaz se aplique solo a unos pocos métodos (para que más clases puedan implementar la interfaz), sería mejor utilizar un resumen. clase (para que los comportamientos predeterminados se puedan implementar en un árbol de herencia).

Por lo tanto, las interfaces de necesidad previa probablemente no sean una buena idea.

Si es parte del principio de inversión de dependencia . Básicamente, el código depende de las interfaces y no de las implementaciones.

Esto le permite intercambiar fácilmente las implementaciones sin afectar las clases de llamada. Permite un acoplamiento más suelto que facilita mucho el mantenimiento del sistema.

¡A medida que su sistema crece y se vuelve más complejo, este principio sigue teniendo más y más sentido!

Podría haber, si desea asegurarse de poder inyectar otras implementaciones en el futuro. Para algunos (tal vez la mayoría) de los casos, esto es una exageración, pero es como con la mayoría de los hábitos: si estás acostumbrado a hacerlo, no pierdes mucho tiempo haciéndolo. Y como nunca puede estar seguro de lo que querrá reemplazar en el futuro, extraer una interfaz en cada clase tiene un punto.

Nunca hay una sola solución a un problema. Por lo tanto, siempre podría haber más de una implementación de la misma interfaz.

Es bueno tener las interfaces, ya que puedes burlarte de las clases cuando (unidad) las pruebas.

Creo interfaces para al menos todas las clases que tocan recursos externos (por ejemplo, base de datos, sistema de archivos, servicio web) y luego escribo un simulacro o uso un marco de simulacros para simular el comportamiento.

Las interfaces definen un comportamiento. Si implementa una o más interfaces, entonces su objeto se comporta como lo describe una u otra interfaz. Esto permite el acoplamiento suelto entre clases. Es realmente útil cuando tienes que reemplazar una implementación por otra. La comunicación entre clases siempre se realizará utilizando interfaces, excepto si las clases están realmente estrechamente vinculadas entre sí.

¿Por qué necesitas interfaces? Piensa de manera práctica y profunda. Las interfaces no están realmente vinculadas a las clases, sino que están vinculadas a los servicios. El objective de la interfaz es lo que permite que otros hagan con su código sin proporcionarles el código. Así que se relaciona con el servicio y su gestión.

Nos vemos