¿Que es la interfaz de una clase?
En teoría de orientación a objetos, la interfaz de una
clase es todo lo que podemos hacer con ella. A efectos prácticos: todos
los métodos, propiedades y variables públicas (aunque no deberían haber nunca
variables públicas, debemos usar propiedades en su lugar) de la clase
conforman su interfaz.
Dada la siguiente clase:
class Contenedor
{
public int Quitar();
public void Meter(int v);
private bool EstaRepetido(int v);
}
Su interfaz está formada por los
métodos Quitar y Meter. El
método EstaRepetido no forma parte de la interfaz de dicha
clase, ya que es privado.
En orientación a objetos decimos que la interfaz de
una clase define el comportamiento de dicha clase, ya que define que
podemos y que no podemos hacer con objetos de dicha clase: dado un objeto de la
clase Contenedor yo puedo llamar al método Quitar y al
métdo Meter pero no puedo llamar al método EstaRepetido.
Así pues: toda clase tiene una interfaz que
define que podemos hacer con los objetos de dicha clase.
2. Interfaces idénticas no significa clases
intercambiables
Fíjate en estas dos clases:
class Contenedor
{
public int Quitar() { ... }
public void Meter (int v) { ... }
}
class OtroContenedor
{
public int Quitar() { ... }
public void Meter (int v) { ... }
}
Que puedes deducir de ellas? Exacto! Su inerfaz es la
misma: con ambas clases podemos hacer lo mismo: llamar al
método Quitar y al método Meter.
Ahora imagina que en cualquier otro sitio tienes un
método definido tal y como sigue:
public void foo (Contenedor c)
{
//
Hacer cosas con c como p.ej:
int i = c.Quitar();
c.Meter(10);
}
El método recibe un Contenedor y opera con
él. Ahora dado que las interfaces
de Contenedor y OtroContenedor son iguales, uno podría
esperar que lo siguiente funcionase:
OtroContenedor oc = new OtroContenedor();
foo(oc);
Pero esto no va a compilar. ¿Por que? Pues aunque
nosotros somos capaces leyendo el código de comparar la interfaz de ambas
clases, el compilador no puede hacer esto. Para el
compilador Contenedor y OtroContenedor son dos clases
totalmente distintas sin ninguna relación. Por lo tanto un método que espera
un Contenedor no puede aceptar un objeto de la
clase OtroContenedor.
Quiero recalcar que el hecho de que el compilador
no compare las interfaces de las clases no se debe a una
imposibilidad técnica ni nada parecido: se debe a que no tiene sentido hacerlo.
¿Por que? Pues simplemente porque las interfaces son
idénticas por pura casualidad. Supón que fuese legal llamar a foo con un
objeto OtroContenedor, ok?
Entonces podría pasar lo siguiente:
1.
Alguien añade un método público a la
clase Contenedor.
2.
Se modifica el método foo para que llame a dicho
método nuevo. Eso es legal porque foo espera un Contenedor como
parámetro
3.
La llamada a foo(oc) donde oc es OtroContenedor…
como debe comportarse ahora? OtroContenedor no tiene el método nuevo
que se añadió a Contenedor!
Así pues: dos clases con la misma interfaz no
tienen relación alguna entre ellas y por lo tanto no se pueden intercambiar.
3. Implementación de interfaces
El ejemplo anterior ejemplifica un caso muy común:
tener dos clases que hacen lo mismo pero de diferente manera. P.ej.
imagina que Contenedor está implementado usando un array en memoria
y OtroContenedor está implementando usando, que sé yo, pongamos un
fichero en disco. La funcionalidad (la interfaz) es la misma, lo que varía
es la implementación. Es por ello que en programación orientada a
objetos decimos que las interfaces son funcionalidades (o comportamientos) y
las clases representen implementaciones.
Ahora bien, si dos clases representan dos
implementaciones distintas de la misma funcionalidad, es muy enojante (y
estúpido) que no las podamos intercambiar. Para que dicho intercambio sea
posible C# (y en general cualquier lenguaje orientado a objetos) permite explicitar
la interfaz, es decir separar la declaración de la interfaz de su
implementación (de su clase). Para ello usamos la palabra
clave interface:
interface IContenedor
{
int Quitar();
void Meter(int i);
}
Este código declara una interfaz IContenedor que
declara los métodos Quitar y Meter. Fíjate que los métodos no se declaran
como public (en una interfaz la visibilidad no tiene sentido, ya que todo es
public) y que no se implementan los métodos.
Las interfaces son un concepto más teórico que real. No
se pueden crear interfaces. El siguiente código NO compila:
IContenedor c = new IContenedor();
// Error: No se puede crear una
interfaz!
Es lógico que NO podamos crear interfaces, ya que si
se nos dejara, y luego hacemos c.Quitar()… que método se llamaría si el método
Quitar() no está implementado?
Aquí es donde volvemos a las clases: podemos indicar
explícitamente que una clase implementa una interfaz, es
decir proporciona implementación (código) a todos y cada uno de los
métodos (y propiedades) declarados en la interfaz:
class Contenedor : IContenedor
{
public int Quitar() { ... }
public void Meter(int i) { ... }
}
La clase Contenedor declara explícitamente
que implementa la interfaz IContenedor. Así pues la clase debe proporcionar
implementación para todos los métodos de la interfaz. El siguiente código
p.ej. no compila:
class Contenedor : IContenedor
{
public void Meter(int i) { ... }
}
// Error: Y el método Quitar()???
Es por esto que en orientación a objetos decimos que
las interfaces son contratos, porque si yo creo la clase la interfaz me
obliga a implementar ciertos métodos y si yo uso la clase, la interfaz me dice
que métodos puedo llamar.
Y ahora viene lo bueno: Si dos clases implementan
la misma interfaz son intercambiables. Dicho de otro modo, en cualquier
sitio donde se espere una instancia de la interfaz puede pasarse una instancia
de cualquier clase que implemente dicha interfaz.
Podríamos declarar nuestro método foo anterior como
sigue:
void foo(IContenedor c)
{
//
Cosas con c...
c.Quitar();
c.Meter(10);
}
Fíjate que la clave es que el parámetro de foo está
declarado como IContenedor, no
como Contenedor o OtroContenedor, con esto indicamos que el
método foo() trabaja con cualquier objeto de cualquier clase que implemente
IContenedor.
Y ahora, si supones que tanto Contenedor como
OtroContenedor implementan la interfaz IContenedor el siguiente código es
válido:
Contenedor c = new Contenedor();
foo(c); //
Ok. foo espera IContenedor y Contenedor implementa IContenedor
OtroContenedor oc = new OtroContenedor();
foo(oc); // Ok. foo espera IContenedor y
OtroContenedor implementa IContenedor
// Incluso esto es válido:
IContenedor ic = new Contenedor();
IContenedor ic2 = new OtroContenedor();
¿Cuando usar
interfaces?
En general siempre que tengas, o preveas que puedes
tener más de una clase para hacer lo mismo: usa interfaces. Es mejor pecar de
exceso que de defecto en este caso. No te preocupes por penalizaciones de
rendimiento en tu aplicación porque no las hay.´
No digo que toda clase deba implementar una
interfaz obligatoriamente, muchas clases internas no lo
implementarán, pero en el caso de las clases públicas (visibles desde el
exterior) deberías pensarlo bien. Además pensar en la interfaz antes que en la
clase en sí, es pensar en lo que debe hacerse en lugar de
pensar en como debe hacerse. Usar interfaces permite a posteriori cambiar
una clase por otra que implemente la misma interfaz y poder integrar la nueva
clase de forma mucho más fácil (sólo debemos modificar donde instanciamos los
objetos pero el resto de código queda igual).
DEMOSTRACIÓN
En Visual Studio vamos a dar click en menú File à New à Project
Seleccionamos la opción
‘Console Application’ y colocamos como nombre ‘DemoAppInterfaces’ y damo click
en el botón OK
Fíjese que se agrega nuestro
proyecto al navegador de soluciones
Fíjese nuevamente que en el
proyecto ‘DemoAppInterfaces’ existe un archivo de nombre ‘Program.cs’, le damos
doble click en él, y procedemos a escribir nuestra interface que sera IAve la
cual contendra un par de metodos que todas las aves realizan Volar() y Comer().
Ahora crearemos una clase la
cual implementara la interface anteriormente realizada, como se muestra a continuación:
Como vemos hemos
implementado ambos metodos de nuestra interface(Volar();, Comer();) en caso de
no implementarlos se generaria un error debido a que estas propiedades o
metodos deben de ser obligatorios para poder implementar la interface en
nuestra clase.
Ahora crearemos otra clase en la cual implementaremos nuestra clase
AvePropiedades la cual contiene un constructor el cual nos inicializa la
variable Nombre.
Como podemos notar nuestro
contructor de la clase Ave nos pide como parametro un string el cual
inicalizara nustra clase padre o base AvePropiedades.
Enseguida implementaremos nuestra clase Ave en un código simple.
Ejecutamos
El resultado es el
siguiente: