Declarar interfaz en el mismo archivo que la clase base, ¿es una buena práctica?


29

Para ser intercambiable y comprobable, normalmente los servicios con lógica deben tener una interfaz, p. Ej.

public class FooService: IFooService 
{ ... }

En cuanto al diseño, estoy de acuerdo con esto, pero una de las cosas que me molesta con este enfoque es que para un servicio deberá declarar dos cosas (la clase y la interfaz), y en nuestro equipo, normalmente dos archivos (uno para la clase y uno para la interfaz). Otra incomodidad es la dificultad en la navegación porque el uso de "Ir a definición" en IDE (VS2010) apuntará a la interfaz (ya que otras clases se refieren a la interfaz), no a la clase real.

Estaba pensando que escribir IFooService en el mismo archivo que FooService reducirá la rareza anterior. Después de todo, IFooService y FooService están muy relacionados. ¿Es esta una buena practica? ¿Hay una buena razón por la que IFooService debe ubicarse en su propio archivo?


3
Si solo tiene una implementación de una interfaz en particular, no hay una necesidad lógica real para la interfaz. ¿Por qué no usar la clase directamente?
Joris Timmermans

11
@MadKeithV para acoplamiento flojo y capacidad de prueba?
Louis Rhys

10
@MadKeithV: tienes razón. Pero cuando escribe pruebas unitarias para el código que se basa en IFooService, generalmente proporcionará un MockFooService, que es una segunda implementación de esa interfaz.
Doc Brown

55
@DocBrown: si tiene múltiples implementaciones, entonces obviamente el comentario desaparece, pero también lo hace la utilidad de poder ir directamente a la "implementación única" (porque hay al menos dos). Entonces la pregunta es: ¿qué información hay en la implementación concreta que no debería estar ya en la descripción / documentación de la interfaz en el punto donde la está utilizando?
Joris Timmermans

1
Publicidad espontánea
Marjan Venema

Respuestas:


24

No tiene que estar en su propio archivo, pero su equipo debe decidir sobre un estándar y cumplirlo.

Además, tiene razón en que "Ir a definición" lo lleva a la interfaz, pero si tiene instalado Resharper , es solo un clic para abrir una lista de clases / interfaces derivadas de esa interfaz, por lo que no es gran cosa. Es por eso que mantengo la interfaz en un archivo separado.

 


55
Estoy de acuerdo con esta respuesta, después de todo, el IDE debería ajustarse a nuestro diseño y no al revés, ¿verdad?
marktani

1
Además, sin Resharper, F12 y Shift + F12 prácticamente hacen lo mismo. Ni siquiera a un clic derecho de distancia :) (para ser justos, solo lo llevará a los usos directos de la interfaz, no estoy seguro de lo que hace la versión Resharper)
Daniel B

@DanielB: en Resharper, Alt-End va a la implementación (o le presenta una lista de posibles implementaciones para ir).
StriplingWarrior

@StriplingWarrior, entonces parece que es exactamente lo que hace Vanilla VS también. F12 = ir a definición, Shift + F12 = ir a implementación (estoy bastante seguro de que esto funciona sin Resharper, aunque lo tengo instalado, por lo que es difícil de confirmar). Es uno de los atajos de navegación más útiles, solo estoy corriendo la voz.
Daniel B

@DanielB: El valor predeterminado para Shift-F12 es "buscar usos". jetbrains.com/resharper/webhelp/…
StriplingWarrior

15

Creo que deberías mantenerlos separados. Como dijiste, la idea es seguir siendo comprobable e intercambiable. Al colocar la interfaz en el mismo archivo que su implementación, está asociando la interfaz con la implementación específica. Si decide crear un objeto simulado u otra implementación, la interfaz no estará separada lógicamente de FooService.


10

Según SOLID, no solo debe crear la interfaz, y no solo debe estar en un archivo diferente, sino que debe estar en un ensamblaje diferente.

¿Por qué? Debido a que cualquier cambio en un archivo fuente que se compila en un ensamblaje requiere la recompilación del ensamblaje, y cualquier cambio en un ensamblaje requiere la recompilación de cualquier ensamblaje dependiente. Entonces, si su objetivo, basado en SOLID, es poder reemplazar una implementación A con una implementación B, mientras que la clase C que depende de la interfaz I no tiene que saber la diferencia, debe asegurarse de que el ensamblaje con I en esto no cambia, protegiendo así los usos.

"Pero es solo una recompilación" Te escucho protestar. Bueno, eso puede ser, pero en la aplicación de su teléfono inteligente, que es más fácil en el ancho de banda de datos de sus usuarios; ¿descargar un binario que cambió o descargar ese binario y otros cinco con código que depende de él? No todos los programas están escritos para ser consumidos por computadoras de escritorio en una LAN. Incluso en ese caso, donde el ancho de banda y la memoria son baratos, las versiones de parches más pequeñas pueden tener valor porque son triviales para enviar a toda la LAN a través de Active Directory o capas de administración de dominio similares; sus usuarios esperarán solo unos segundos para que se aplique la próxima vez que inicien sesión en lugar de unos minutos para que todo se reinstale. Sin mencionar que, cuantos menos ensambles se deben volver a compilar al construir un proyecto, más rápido se construirá,

