Las interfaces son una abstracción estupenda que nos ofrecen la mayor parte de los lenguajes de programación orientados a objetos. Básicamente nos permiten definir un "contrato" sobre el que podemos estar seguros de que, las clases que las implementen, lo van a cumplir.
Por ejemplo, podemos definir una interfaz muy simple de esta manera que nos servirá para crear clases que representen a animales que vuelan:
public interface IVolador
{
void Volar();
}
De este modo estamos diciendo que si una clase la implementa tiene necesariamente que tener un método llamado Volar
con la firma del que hemos puesto ahí, es decir, que no devolverá nada ni tomará parámetro alguno. Internamente cada clase lo implementará de una forma diferente.
Así, por ejemplo, podemos definir una clase Aguila
y otra Abeja
que implementen esta interfaz. En ambos casos el "bicho" volará, pero lo hará de forma diferente. Si fuese un juego, por ejemplo, el águila movería las alas despacio y se desplazaría a toda velocidad, mientras que la abeja las movería muy rápido y se trasladaría de manera errática en distancias cortas.
Cuando la aplicamos a la definición de una clase tenemos dos maneras de implementar una interfaz: de manera explícita o de manera implícita. De hecho, el propio Visual Studio nos ofrece ambas posibilidades en cuanto la añadimos a la clase:
Así, si por ejemplo quisiésemos implementar la interfaz en la clase Abeja
, la manera explícita sería esta:
public class Abeja : Animal, IVolador
{
//Miembros propios de la clase Abeja
//....
//
//Implementación explícita de la interfaz
IVolador.Volar()
{
//Código que haga lo que sea para esta clase en particular
}
}
La forma implícita de implementarla es muy similar:
public class Abeja : Animal, IVolador
{
//Miembros propios de la clase Abeja
//....
//
//Implementación implícita de la interfaz
public Volar()
{
//Código que haga lo que sea para esta clase en particular
}
}
El código es el mismo en ambos casos. Las únicas diferencias son dos:
- En el caso de la implementación explícita es necesario indicar el nombre de la interfaz delante del nombre del método:
IVolador.Volar()
, precisamente de ahí viene el nombre, puesto que estamos indicando explícitamente que ese método es el que cumple el contrato establecido por la interfaz.
- En el caso de la implementación implícita necesitamos indicar que el método es público, ya que si se cumple la interfaz, éste debe ser accesible desde el exterior de la clase con el mismo nivel de acceso que la propia clase. Fíjate que en el primer fragmento no se indica nada.
Pero en ambos casos el resultado es el mismo: la interfaz queda implementada y el método Volar
funcionará igual en ambos casos.
Entonces ¿por qué existen dos maneras de hacer lo mismo?
En realidad sí que existe una gran diferencia entre una forma y la otra de hacer lo mismo, solo que no nos daremos cuenta hasta que lo necesitemos. La diferencia estriba en que si definimos la interfaz explícitamente, solo podremos acceder a la funcionalidad de dicha interfaz cuando la estemos tratando como un objeto de dicha interfaz.
En el ejemplo anterior, mira qué nos enseña el compilador si intentamos este código:
Abeja maya = new Abeja();
maya.Volar();
Fíjate en cómo, a pesar de que el método existe y está implementado en la clase, el compilador nos dice que no existe ningún método Volar
y que deberíamos implementarlo. WTF?
El motivo es, como decía antes, que cuando implementamos una interfaz de manera explícita solo se va a ver si usamos el objeto conformado con esa interfaz. Esto implica hacer una conversión (o cast), o bien usar el operador as
. O sea, así:
Abeja maya = new Abeja();
IVolador mayavoladora = maya as IVolador;
mayavoladora.Volar();
En donde utilizamos el operador as
para conformar la variable maya a la interfaz IVolador
, o bien así:
Abeja maya = new Abeja();
((IVolador)maya).Volar();
que utilizamos una conversión explícita (el (IVolador)
delante de la variable) para conformar la variable maya
a la interfaz que nos interesa. Ambas formas son equivalentes.
Sin embargo, cuando la implementamos de manera implícita, es un miembro normal de la clase, por lo que podemos usarla directamente y no tenemos que complicarnos.
Pero entonces ¿por qué iba a querer alguien implementar una interfaz de manera explícita?
La respuesta es que el uso explícito nos proporciona una mayor seguridad de tipos y un mayor orden.
Por ejemplo, ¿qué pasa si tenemos dos interfaces que se pueden implementar en un momento dado sobre la misma clase y que tienen un miembro en común?
En nuestro ejemplo sencillo con los animales podríamos necesitar implementar una interfaz IPajaro
con varios métodos y propiedades específicos, y entre ellos un método Volar()
. A los pájaros los marcamos con esta interfaz, pero a todos los bichos que vuelan los marcamos con la interfaz IVolador
para poder hacer algo específico con ellos o poder localizarlos más fácilmente en ciertas colecciones.
Fíjate en cómo cambia el resultado del compilador cuando cambiamos la implementación explícita por la implícita en esta animación:
En la animación se ve cómo, de entrada, al estar implemementado tan solo el método Volar()
para la interfaz IVolador
, el compilador se queja de que IPajaro
no está completamente implementado (claro, le falta su propio método Volar()
). Tenemos dos soluciones:
- Implementar explícitamente
IPajaro.Volar()
(podríamos reutilizar el código de la anterior, no hace falta copiar y pegar)
- Dejar la implementación implícita, como se ve en la animación, en la que no especificamos interfaz alguna delante de
Volar()
.
En este último caso el método público Volar()
sirve para todos los casos: para cuando usemos la clase directamente, para cuando se use como IVolador
y como IPajaro
.
Esto puede parecer que es lo que queremos siempre, pero dependiendo de nuestra aplicación puede que tengamos necesidad de que un método se comporte de manera diferente según la interfaz a través de la cual se esté utilizando, y que además no se pueda usar en otras circunstancias. En este caso hay que implementarlo de manera específica y distinta según la interfaz.
Por ejemplo, siguiendo con nuestros animalitos, supongamos que tenemos otras dos interfaces para implementar animales terrestres (ITerrestre
) y animales acuáticos (IAcuatico
), además de los voladores que ya hemos visto. Un perro es terrestre y un pez es acuático pero, si pensamos en las ranas, se trata de animales anfibios que van tanto por agua como tierra. Así que una rana implementaría las dos interfaces: ITerrestre
y IAcuatico
. Cuando lo estemos usando como animal terrestre solo podrá Caminar()
, y como animal acuático solo podrá Nadar()
, pero mientras nada no camina y viceversa. Es decir, en este caso aunque no hay coincidencia de interfaces, sí que deberíamos implementarlas de manera explícita. No queremos que en un contexto cualquiera se pueda llamar a Nadar()
o Caminar()
puesto que a lo mejor no es correcto. Así que hay que llamar en cada caso a lo que corresponda.
¡Espero que te resulte útil!
Fecha de publicación: