C sharp NET/Capítulo 9

De Wikilibros, la colección de libros de texto de contenido libre.
Versión para imprimir esta pagina

Interfaces[editar]



Definición[editar]

Una Interfaz es una colección de miembros abstractos relacionados semánticamente. Una interfaz representa un comportamiento que una clase dada puede soportar.

El número de miembros de una interfaz dependen del comportamiento que queramos soportar, por ejemplo todos los objetos que sean móviles podrían querer soportar los métodos acelerar y frenar.

Según la interfaz de C# una interfaz sería:

public interface IMovil
{
     bool Acelerar(int n);
     bool Frenar(int n);
}

También podríamos declarar dentro de la interfaz una propiedad que nos permita leer y/o escribir la velocidad que queremos que tome nuestro objeto.

public interface IMovil
{
     bool Velocidad{get; set;}
}

Dado que una interfaz es una colección de miembros abstractos cualquier clase o estructura que quiera implementar una interfaz está obligada a implementar cada uno de los métodos que se declaran en la interfaz. De esta forma se consigue un cierto tipo de polimorfismo ya que si varias clases implementan la misma estructura tenemos la posibilidad de tratar con todas ellas de la misma forma.

Seguramente alguien se preguntara por que usar interfaces pudiendo usar una clase base abstracta definiendo los métodos anteriores como abstractos, la primera razón es simplicidad, una clase base abstracta suele hacer más que definir una colección de métodos, es capaz de definir métodos públicos, privados, protegidos y también metodos concretos (estáticos) a los que pueden acceder todas las clases que deriven de ella mientras que una interfaz se limita a definir una colección de métodos sin ninguna implementación. La segunda razón es que C# solamente soporta herencia simple, pero sin embargo podemos hacer que una clase implemente múltiples interfaces.

He aquí como haríamos para heredar de una clase base e implementar una interfaz, teniendo en cuenta que VehiculoDeMotor será nuestra clase base e IMovil nuestra interfaz.

public class CocheDeportivo : VehiculoDeMotor, IMovil
{
     //Implementación de los métodos abstractos de vehículo
     
     bool Acelerar(int n)
     {
       //implementación de Acelerar
     }
     bool Frenar(int n)
     {
       //implementación de Frenar
     }
} 

Hay que tener en cuenta que siempre hay que poner la clase base antes de las interfaces.

Ahora nuestra clase CocheDeportivo así como cualquier otra clase que implemente IMovil podra acelerar y frenar, hay que tener en cuenta que si implementamos IMovil tendremos que implementar absolutamente todos sus métodos.

Obteniendo Referencias a la Interfaz[editar]

Si hemos creado la clase CocheDeportivo podemos querer saber si éste soporta el comportamiento de IMovil de modo que podemos hacer un cast explícito:

CocheDeportivo coche1 = new CocheDeportivo();
IMovil movil = (IMovil) coche1;
movil.Acelerar(30);

En caso de que nuestro objeto implemente la interfaz podríamos operar sobre él con todos los métodos de la misma, pero en caso de que no la soporte tendríamos un error en tiempo de ejecución, con lo cual la forma correcta de hacerlo es:

CocheDeportivo coche1 = new CocheDeportivo();
try{
     IMovil movil = (IMovil) coche1;
     movil.Acelerar(30);
}
catch(InvalidCastException e){
     //gestión del error
}

Otra forma de hacerlo sin tener que recurrir a la gestión de excepciones sería utilizando la palabra reservada as de C#:

CocheDeportivo coche1 = new CocheDeportivo();
IMovil movil;
movil = coche1 as IMovil;
if (movil != null)
     movil.Frenar(10);
else
     //otro tratamiento

La palabra reservada as pone la variable de tipo interfaz a null si la interfaz dada no está soportada por el objeto.

Por último también podemos usar la palabra reservada is de C# para descubrir si un objeto implementa o no una interfaz:

CocheDeportivo coche1 = new CocheDeportivo();
if (coche1 is IMovil)
     coche1.Acelerar(10);
else
     //otra gestión

Pasar interfaces como parámetros[editar]

Las interfaces son tipos de datos fuertemente tipados (valga la redundancia) de modo que se pueden pasar como parámetros a métodos y se pueden usar también como valores de retorno.

Hemos creado la interfaz IGiro de la siguiente manera:

public interface IGiro
{
     void GirarDerecha(int grados);
     void GirarIzquierda(int grados);
}

Y queremos que nuestro coche deportivo pueda girar a izquierda y derecha:

public class CocheDeportivo : VehiculoDeMotor, IMovil, IGiro
{
     //Implementación de los métodos abstractos de vehículo
     
     bool Acelerar(int n)
     {
       //Implementación de Acelerar
     }
     bool Frenar(int n)
     {
       //Implementación de frenar
     }
     void GirarDerecha(int grados)
     {
       //Implementación de GirarDerecha
     }
     void GirarIzquierda(int grados)
     {
       //Implementación de GirarIzquierda
     }
} 