Ahora, el descargo de responsabilidad: esto no siempre es posible o factible de hacer. La forma más fácil de hacer esto es crear un proyecto centralizado de "interfaces". Esto tiene sus propios inconvenientes; el código se vuelve menos reutilizable porque el proyecto de interfaz Y el proyecto de implementación deben ser referenciados en otras aplicaciones que reutilicen la capa de persistencia u otros componentes clave de su aplicación. Puede superar ese problema dividiendo las interfaces en ensamblajes más estrechamente acoplados, pero luego tiene más proyectos en su aplicación, lo que hace que una compilación completa sea muy dolorosa. La clave es el equilibrio y mantener el diseño débilmente acoplado; por lo general, puede mover los archivos según sea necesario, por lo que cuando vea que una clase necesitará muchos cambios o que se necesitarán nuevas implementaciones de una interfaz regularmente (tal vez para interactuar con versiones de otro software recientemente admitidas,


8

¿Por qué es molesto tener archivos separados? Para mí, es mucho más ordenado y limpio. Es común crear una subcarpeta llamada "Interfaces" y pegar sus archivos IFooServer.cs allí, si desea ver menos archivos en su Explorador de soluciones.

La razón por la que la interfaz se define en su propio archivo es la misma razón por la que las clases se definen generalmente en su propio archivo: la gestión de proyectos es más simple cuando su estructura lógica y estructura de archivos son idénticas, por lo que siempre sabe qué archivo se define una clase determinada in. Esto puede facilitarle la vida al depurar (los rastreos de la pila de excepciones generalmente le dan números de archivo y línea) o al fusionar el código fuente en un repositorio de control de fuente.


6

A menudo es una buena práctica dejar que sus archivos de código contengan una sola clase o una única interfaz. Pero estas prácticas de codificación son un medio para lograr un fin: estructurar mejor su código y facilitar su trabajo. Si a usted y a su equipo les resulta más fácil trabajar si las clases se mantienen junto con sus interfaces, de todas maneras.

Personalmente, prefiero tener la interfaz y la clase en el mismo archivo cuando solo hay una clase que implementa la interfaz, como en su caso.

En cuanto a los problemas que tiene con la navegación, le recomiendo ReSharper . Contiene algunos atajos muy útiles para saltar directamente al método que implementa un método de interfaz específico.


3
+1 por señalar que las prácticas del equipo deben dictar la estructura del archivo y por tener el enfoque contrario a la norma.

3

Hay muchas veces en las que desea que su interfaz esté no solo en un archivo separado para la clase, sino incluso en un ensamblaje separado por completo.

Por ejemplo, una interfaz de contrato de servicio WCF puede ser compartida por el cliente y el servicio si tiene control sobre ambos extremos del cable. Al mover la interfaz a su propio ensamblaje, tendrá menos dependencias de ensamblaje propias. Esto facilita mucho el consumo del cliente, aflojando el acoplamiento con su implementación.


2

Raramente, si alguna vez, tiene sentido tener una única implementación de una interfaz 1 . Si coloca una interfaz pública y una clase pública que implementa esa interfaz en el mismo archivo, es muy probable que no necesite una interfaz.

Cuando la clase que co-ubica con la interfaz es abstracta , y sabe que todas las implementaciones de su interfaz deben heredar esa clase abstracta, tiene sentido ubicar las dos en el mismo archivo. Todavía debe analizar su decisión de usar una interfaz: pregúntese si una clase abstracta por sí sola estaría bien y descarte la interfaz si la respuesta es afirmativa.

Sin embargo, en general, debe apegarse a la estrategia "una clase / interfaz pública corresponde a un archivo": es fácil de seguir y hace que su árbol de origen sea más fácil de navegar.


1 Una excepción notable es cuando necesita tener una interfaz para fines de prueba, porque su elección de marco de imitación impuso este requisito adicional en su código.


3
De hecho, es un patrón bastante normal tener una interfaz para una clase en .NET, ya que permite que las pruebas unitarias sustituyan dependencias con simulacros, stubs, spys u otros dobles de prueba.
Pete

2
@Pete Edité mi respuesta para incluir esto como una nota. No llamaría a este patrón "normal", ya que permite que su preocupación de prueba "se filtre" en su base de código principal. Los marcos de trabajo modernos te ayudan a superar este problema en gran medida, pero tener una interfaz allí definitivamente simplifica mucho las cosas.
dasblinkenlight

2
@KeithS Una clase sin interfaz debe estar en su diseño solo cuando esté completamente seguro de que no tiene ningún sentido subclasificarla, y usted hace esa clase sealed. Si sospecha que en algún momento en el futuro puede agregar una segunda subclase, puede poner una interfaz de inmediato. PD: un asistente de refactorización de calidad decente puede ser suyo a un precio modesto de una sola licencia ReSharper :)
dasblinkenlight

