¿No es el objetivo de una interfaz que múltiples clases se adhieran a un conjunto de reglas e implementaciones?
¿No es el objetivo de una interfaz que múltiples clases se adhieran a un conjunto de reglas e implementaciones?
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.
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 clase BarImpl
, se compromete firmemente a cambiar Foo
cada vez que cambie BarImpl
. Básicamente los estás tratando como una unidad de código que se divide en dos clases.Foo
referencia a la interfaz Bar
, que está en lugar de comprometerse a evitar cambios en Foo
cuando cambia BarImpl
.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 interfaz Bar
, generalmente puede recompilar BarImpl
sin necesidad de recompilar Foo
. En aplicaciones grandes, esto puede ahorrar mucho tiempo.
Foo
depende de la interfaz Bar
, BarImpl
se puede modificar sin tener que volver a compilar Foo
. 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 usando interface 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)
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.
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:
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:
Diseñe bien y coloque interfaces donde se necesiten interfaces.
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 un IginitionSwitch
, SteeringWheel
, GearShift
, GasPedal
, y BrakePedal
. Como este auto contiene un AutomaticTransmission
, no necesitas un ClutchPedal
. (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:
Interface ICabin
Event IgnitionSwitchTurnedOn()
Event IgnitionSwitchTurnedOff()
Event BrakePedalPositionChanged(int percent)
Event GasPedalPositionChanged(int percent)
Event GearShiftGearChanged(int gearNum)
Event SteeringWheelTurned(float degree)
End Interface
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:
El territorio sobre el cual se ejerce el dominio o la autoridad; las posesiones de un soberano o una comunidad, o similares. También se usa figurativamente. [WordNet sentido 2] [1913 Webster]
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 la GasPedal
. 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.
De MSDN :
Las interfaces se adaptan mejor a situaciones en las que sus aplicaciones requieren muchos tipos de objetos posiblemente no relacionados para proporcionar cierta funcionalidad.
Las interfaces son más flexibles que las clases base porque puede definir una única implementación que puede implementar múltiples interfaces.
Las interfaces son mejores en situaciones en las que no necesita heredar la implementación de una clase base.
Las interfaces son útiles en los casos en que no se puede usar la herencia de clases. Por ejemplo, las estructuras no pueden heredar de las clases, pero pueden implementar interfaces.
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.
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.
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.
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.
interface NameChangeListener { // Implemented by a lot of people
void nameChanged(String name);
}
interface NameChangeCount { // Only implemented by my class
int getCount();
}
class NameChangeCounter implements NameChangeListener, NameChangeCount {
...
}
class SomeUserInterface {
private NameChangeCount currentCount; // Will never know that you can change the counter
}
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.
interface SomeRepository { // Guarantee that the external library details won't leak trough
...
}
class OracleSomeRepository implements SomeRepository {
... // Oracle prefix allow us to quickly know what is going on in this class
}
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.
package project.domain;
interface UserRequestSource {
public UserRequest getLastRequest();
}
class UserBehaviorAnalyser {
private UserRequestSource requestSource;
}
package project.ui;
class OrderCompleteDialog extends SomeUIClass implements project.domain.UserRequestSource {
// UI concern, no need for my domain object to know about this method.
public void displayLabelInErrorMode();
// They most certainly need to know about *that* though
public UserRequest getLastRequest();
}
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
interface Sender {
void sendMessage(Message message)
}
class PacketSender implements Sender {
void sendMessage(Message message);
void setPacketSize(int sizeInByte);
}
class Throttler { // This class need to have full access to the object
private PacketSender sender;
public useLowNetworkUsageMode() {
sender.setPacketSize(LOW_PACKET_SIZE);
sender.sendMessage(new NotifyLowNetworkUsageMessage());
... // Other details
}
}
class MailOrder { // Not this one though
private Sender sender;
}
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.
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!
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.
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.
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.