¿No es el objetivo de una interfaz que múltiples clases se adhieran a un conjunto de reglas e implementaciones?
design-patterns
coding-standards
interfaces
Lamin Sanneh
fuente
fuente
Respuestas:
Estrictamente hablando, no, no, YAGNI aplica. Dicho esto, el tiempo que pasará creando la interfaz es mínimo, especialmente si tiene una práctica herramienta de generación de código que hace la mayor parte del trabajo por usted. Si no está seguro de si va a necesitar la interfaz o no, diría que es mejor equivocarse para apoyar la definición de una interfaz.
Además, el uso de una interfaz incluso para una sola clase le proporcionará otra implementación simulada para pruebas unitarias, una que no está en producción. La respuesta de Avner Shahar-Kashtan se expande sobre este punto.
fuente
Yo respondería que si necesita una interfaz o no, no depende de cuántas clases la implementen. Las interfaces son una herramienta para definir contratos entre múltiples subsistemas de su aplicación; Entonces, lo que realmente importa es cómo se divide su aplicación en subsistemas. Debe haber interfaces como el front-end para subsistemas encapsulados, sin importar cuántas clases los implementen.
Aquí hay una regla práctica muy útil:
Foo
refiera directamente a la claseBarImpl
, se compromete firmemente a cambiarFoo
cada vez que cambieBarImpl
. Básicamente los estás tratando como una unidad de código que se divide en dos clases.Foo
referencia a la interfazBar
, que está en lugar de comprometerse a evitar cambios enFoo
cuando cambiaBarImpl
.Si define interfaces en los puntos clave de su aplicación, piensa detenidamente en los métodos que deberían admitir y cuáles no, y comenta las interfaces claramente para describir cómo se supone que debe comportarse una implementación (y cómo no), la aplicación va a ser mucho más fácil de entender porque estas interfaces comentadas proporcionarán una especie de especificación de la aplicación de una descripción de cómo se pretende comportarse. Esto hace que sea mucho más fácil leer el código (en lugar de preguntar "qué diablos se supone que debe hacer este código", puede preguntar "cómo hace este código lo que se supone que debe hacer").
Además de todo esto (o en realidad por eso), las interfaces promueven una compilación separada. Dado que las interfaces son triviales para compilar y tienen menos dependencias que sus implementaciones, significa que si escribe clase
Foo
para usar una interfazBar
, generalmente puede recompilarBarImpl
sin necesidad de recompilarFoo
. En aplicaciones grandes, esto puede ahorrar mucho tiempo.fuente
Foo
depende de la interfazBar
,BarImpl
se puede modificar sin tener que volver a compilarFoo
. 4. Control de acceso más detallado que las ofertas públicas / privadas (exponga la misma clase a dos clientes a través de diferentes interfaces).If you make class Foo refer directly to class BarImpl, you're strongly committing yourself to change Foo every time you change BarImpl
Al cambiar BarImpl, ¿cuáles son los cambios que se pueden evitar en Foo usandointerface Bar
? Dado que las firmas y la funcionalidad de un método no cambian en BarImpl, Foo no requerirá cambios incluso sin la interfaz (todo el propósito de la interfaz). Estoy hablando del escenario en el que solo una clase, es decir,BarImpl
implementa Bar. Para el escenario de varias clases, entiendo el principio de inversión de dependencia y cómo es útil su interfaz.Las interfaces se designan para definir un comportamiento, es decir, un conjunto de prototipos de funciones / métodos. Los tipos que implementan la interfaz implementarán ese comportamiento, por lo que cuando se trata con un tipo de este tipo, usted sabe (en parte) qué comportamiento tiene.
No es necesario definir una interfaz si sabe que el comportamiento definido por ella se usará solo una vez. BESO (mantenlo simple, estúpido)
fuente
Si bien, en teoría, no debería tener una interfaz solo por tener una interfaz, la respuesta de Yannis Rizos sugiere otras complicaciones:
Cuando escribe pruebas unitarias y utiliza marcos simulados como Moq o FakeItEasy (por nombrar los dos más recientes que he usado), está creando implícitamente otra clase que implementa la interfaz. Buscar el código o el análisis estático podría afirmar que solo hay una implementación, pero de hecho está la implementación simulada interna. Cada vez que comience a escribir simulacros, encontrará que extraer interfaces tiene sentido.
Pero espera hay mas. Hay más escenarios donde hay implementaciones de interfaz implícitas. El uso de la pila de comunicación WCF de .NET, por ejemplo, genera un proxy para un servicio remoto, que, nuevamente, implementa la interfaz.
En un entorno de código limpio, estoy de acuerdo con el resto de las respuestas aquí. Sin embargo, preste atención a los marcos, patrones o dependencias que tenga que puedan hacer uso de interfaces.
fuente
No, no los necesita, y considero que es un antipatrón crear interfaces automáticamente para cada referencia de clase.
Hacer un Foo / FooImpl para todo tiene un costo real. El IDE puede crear la interfaz / implementación de forma gratuita, pero cuando navega por el código, tiene la carga cognitiva adicional de F3 / F12 al
foo.doSomething()
llevarlo a la firma de la interfaz y no a la implementación real que desea. Además, tiene el desorden de dos archivos en lugar de uno para todo.Por lo tanto, solo debe hacerlo cuando realmente lo necesite para algo.
Ahora abordando los contraargumentos:
Necesito interfaces para marcos de inyección de dependencia
Las interfaces para soportar marcos son heredadas. En Java, las interfaces solían ser un requisito para los proxies dinámicos, pre-CGLIB. Hoy, generalmente no lo necesitas. Se considera progreso y una bendición para la productividad del desarrollador que ya no los necesite en EJB3, Spring, etc.
Necesito simulacros para pruebas unitarias
Si escribe sus propios simulacros y tiene dos implementaciones reales, entonces una interfaz es apropiada. Probablemente no tendríamos esta discusión en primer lugar si su base de código tuviera un FooImpl y un TestFoo.
Pero si está utilizando un marco de imitación como Moq, EasyMock o Mockito, puede simular clases y no necesita interfaces. Es análogo a la configuración
foo.method = mockImplementation
en un lenguaje dinámico donde se pueden asignar métodos.Necesitamos interfaces para seguir el principio de inversión de dependencia (DIP)
DIP dice que usted construye para depender de contratos (interfaces) y no de implementaciones. Pero una clase ya es un contrato y una abstracción. Para eso están las palabras clave públicas / privadas. En la universidad, el ejemplo canónico era algo así como una clase Matrix o Polinómica: los consumidores tienen una API pública para crear matrices, agregarlas, etc., pero no se les permite preocuparse si la matriz se implementa en forma dispersa o densa. No se requería IMatrix o MatrixImpl para probar ese punto.
Además, DIP a menudo se aplica en exceso en cada nivel de llamada de clase / método, no solo en los límites de los módulos principales. Una señal de que está aplicando DIP en exceso es que su interfaz e implementación cambian en el paso de bloqueo, de modo que tiene que tocar dos archivos para hacer un cambio en lugar de uno. Si DIP se aplica adecuadamente, significa que su interfaz no debería cambiar con frecuencia. Además, otra señal es que su interfaz solo tiene un consumidor real (su propia aplicación). Historia diferente si está creando una biblioteca de clase para el consumo en muchas aplicaciones diferentes.
Este es un corolario al punto del tío Bob Martin sobre burlarse: solo debería burlarse de los principales límites arquitectónicos. En una aplicación web, el acceso HTTP y DB son los límites principales. Todas las llamadas de clase / método intermedias no lo son. Lo mismo va para DIP.
Ver también:
fuente
new Mockup<YourClass>() {}
y toda la clase, incluidos sus constructores, se burlan, ya sea una interfaz, una clase abstracta o una clase concreta. También puede "anular" el comportamiento del constructor si lo desea. Supongo que hay formas equivalentes en Mockito o Powermock.Parece que las respuestas en ambos lados de la cerca se pueden resumir en esto:
Como señalé en mi respuesta a la respuesta de Yanni , no creo que puedas tener una regla dura y rápida sobre las interfaces. La regla debe ser, por definición, flexible. Mi regla sobre las interfaces es que se debe usar una interfaz en cualquier lugar donde esté creando una API. Y se debe crear una API en cualquier lugar que esté cruzando el límite de un dominio de responsabilidad a otro.
Para un ejemplo (terriblemente ideado), digamos que estás construyendo una
Car
clase. En su clase, ciertamente necesitará una capa de interfaz de usuario. En este ejemplo particular, se toma la forma de unIginitionSwitch
,SteeringWheel
,GearShift
,GasPedal
, yBrakePedal
. Como este auto contiene unAutomaticTransmission
, no necesitas unClutchPedal
. (Y como este es un automóvil terrible, no hay aire acondicionado, radio o asiento. De hecho, también faltan las tablas del piso, ¡solo tienes que agarrarte al volante y esperar lo mejor!)Entonces, ¿cuál de estas clases necesita una interfaz? La respuesta podría ser todas ellas, o ninguna, dependiendo de su diseño.
Podría tener una interfaz similar a esta:
En ese punto, el comportamiento de esas clases se convierte en parte de la interfaz / API ICabin. En este ejemplo, las clases (si hay algunas) son probablemente simples, con algunas propiedades y una función o dos. Y lo que está afirmando implícitamente con su diseño es que estas clases existen únicamente para apoyar cualquier implementación concreta de ICabin que tenga, y no pueden existir por sí mismas o no tienen sentido fuera del contexto de ICabin.
Es la misma razón por la que no realiza una prueba unitaria de los miembros privados: existen solo para admitir la API pública y, por lo tanto, su comportamiento debe probarse probando la API.
Entonces, si su clase existe únicamente para admitir otra clase, y conceptualmente la ve como que realmente no tiene su propio dominio, entonces está bien omitir la interfaz. Pero si su clase es lo suficientemente importante como para considerar que ha crecido lo suficiente como para tener su propio dominio, entonces continúe y dele una interfaz.
EDITAR:
Con frecuencia (incluso en esta respuesta) leerá cosas como 'dominio', 'dependencia' (frecuentemente junto con 'inyección') que no significan nada para usted cuando comienza a programar (seguro que no significa algo para mí). Para dominio, significa exactamente cómo suena:
En los términos de mi ejemplo, consideremos el
IgnitionSwitch
. En un automóvil con espacio para carnes, el interruptor de encendido es responsable de:Esas propiedades conforman el dominio de
IgnitionSwitch
, o en otras palabras, de lo que sabe y es responsable.El
IgnitionSwitch
no es responsable de laGasPedal
. El interruptor de encendido ignora completamente el pedal del acelerador en todos los sentidos. Ambos operan de manera completamente independiente el uno del otro (¡aunque un automóvil sería bastante inútil sin ambos!).Como dije originalmente, depende de su diseño. Puede diseñar un
IgnitionSwitch
que tenga dos valores: On (True
) y Off (False
). O puede diseñarlo para autenticar la clave provista por él y una gran cantidad de otras acciones. Esa es la parte difícil de ser desarrollador: decidir dónde dibujar las líneas en la arena, y sinceramente, la mayoría de las veces es completamente relativo. Sin embargo, esas líneas en la arena son importantes: ahí es donde está su API y, por lo tanto, donde deberían estar sus interfaces.fuente
No (YAGNI) , a menos que esté planeando escribir pruebas para otras clases usando esta interfaz, y esas pruebas se beneficiarían de burlarse de la interfaz.
fuente
De MSDN :
En general, en el caso de una sola clase, no será necesario implementar una interfaz, pero teniendo en cuenta el futuro de su proyecto, podría ser útil definir formalmente el comportamiento necesario de las clases.
fuente
Para responder a la pregunta: hay más que eso.
Un aspecto importante de una interfaz es la intención.
Una interfaz es "un tipo abstracto que no contiene datos, pero expone comportamientos" - Interfaz (informática) Entonces, si este es un comportamiento, o un conjunto de comportamientos, que la clase admite, es probable que una interfaz sea el patrón correcto. Sin embargo, si el (los) comportamiento (s) es (n) intrínseco al concepto incorporado por la clase, entonces es probable que no desee una interfaz en absoluto.
La primera pregunta que debe hacerse es cuál es la naturaleza de la cosa o proceso que está tratando de representar. Luego continúe con las razones prácticas para implementar esa naturaleza de una manera determinada.
fuente
Desde que hizo esta pregunta, supongo que ya ha visto los intereses de tener una interfaz que oculte múltiples implementaciones. Esto puede manifestarse por el principio de inversión de dependencia.
Sin embargo, la necesidad de tener una interfaz o no no depende del número de implementaciones. La función real de una interfaz es que define un contrato que establece qué servicio se debe proporcionar en lugar de cómo se debe implementar.
Una vez definido el contrato, dos o más equipos pueden trabajar de forma independiente. Digamos que está trabajando en un módulo A y depende del módulo B, el hecho de crear una interfaz en B le permite continuar su trabajo sin preocuparse por la implementación de B porque la interfaz oculta todos los detalles. Por lo tanto, la programación distribuida se hace posible.
Aunque el módulo B solo tiene una implementación de su interfaz, la interfaz sigue siendo necesaria.
En conclusión, una interfaz oculta los detalles de implementación de sus usuarios. La programación para la interfaz ayuda a escribir más documentos porque se debe definir el contrato, escribir más software modular, promover pruebas unitarias y acelerar la velocidad de desarrollo.
fuente
Todas las respuestas aquí son muy buenas. De hecho, la mayoría de las veces no necesita implementar una interfaz diferente. Pero hay casos en los que es posible que desee hacerlo de todos modos. Aquí hay un caso donde lo hago:
La clase implementa otra interfaz que no quiero exponer
Happen a menudo con la clase de adaptador que une el código de terceros.
La clase utiliza una tecnología específica que no debería filtrarse
principalmente cuando interactúa con bibliotecas externas. Incluso si solo hay una implementación, uso una interfaz para asegurarme de no introducir un acoplamiento innecesario con la biblioteca externa.
Comunicación entre capas
Incluso si solo una clase de IU implementa alguna vez una de las clases de dominio, permite una mejor separación entre esas capas y, lo más importante, evita la dependencia cíclica.
Solo un subconjunto del método debe estar disponible para la mayoría de los objetos.
Principalmente sucede cuando tengo algún método de configuración en la clase concreta
Así que al final uso la interfaz por la misma razón que uso el campo privado: otro objeto no debería tener acceso a cosas a las que no debería acceder. Si tengo un caso así, presento una interfaz incluso si solo una clase lo implementa.
fuente
Las interfaces son realmente importantes, pero intenta controlar la cantidad de ellas que tienes.
Después de haber creado interfaces para casi todo, es fácil terminar con un código de "espagueti cortado". Me refiero a la mayor sabiduría de Ayende Rahien, quien ha publicado algunas palabras muy sabias sobre el tema:
http://ayende.com/blog/153889/limit-your-abstraction-analyzing-a-ddd-application
Esta es su primera publicación de toda una serie, ¡así que sigue leyendo!
fuente
Una razón por la que aún puede querer introducir una interfaz en este caso es seguir el Principio de Inversión de Dependencia . Es decir, el módulo que utiliza la clase dependerá de una abstracción de la misma (es decir, la interfaz) en lugar de depender de una implementación concreta. Desacopla los componentes de alto nivel de los componentes de bajo nivel.
fuente
No hay una razón real para hacer nada. Las interfaces son para ayudarlo y no al programa de salida. Entonces, incluso si la interfaz es implementada por un millón de clases, no hay una regla que diga que tienes que crear una. Usted crea uno para que cuando usted, o cualquier otra persona que use su código, quiera cambiar algo que se filtre en todas las implementaciones. La creación de una interfaz lo ayudará en todos los casos futuros en los que desee crear otra clase que la implemente.
fuente
No siempre es necesario definir una interfaz para una clase.
Los objetos simples como los objetos de valor no tienen implementaciones múltiples. Tampoco necesitan que se burlen de ellos. La implementación se puede probar por sí misma y cuando se prueban otras clases que dependen de ellas, se puede usar el objeto de valor real.
Recuerde que crear una interfaz tiene un costo. Debe actualizarse a lo largo de la implementación, necesita un archivo adicional y algunos IDE tendrán problemas para ampliar la implementación, no la interfaz.
Por lo tanto, definiría interfaces solo para clases de nivel superior, donde desea una abstracción de la implementación.
Tenga en cuenta que con una clase obtiene una interfaz de forma gratuita. Además de la implementación, una clase define una interfaz a partir del conjunto de métodos públicos. Esa interfaz es implementada por todas las clases derivadas. No se trata estrictamente de una interfaz, pero se puede usar exactamente de la misma manera. Así que no creo que sea necesario recrear una interfaz que ya existe bajo el nombre de la clase.
fuente