¿El objetivo de una interfaz no es que varias clases se adhieran a un conjunto de reglas e implementaciones?
¿El objetivo de una interfaz no es que varias clases se adhieran a un conjunto de reglas e implementaciones?
Estrictamente hablando, no, no lo hace, se aplica YAGNI . Dicho esto, el tiempo que pasará creando la interfaz es mínimo, especialmente si tiene una herramienta de generación de código útil que realiza la mayor parte del trabajo por usted. Si no está seguro de si va a necesitar la interfaz de o no, yo diría que es mejor errar 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 en este punto.
Yo respondería que si necesita una interfaz o no no depende de cuántas clases la implementarán. Las interfaces son una herramienta para definir contratos entre varios subsistemas de su aplicación; Entonces, lo que realmente importa es cómo su aplicación se divide en subsistemas. Debe haber interfaces como el extremo frontal de los subsistemas encapsulados, sin importar cuántas clases los implementen.
Aquí hay una regla de oro muy útil:
Foo
se refiera directamente a la clase BarImpl
, te comprometes a cambiar Foo
cada vez que cambies BarImpl
. Básicamente, los trata como una unidad de código que se divide en dos clases. Foo
, refiérase a la interfaz Bar
, en su lugar, se compromete a evitar los cambios a Foo
cuando cambie BarImpl
. Si define interfaces en los puntos clave de su aplicación, presta especial atención a los métodos que deben admitir y cuáles no, y comenta las interfaces claramente para describir cómo se supone que se comporte una implementación (y cómo no), su aplicación será mucho más fácil de entender porque estas interfaces comentadas proporcionarán una especie de especificación de la aplicación, una descripción de cómo 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 a causa de ello), las interfaces promueven la compilación por separado. Como las interfaces son triviales para compilar y tienen menos dependencias que sus implementaciones, significa que si escribe la clase Foo
para usar una interfaz Bar
, generalmente puede recompilar BarImpl
sin necesidad de recompilar Foo
. En aplicaciones grandes, esto puede ahorrar mucho tiempo.
Las interfaces están diseñadas para definir un comportamiento, es decir, un conjunto de prototipos de funciones / métodos. Los tipos que implementan la interfaz implementarán ese comportamiento, de modo que cuando se trate de un tipo de este tipo, sabrá (en parte) qué comportamiento tiene.
No es necesario definir una interfaz si sabe que el comportamiento definido por ella se utilizará solo una vez. KISS (mantenlo simple, estúpido)
Aunque en teoría no deberías tener una interfaz solo por tener una interfaz, La respuesta de Yannis Rizos insinúa nuevas complicaciones:
Cuando escribes pruebas unitarias y usas marcos simulados como Moq o FakeItEasy (para nombrar los dos más recientes que he usado), estás creando implícitamente otra clase que implementa la interfaz. Al buscar el código o el análisis estático, se puede afirmar que solo hay una implementación, pero de hecho existe la implementación simulada interna. Siempre que comience a escribir simulacros, encontrará que extraer interfaces tiene sentido.
Pero espera, hay más. 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, de nuevo, 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 utilizar interfaces.
Parece que las respuestas en ambos lados de la cerca se pueden resumir en esto:
Diseñe bien y coloque interfaces donde se necesitan interfaces.
Como señalé en mi respuesta a la respuesta de Yanni , no creo que puedas tener problemas. y la regla rápida sobre las interfaces. La regla debe ser, por definición, flexible. Mi regla en las interfaces es que una interfaz debe usarse en cualquier lugar donde esté creando una API. Y debe crearse una API en cualquier lugar en el que cruce el límite de un dominio de responsabilidad a otro.
Para (un ejemplo horrible), digamos que estás construyendo una clase Car
. En tu clase, ciertamente necesitarás una capa de UI. En este ejemplo en particular, toma la forma de IginitionSwitch
, SteeringWheel
, GearShift
, GasPedal
y BrakePedal
. Como este auto contiene un AutomaticTransmission
, no necesitas un ClutchPedal
. (Y dado que este es un automóvil terrible, no hay aire acondicionado, radio o asiento. De hecho, también faltan las tablas del suelo, ¡solo tienes que aferrarte a ese volante y esperar lo mejor!)
Entonces, ¿cuál de estas clases necesita una interfaz? La respuesta podría ser todas o ninguna de ellas, dependiendo de su diseño.
Podrías tener una interfaz como 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 momento, el comportamiento de esas clases se convierte en parte de la Interfaz / API de 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á indicando 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 prueba a los miembros privados: existen solo para admitir la API pública y, por lo tanto, su comportamiento se debe probar mediante la prueba de la API.
Entonces, si su clase existe únicamente para admitir a otra clase, y conceptualmente la ve como si no fuera realmente teniendo su propio dominio, entonces está bien omitir la interfaz. Pero si tu clase es lo suficientemente importante como para que consideres que ha crecido lo suficiente como para tener su propio dominio, entonces dale una interfaz.
EDIT:
Con frecuencia (incluso en esta respuesta) leerás cosas como 'dominio', 'dependencia' (frecuentemente asociada con 'inyección') que no significan nada para ti cuando empiezas a programar (seguro no significaba nada para mi). Para el dominio, significa exactamente lo que suena:
El territorio sobre el que se ejerce el dominio o la autoridad; las posesiones de un soberano o mancomunidad, o la me gusta. También se usa figurativamente. [WordNet sentido 2] [1913 Webster]
En los términos de mi ejemplo, consideremos el IgnitionSwitch
. En un automóvil de Meatspace, el interruptor de encendido es responsable de:
Esas propiedades forman el dominio del IgnitionSwitch
, o en otras palabras, de lo que sabe y es responsable.
El IgnitionSwitch
no es responsable del GasPedal
. El interruptor de encendido ignora por completo el acelerador en todos los sentidos. Ambos operan completamente independientes el uno del otro (¡aunque un auto sería bastante inútil sin los dos!).
Como dije originalmente, depende de tu diseño. Podría diseñar un IgnitionSwitch
que tuviera dos valores: On ( True
) y Off ( False
). O bien, podría diseñarlo para autenticar la clave que se le proporcionó y una serie de otras acciones. Esa es la parte difícil de ser un desarrollador: decidir dónde dibujar las líneas en la arena, y honestamente, 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, dónde deberían estar sus interfaces.
No, no los necesita, y considero que es un antipatrón para crear interfaces automáticamente para cada referencia de clase.
Hay un costo real para hacer Foo / FooImpl para todo. 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 en foo.doSomething()
que lo lleva a la firma de la interfaz y no a la implementación real que desea. Además, tienes el desorden de dos archivos en lugar de uno para todo.
Por lo tanto, solo debes hacerlo cuando realmente lo necesites para algo.
Ahora abordando los contra-argumentos:
Necesito interfaces para los marcos de inyección de dependencia
Las interfaces para soportar marcos son heredadas. En Java, las interfaces solían ser un requisito para proxies dinámicos, pre-CGLIB. Hoy, usualmente no lo necesitas. Se considera un progreso y una gran ventaja para la productividad del desarrollador que ya no los necesita en EJB3, Spring, etc.
Necesito simulacros para realizar 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 tanto un FooImpl como un TestFoo.
Pero si estás usando un marco de burla como Moq, EasyMock o Mockito, puedes simular clases y no necesitas interfaces. Es análogo a configurar 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 implementaciones. Pero una clase es ya un contrato y una abstracción. Para eso son las palabras clave públicas / privadas. En la universidad, el ejemplo canónico era algo así como una clase Matrix o Polinomial: 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 requirió IMatrix o MatrixImpl para probar ese punto.
Además, el 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á sobre-aplicando DIP es que su interfaz e implementación cambian en el paso de bloqueo, de modo que tiene que tocar dos archivos para realizar un cambio en lugar de uno. Si el DIP se aplica de manera adecuada, significa que su interfaz no debería cambiar a menudo. Además, otra señal es que su interfaz solo tiene un consumidor real (su propia aplicación). Una historia diferente si estás construyendo una biblioteca de clases para el consumo en muchas aplicaciones diferentes.
Esto es un corolario del punto de burla del tío Bob Martin: solo deberías burlarte de los límites arquitectónicos principales. En una aplicación web, los accesos HTTP y DB son los límites principales. Todas las llamadas de clase / método en el medio no lo son. Lo mismo ocurre con DIP.
Ver también:
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 implementación única que puede implementar múltiples interfaces.
Las interfaces son mejores en situaciones en las que no es necesario heredar la implementación de una clase base.
Las interfaces son útiles en los casos en que no se puede usar la herencia de clase. 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 considerando 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 (computación) Entonces, si este es un comportamiento, o un conjunto de comportamientos, que admite la clase, lo más probable es que una interfaz sea el patrón correcto. Sin embargo, si los comportamientos son intrínsecos al concepto incorporado por la clase, es probable que no desee una interfaz en absoluto.
La primera pregunta que se debe hacer es cuál es la naturaleza de la cosa o el proceso que intenta representar. Luego continúe con las razones prácticas para implementar esa naturaleza de una manera determinada.
Desde que hiciste esta pregunta, supongo que ya has visto los intereses de tener una interfaz que oculta múltiples implementaciones. Esto puede ser manifestado 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 debe proporcionarse en lugar de cómo debe implementarse.
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. Así, 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. Programar a la interfaz ayuda a escribir más documentos porque se debe definir un 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 puede querer hacerlo de todos modos. Aquí hay un caso donde lo hago:
La clase implementa otra interfaz que no quiero exponer
Ocurre a menudo con la clase de adaptador que puentea 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 debe filtrarse
Sobre todo al interactuar con bibliotecas externas. Incluso si solo hay una implementación, uso una interfaz para garantizar que no introduzco 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 UI implementa alguna vez una de las clases de dominio, permite una mejor separación entre esas capas y, lo que es 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 debería 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;
}
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 como ese, introduzco una interfaz incluso si solo la clase una la implementa.
Las interfaces son muy importantes, pero intenta controlar el número de ellas que tienes.
Habiendo recorrido el camino de crear interfaces para casi todo, es fácil terminar con el código de 'espagueti picado'. Me refiero a la mayor sabiduría de Ayende Rahien, que ha publicado algunas palabras muy sabias sobre el tema:
¡Este es el primer post de una serie completa, así que sigue leyendo!
Una de las razones por las que aún es posible que desee 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 ninguna razón real para hacer nada. Las interfaces son para ayudarle y no el programa de salida. Por lo tanto, incluso si la Interfaz está implementada por un millón de clases, no hay ninguna regla que indique que debe 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 lo implemente.
No siempre es necesario definir una interfaz para una clase.
Los objetos simples como los objetos de valor no tienen implementaciones múltiples. Ellos tampoco necesitan ser burlados. 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 hacer zoom en la implementación, no en la interfaz.
Por lo tanto, definiría interfaces solo para las clases de nivel superior, donde desea una abstracción de la implementación.
Tenga en cuenta que con una clase obtendrá una interfaz de forma gratuita. Además de la implementación, una clase define una interfaz del conjunto de métodos públicos. Esa interfaz es implementada por todas las clases derivadas. No es estrictamente una interfaz, pero se puede utilizar exactamente de la misma manera. Así que no creo que sea necesario recrear una interfaz que ya existe bajo el nombre de la clase.
Lea otras preguntas en las etiquetas interfaces coding-standards design-patterns