c# - programacion - ¿Es la mejor práctica extraer una interfaz para cada clase?
que es override en programacion (13)
He visto código donde cada clase tiene una interfaz que implementa.
A veces no hay una interfaz común para todos.
Están allí y se usan en lugar de objetos concretos.
No ofrecen una interfaz genérica para dos clases y son específicos del dominio del problema que la clase resuelve.
¿Hay alguna razón para hacer eso?
¿Por qué necesitas interfaces? Piensa de manera práctica y profunda. Las interfaces no están realmente asociadas a las clases, sino que están conectadas a los servicios. El objetivo de la interfaz es lo que permite que otros hagan con su código sin darles el código. Por lo tanto, se relaciona con el servicio y su administración.
Nos vemos
Las interfaces definen un comportamiento. Si implementa una o más interfaces, su objeto se comporta como se describe en una u otras interfaces. Esto permite un acoplamiento flexible entre las clases. Es realmente útil cuando tiene que reemplazar una implementación por otra. La comunicación entre las clases se realizará siempre utilizando interfaces, excepto si las clases están realmente ligadas entre sí.
Las interfaces son buenas ya que puedes simular las clases cuando pruebas (unitarias).
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 burlón para simular el comportamiento.
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, podría implementarse de manera diferente en el futuro o hay dos 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 robo de codificación, o estaban siendo listos y preparándose para la resistencia de la versión, o preparando para la prueba unitaria. Es más probable que lo primero porque la capacidad de recuperación de la versión es una necesidad poco común (es decir, cuando 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 que valen la pena aislar de algún otro código que planeo probar. Si estas interfaces no se crearon para apoyar las pruebas unitarias tampoco, entonces no estoy seguro de que sean una buena idea. La interfaz tiene un costo de mantenimiento y cuando llega el momento de hacer que un objeto pueda intercambiarse con otro, es posible que desee que una interfaz se aplique solo a unos pocos métodos (para que más clases puedan implementarla), podría ser mejor usar un resumen clase (para que los comportamientos predeterminados se puedan implementar en un árbol de herencia).
Entonces las interfaces de pre-necesidad probablemente no sean una buena idea.
No creo que sea razonable para todas las clases.
Es una cuestión de cuánta reutilización esperas de qué tipo de componente. Por supuesto, debe planear una mayor reutilización (sin la necesidad de realizar refactorizaciones importantes más adelante) de lo que realmente va a usar en este momento, pero extraer una interfaz abstracta para cada clase en un programa significaría que tiene menos clases que necesario.
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 muerte excesiva. La razón por la que deben extraer interfaces sería el hecho de que parecen implementar un principio OOAD " Programa para la interfaz, no para la implementación ". Puede encontrar más información sobre este principio con un ejemplo aquí .
No.
Las interfaces son buenas para clases con comportamiento complejo, y son especialmente útiles si desea poder crear una clase de implementación ficticia o falsa de esa interfaz para usar en pruebas unitarias.
Sin embargo, algunas clases no tienen mucho comportamiento y se pueden tratar más como valores y generalmente consisten en un conjunto de campos de datos. No tiene sentido crear interfaces para clases como esta porque hacerlo introduciría una sobrecarga innecesaria cuando no tiene sentido burlarse o proporcionar implementaciones alternativas de la interfaz. Por ejemplo, considera una clase:
class Coordinate
{
public Coordinate( int x, int y);
public int X { get; }
public int y { get; }
}
Es poco probable que desee una interfaz ICoordinate
para ir con esta clase, porque no tiene mucho sentido implementarlo de otra manera que simplemente obtener y establecer los valores 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<Coordinate> GetShortestRoute( List<Coordinate> waypoints );
}
es probable que desee una interfaz IRoutePlanner
para RoutePlanner
porque hay muchos algoritmos diferentes que podrían utilizarse para planificar una ruta.
Además, si tuviera una tercera clase:
class RobotTank
{
public RobotTank( IRoutePlanner );
public void DriveRoute( List<Coordinate> points );
}
Al darle a RoutePlanner
una interfaz, puede escribir un método de prueba para RobotTank
que crea uno con un RoutePlanner
falso que simplemente devuelve una lista de coordenadas sin un orden en particular. Esto permitiría que el método de prueba verifique que el tanque navegue correctamente entre las coordenadas sin probar también el planificador de ruta. Esto significa que puede escribir una prueba que solo pruebe una unidad (el tanque), sin también probar el planificador de ruta.
Sin embargo, verá que es muy fácil alimentar coordenadas reales a una prueba como esta sin necesidad de ocultarlas detrás de una interfaz ICoordinate
.
Permítanme citar a OO guru, Martin Fowler, para agregar alguna justificación sólida a la respuesta más común en este hilo.
Este fragmento proviene de los "Patrones de la arquitectura de aplicaciones empresariales" (alistados en los "clásicos de la programación" y "o todo desarrollador debe leer" la categoría de libros).
[Patrón] Interfaz separada
(...)
Cuándo usarlo
Utiliza 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 las interfaces e implementaciones por separado es trabajo adicional, especialmente porque a menudo necesita clases de fábrica (con interfaces e implementaciones) también. Para las aplicaciones, recomiendo usar una interfaz separada solo si quiere romper una dependencia o si desea tener múltiples implementaciones independientes. Si junta la interfaz y la implementación y necesita separarlas más adelante, se trata de una refactorización simple que se puede retrasar hasta que tenga que hacerlo.
Respondiendo a tu pregunta: no
He visto parte 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.
Puede haber, si quiere estar seguro de poder inyectar otras implementaciones en el futuro. Para algunos casos (tal vez la mayoría), esto es excesivo, pero es como con la mayoría de los hábitos: si estás acostumbrado, no pierdes mucho tiempo haciéndolo. Y como nunca se puede estar seguro de lo que querrá reemplazar en el futuro, extraer una interfaz en cada clase tiene sentido.
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.
Puede parecer tonto, pero el beneficio potencial de hacerlo de esta manera es que si en algún momento se da cuenta de que hay una mejor manera de implementar una determinada funcionalidad, puede simplemente escribir una nueva clase que implemente la misma interfaz, y cambiar una línea a 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 se puede alternar entre implementaciones antiguas y nuevas para compararlas.
Puede que nunca aproveche esta conveniencia y su producto final realmente solo 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 hubieras necesitado, te habrían ahorrado mucho tiempo.
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 flexible que hace que el mantenimiento del sistema sea mucho más fácil.
A medida que su sistema crece y se vuelve más complejo, ¡este principio sigue teniendo más y más sentido!
Tener la interfaz y la codificación de la interfaz hace que sea mucho más fácil intercambiar implementaciones. Esto también se aplica a las pruebas unitarias. 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 sea más enfocada y de grano más fino.
Es más común por lo que he visto cambiar las implementaciones para probar (simulacros) luego en el código de producción real. Y sí, está enojado por las pruebas unitarias.
Después de revisar esta respuesta, he decidido enmendarlo 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 algunas razones:
- Soporte de prueba (burlas, trozos).
- Abstracción de implementación (avance en IoC / DI).
- Cosas auxiliares como el apoyo de co y contra varianza en C #.
Para lograr estos objetivos, las interfaces se consideran buenas prácticas (y en realidad se requieren para el último punto). Dependiendo del tamaño del proyecto, encontrará que puede que nunca necesite hablar con una interfaz o que esté constantemente extrayendo interfaces por alguna de las razones anteriores.
Mantenemos una gran aplicación, algunas partes son geniales y otras sufren de falta de atención. Con frecuencia nos encontramos refactorizando para sacar una interfaz de un tipo para que sea comprobable o para que podamos cambiar las implementaciones mientras se reduce el impacto de ese cambio. También hacemos esto para reducir el efecto de "acoplamiento" que los tipos concretos pueden imponer accidentalmente si no es estricto en su API pública (las interfaces solo pueden representar una API pública, por lo que para nosotros es inherentemente muy estricto).
Dicho esto, es posible abstraer el comportamiento sin interfaces y probar los 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 efectivamente contra las interfaces.
Dejaré mi respuesta anterior por contexto.Las interfaces definen un contrato público. Las personas que implementan las interfaces deben implementar este contrato. Los consumidores solo ven el contrato público. Esto significa que los detalles de implementación se han abstraído del consumidor.
Un uso inmediato para esto en estos días es la Unidad de Pruebas . Las interfaces son fáciles de burlar, trozo, falso, lo que sea.
Otro uso inmediato es la Inyección de Dependencia . Se proporciona un tipo concreto registrado para una interfaz dada a un tipo que consume una interfaz. El tipo no se preocupa específicamente por la implementación, por lo que puede solicitar de forma abstracta la interfaz. Esto le permite cambiar implementaciones sin afectar gran cantidad de código (el área de impacto es muy pequeña siempre que el contrato permanezca igual).
Para proyectos muy pequeños, no suelo molestarme, en los proyectos medianos, tiendo a preocuparme por los ítems principales importantes, y para proyectos grandes, tiende a haber una interfaz para casi todas las clases. Esto es casi siempre para respaldar las pruebas, pero en algunos casos, el comportamiento inyectado o la abstracción del comportamiento para reducir la duplicación del código.