¿Qué significa "programar en una interfaz"?
He visto esto mencionado varias veces y no tengo claro lo que significa. ¿Cuándo y por qué harías esto?
Sé lo que hacen las interfaces, pero el hecho de que no lo tenga claro me hace pensar que me estoy perdiendo la oportunidad de usarlas correctamente.
¿Es así si hicieras:
IInterface classRef = new ObjectWhatever()
¿ Podrías usar cualquier clase que implemente IInterface
? ¿Cuándo necesitarías hacer eso? Lo único que se me ocurre es si tiene un método y no está seguro de qué objeto se pasará excepto su implementación IInterface
. No puedo imaginar con qué frecuencia necesitarías hacer eso.
Además, ¿cómo se podría escribir un método que admita un objeto que implemente una interfaz? ¿Es eso posible?
Aquí hay algunas respuestas maravillosas a estas preguntas que detallan todo tipo de detalles sobre interfaces y código de acoplamiento flexible, inversión de control, etc. Hay algunas discusiones bastante apasionantes, por lo que me gustaría aprovechar la oportunidad para desglosar un poco las cosas para comprender por qué una interfaz es útil.
Cuando comencé a exponerme a las interfaces, yo también estaba confundido acerca de su relevancia. No entendí por qué los necesitabas. Si usamos un lenguaje como Java o C#, ya tenemos herencia y yo veía las interfaces como una forma más débil de herencia y pensé: "¿para qué molestarse?". En cierto sentido tenía razón, se puede pensar en las interfaces como una especie de forma débil de herencia, pero más allá de eso, finalmente entendí su uso como una construcción del lenguaje al pensar en ellas como un medio para clasificar rasgos o comportamientos comunes que eran exhibidos por potencialmente muchas clases de objetos no relacionados.
Por ejemplo, digamos que tienes un juego SIM y tienes las siguientes clases:
class HouseFly inherits Insect {
void FlyAroundYourHead(){}
void LandOnThings(){}
}
class Telemarketer inherits Person {
void CallDuringDinner(){}
void ContinueTalkingWhenYouSayNo(){}
}
Es evidente que estos dos objetos no tienen nada en común en términos de herencia directa. Pero se podría decir que ambos son molestos.
Digamos que nuestro juego necesita tener algún tipo de elemento aleatorio que moleste al jugador cuando cena. Esto podría ser uno HouseFly
o Telemarketer
ambos, pero ¿cómo se pueden permitir ambos con una sola función? ¿Y cómo le pides a cada tipo diferente de objeto que "haga lo que molesta" de la misma manera?
La clave a tener en cuenta es que tanto a Telemarketer
como HouseFly
comparten un comportamiento común interpretado de manera vaga, aunque no se parecen en nada en términos de modelarlos. Entonces, creemos una interfaz que ambos puedan implementar:
interface IPest {
void BeAnnoying();
}
class HouseFly inherits Insect implements IPest {
void FlyAroundYourHead(){}
void LandOnThings(){}
void BeAnnoying() {
FlyAroundYourHead();
LandOnThings();
}
}
class Telemarketer inherits Person implements IPest {
void CallDuringDinner(){}
void ContinueTalkingWhenYouSayNo(){}
void BeAnnoying() {
CallDuringDinner();
ContinueTalkingWhenYouSayNo();
}
}
Ahora tenemos dos clases, cada una de las cuales puede resultar molesta a su manera. Y no necesitan derivar de la misma clase base y compartir características inherentes comunes; simplemente necesitan satisfacer el contrato de IPest
... ese contrato es simple. Sólo tienes que BeAnnoying
. En este sentido, podemos modelar lo siguiente:
class DiningRoom {
DiningRoom(Person[] diningPeople, IPest[] pests) { ... }
void ServeDinner() {
when diningPeople are eating,
foreach pest in pests
pest.BeAnnoying();
}
}
Aquí tenemos un comedor que acepta varios comensales y varias plagas; observe el uso de la interfaz. Esto significa que en nuestro pequeño mundo, un miembro de la pests
matriz podría ser en realidad un Telemarketer
objeto o un HouseFly
objeto.
El ServeDinner
método se llama cuando se sirve la cena y se supone que nuestra gente en el comedor debe comer. En nuestro pequeño juego, es entonces cuando nuestras plagas hacen su trabajo: cada plaga recibe instrucciones de ser molesta a través de la IPest
interfaz. De esta manera, podemos fácilmente tener ambos Telemarketers
y HouseFlys
ser molestos en cada una de sus formas: sólo nos importa que tengamos algo en el DiningRoom
objeto que sea una plaga, realmente no nos importa qué es y ellos no podrían tener nada en común con otros.
Este ejemplo de pseudocódigo muy artificial (que se prolongó mucho más de lo que anticipé) simplemente pretende ilustrar el tipo de cosas que finalmente me encendieron la luz en términos de cuándo podríamos usar una interfaz. Pido disculpas de antemano por la tontería del ejemplo, pero espero que ayude a su comprensión. Y, sin duda, las otras respuestas publicadas que ha recibido aquí realmente cubren toda la gama del uso actual de interfaces en patrones de diseño y metodologías de desarrollo.
El ejemplo específico que solía dar a los estudiantes es que deberían escribir
List myList = new ArrayList(); // programming to the List interface
en lugar de
ArrayList myList = new ArrayList(); // this is bad
Estos se ven exactamente iguales en un programa corto, pero si los usas myList
100 veces en tu programa puedes comenzar a ver una diferencia. La primera declaración garantiza que solo llame a métodos myList
definidos por la List
interfaz (por lo que no hay ArrayList
métodos específicos). Si ha programado la interfaz de esta manera, más adelante podrá decidir que realmente necesita
List myList = new TreeList();
y solo tienes que cambiar tu código en ese lugar. Ya sabes que el resto de tu código no hace nada que se pueda estropear al cambiar la implementación porque lo programaste en la interfaz .
Los beneficios son aún más obvios (creo) cuando se habla de parámetros de método y valores de retorno. Tomemos esto por ejemplo:
public ArrayList doSomething(HashMap map);
Esa declaración de método lo vincula a dos implementaciones concretas ( ArrayList
y HashMap
). Tan pronto como se llame a ese método desde otro código, cualquier cambio en esos tipos probablemente signifique que también tendrá que cambiar el código de llamada. Sería mejor programar las interfaces.
public List doSomething(Map map);
Ahora no importa qué tipo List
devuelve o qué tipo Map
se pasa como parámetro. Los cambios que realice dentro del doSomething
método no lo obligarán a cambiar el código de llamada.
Programar en una interfaz es decir: "Necesito esta funcionalidad y no me importa de dónde viene".
Considere (en Java), la List
interfaz versus las ArrayList
clases LinkedList
concretas. Si lo único que me importa es tener una estructura de datos que contiene múltiples elementos de datos a los que debo acceder mediante iteración, elegiría uno List
(y eso es el 99% de las veces). Si sé que necesito insertar/eliminar en tiempo constante desde cualquier extremo de la lista, podría elegir la LinkedList
implementación concreta (o más probablemente, usar la interfaz de cola ). Si sé que necesito acceso aleatorio por índice, elegiría la ArrayList
clase concreta.