Como hemos visto para soportar otra interfaz simplemente la añadimos al final después de una ",".

Ahora supongamos que queremos hacer un método que nos provea de utilidades para el giro por ejemplo hacer trompos, le podríamos pasar una interfaz IGiro de la siguiente forma

public class UtilsGiro
{
     public static void Trompo(IGiro giro)
     {
       giro.GirarIzquierda(360);
     }
}

Implementación Explícita de una Interfaz[editar]

Siguiendo con nuestro ejemplo de los coches definimos una nuevas interfaces:

public interface IAltaVelocidad
{
     void Turbo(bool activar);
     //resto de metodods
}

Como se puede ver nuestra interfaz implementa un método Turbo. ¿Qué pasaría si una clase heredase a su vez de la clase Formula Uno que también implemente el método void Turbo (bool)? Bueno vamos a verlo:

public class Campeon : Formula1, IAltaVelocidad
{
     public override void Turbo(bool activar)
     {
       //gestion del turbo
     }
}

Esto en un principio sería correcto pero qué pasa si hacemos lo siguiente:

Campeon miCampeon = new Campeon();
miCampeon.Turbo(true);

IAltaVelocidad iav = (IAltaVelocidad) miCampeon;
iav.Turbo(true);

Ambas veces se llamaría al mismo método, el definido en la clase Formula1, pero como haríamos si quisiéramos tener dos Turbos diferentes? la respuesta es hacer que los métodos definidos en la interfaz sean sólo accesibles desde una referencia a la interfaz, esto es lo que se llama implementación explícita de una interface.

public class Campeon : Formula1, IAltaVelocidad
{
     public override void Turbo(bool activar)
     {
       //gestión del turbo
     }

     void IAltaVelocidad.Turbo(bool activar)
     {
       //gestión del turbo
     }
}

El segundo método sólo podrá ser llamado si usamos una referencia de tipo IAltaVelocidad mientras que el primero podrá ser llamado usando una referencia a Campeon o a Formula1 (su clase base).

Existen algunas reglas extra al hacer esto, por ejemplo no podemos usar modificadores de accesibilidad (public, private, protected) ya que si intentamos que sólo se pueda acceder al método desde una referencia a la interfaz hacerlo sería contraproducente.

También hay que tener en cuenta que pueden haber colisiones de nombres entre clases base e interfaces y entre interfaces entre sí, técnicamente no existe ninguna diferencia y todas pueden ser tratadas como hemos explicado arriba.

Jerarquías de interfaces[editar]

Las interfaces pueden servir de base para otras interfaces al igual que las clases, e igual que en éstas la idea es que vayamos de lo general a lo particular.

Por ejemplo:

interface IVehiculo
{
     void Acelerar();
     void Frenar();
}
interface IVehiculoGasolina : IVehiculo
{
     void CambiarVelocidadInyeccion(int velocidad);
}
interface IVehiculo4x4: IVehiculoGasolina
{
     void Activar4x4(bool activar);
}

Al implementar una de estas interfaces en nuestra clase tenemos que implementar todos los métodos de esta interfaz y de sus ancestros.

public class CocheDeJuguete : IVehiculo
{
     void IVehiculo.Acelerar(int n)
     {
       //Gestión del acelerado
     }
     void IVehiculo.Frenar(int n)
     {
       //Gestión del frenado
     }
}
public class CocheNormal:IVehiculoGasolina
{
     void IVehiculo.Acelerar(int n)
     {
       //Gestión del acelerado
     }
     void IVehiculo.Frenar(int n)
     {
       //Gestión del frenado
     }
     void IVehiculoGasolina.CambiarVelocidadInyeccion(int velocidad)
     {
       //Gestión de la inyeccion
     }
}
public class TodoTerreno:IVehiculo4x4
{

     void IVehiculo.Acelerar(int n)
     {
       //Gestión del acelerado
     }
     void IVehiculo.Frenar(int n)
     {
       //Gestión del frenado
     }
     void IVehiculoGasolina.CambiarVelocidadInyeccion(int velocidad)
     {
       //Gestión de la inyeccion
     }
     void IVehiculo4x4.Activar4x4(bool activar)
     {
       //Gestión de 4x4
     }
}

Y lógicamente las llamadas a los métodos serían:

TodoTerreno miTodoTerreno = new TodoTerreno();
((IVehiculo4x4)miTodoTerreno).Acelerar(20);
((IVehiculo4x4)miTodoTerreno).Frenar(20);
((IVehiculo4x4)miTodoTerreno).CambiarVelocidadInyeccion(1000);
((IVehiculo4x4)miTodoTerreno).Activar4x4(true);
Versión para imprimir esta pagina