1
@KeithS Y sí, todas sus clases que no están específicamente diseñadas para subclases deben estar selladas. De hecho, las clases de deseo estaban selladas de forma predeterminada, lo que le obliga a designar clases para herencia explícitamente, de la misma manera que el lenguaje actualmente lo obliga a designar funciones para anularlas marcándolas virtual.
dasblinkenlight

2
@KeithS: ¿Qué sucede cuando su única implementación se convierte en dos? Lo mismo que cuando cambia su implementación; cambia el código para reflejar ese cambio. YAGNI aplica aquí. Nunca sabrás qué va a cambiar en el futuro, así que haz lo más simple posible. (Por desgracia, las pruebas se mete con esta máxima)
Groky

2

Las interfaces pertenecen a sus clientes, no a sus implementaciones, como se define en los Principios, prácticas y patrones ágiles de Robert C. Martin. Por lo tanto, combinar la interfaz y la implementación en el mismo lugar es contrario a su principio.

El código del cliente depende de la interfaz. Esto permite la posibilidad de compilar e implementar el código del cliente y la interfaz sin la implementación. De lo que puede tener diferentes implementaciones que funcionan como complementos para el código del cliente.

ACTUALIZACIÓN: Este no es un principio ágil solo aquí. La Banda de los Cuatro en los Patrones de Diseño, en el '94, ya está hablando de clientes que se adhieren a las interfaces y programan a las interfaces. Su punto de vista es similar.


1
El uso de interfaces va mucho más allá del dominio que posee Agile. Agile es una metodología de desarrollo que utiliza construcciones de lenguaje de formas particulares. Una interfaz es una construcción de lenguaje.

1
Sí, pero la interfaz aún pertenece al cliente y no a la implementación, independientemente del hecho de que estemos hablando de ágil o no.
Patkos Csaba

Estoy contigo en esto.
Marjan Venema

0

Personalmente no me gusta el "yo" frente a una interfaz. Como usuario de ese tipo, no quiero ver si se trata de una interfaz o no. El tipo es lo interesante. En términos de dependencias, su FooService es UNA posible implementación de IFooService. La interfaz debe estar cerca del lugar donde se usa. Por otro lado, la implementación debe estar en un lugar donde pueda cambiarse fácilmente sin afectar al cliente. Por lo tanto, preferiría dos archivos, normalmente.


2
Como usuario, quiero saber si un tipo es una interfaz, porque, por ejemplo, significa que no puedo usarlo new, pero por otro lado, puedo usarlo de manera co-o contra-variante. Y Ies una convención de nomenclatura establecida en .Net, por lo que es una buena razón para cumplirla, aunque solo sea para mantener la coherencia con otros tipos.
svick

Comenzar con la Iinterfaz fue solo la introducción a mis argumentos sobre la separación en dos archivos. El código es mejor legible y hace que la inversión de las dependencias sea más clara.
ollins

44
Nos guste o no, usar una 'I' delante del nombre de una interfaz es un estándar de facto en .NET. No seguir el estándar en un proyecto .NET sería, en mi punto de vista, una violación del "principio de mínimo asombro".
Pete

Mi punto principal es: separar en dos archivos. Uno para la abstracción y otro para la implementación. Si el uso de la Ies normal en su entorno: ¡Úselo! (Pero no me gusta :))
ollins

Y en el caso de P.SE, el prefijo I delante de una interfaz hace más evidente que el póster está hablando de una interfaz, no heredando de otra clase.

0

La codificación de interfaces puede ser mala, de hecho se trata como un antipatrón para ciertos contextos y no es una ley. Por lo general, un buen ejemplo de codificación para interfaces anti-patrón es cuando tiene una interfaz que solo tiene una implementación en su aplicación. Otra sería si el archivo de interfaz se vuelve tan molesto que tiene que ocultar su declaración en el archivo de la clase implementada.


tener solo una implementación en su aplicación de una interfaz no es necesariamente algo malo; uno podría estar protegiendo la aplicación contra el cambio tecnológico. Por ejemplo, una interfaz para una tecnología de persistencia de datos. Solo tendría una implementación para la tecnología actual de persistencia de datos mientras protege la aplicación si eso cambiara. Entonces solo habría un diablillo en ese momento.
TSmith

0

Poner una clase y una interfaz en el mismo archivo no limita cómo se puede usar. Una interfaz todavía se puede usar para simulacros, etc. de la misma manera, incluso si está en el mismo archivo que una clase que la implementa. La pregunta podría percibirse simplemente como una cuestión de conveniencia organizacional, en cuyo caso diría que haga lo que sea que le haga la vida más fácil.

Por supuesto, uno podría argumentar que tener los dos en el mismo archivo podría llevar a los incautos a considerar que están más acoplados programáticamente de lo que realmente son, lo cual es un riesgo.

Personalmente, si me encuentro con una base de código que usa una convención sobre la otra, no me preocuparía demasiado de ninguna manera.


No es solo una "conveniencia organizacional" ... también es una cuestión de gestión de riesgos, gestión de implementación, trazabilidad de código, control de cambios, ruido de control de fuente, gestión de seguridad. Hay muchos ángulos para esta pregunta.
TSmith
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.