que-son-los-interfaces-en-programacion

Qué son y cómo usar los interfaces

En programación orientada a objetos un interface es una declaración que define un conjunto de métodos que posteriormente las clases pueden implementar.

Si una clase implementa todos los métodos del interfaz, decimos que la clase implementa o cumple ese interface (con que exista uno que la clase no tenga, esta no implementa el interface).

Los interfaces define un conjunto de funcionalidades sin especificar cómo deben ser implementadas. Solo declaran las firmas de los métodos, dejando su implementación a las clases.

De esta forma, los interfaces proporcionan un nivel muy alto de abstracción, ya que permiten definir lo que una clase puede hacer, sin entrar en absoluto a definir cómo lo hacen.

Como son uno de los puntos más importantes de la programación orientada a objetos, la máxima expresión del Polimorfismo, mejor invento que los donuts, y a la vez es de los conceptos que más cuestan de entender, ¡se viene ejemplo cotiano! 👇

Propósito de los interface

Imagina que mañana tienes que hacer un viaje muy importante. Tu sabes conducir, pero no tienes coche. Pero no pasa nada, tu jefe de dice:

- Mañana te voy a dejar un vehículo de alquiler en la puerta

Tú te vas a dormir más tranquilo. Pero de repente piensas “un momento… ¿ha dicho un vehículo? ¿Qué vehículo? ¿No me irá a dejar un helicóptero? ¿¿¡o un submarino??!”

Claro, porque tu sabes conducir coches. Pero no tienes ni idea de conducir helicópteros, ni submarinos, ni avestruces.

interface

Tu jefe, el simpático, te ha dejado un helicóptero

El problema es que tu jefe te ha dicho que te deja un objeto en la puerta, pero tu no tienes ni idea de si vas a saber usarlo. Pues a tus métodos les pasa igual con tus objetos.

Una forma de solucionar esto es usar una clase abstracta Coche. Si tu jefe te dice que te va a dejar un coche de alquiler, ya no hay problema, porque tú sabes conducir.

Pero, otra forma de hacerlo, es si tu jefe te dice:

- Mañana te voy a dejar una cosa con pedales y volantes que se puede conducir

Vale, realmente deberías ir buscándote otro trabajo porque tu jefe es muy raro. Pero, igualmente duermes tranquilo. Porque te va a dejar algo que se puede conducir. Y tú (nuevamente) sabes conducir.

De hecho esta forma es mucho más versátil. Porque ya no solo puede darte un coche. Puede dejarte una furgoneta pequeña, un tractor, o una carroza de carnaval… pero tu vas a poder conducirla. Porque 🌟 —> tu sabes conducir <— 🌟.

(sí, me he puesto pesado con lo de “tu sabes conducir”. Veremos porqué en el siguiente apartado 👇)

Caso práctico

Vamos a ver si traducimos lo que hemos explicado antes en el punto anterior al equivalente en programación. Lo que estábamos diciendo es que tu eres un Conductor. Y los conductores, tienen un método que les permite Conducir(...).

class Conductor
{
	Conducir(... algo ... );
}

¿Qué es lo que puedes conducir? Bueno, pues una de las alternativas es decir que puedes conducir Coches. Que vendría a ser esto.

class Conductor
{
	Conducir(Coche coche);
}

Pero he repetido tanto lo de “tu sabes conducir”. Tu método, no sabe conducir coches, si no “cualquier cosa que se pueda conducir”. Lo mismo da que sea un coche, o una furgoneta, o una carroza de carnaval.

Así que ahora podemos ser mucho más generalistas. Lo que trasladado al mundillo del código es esto:

class IConducible 
{
	PisarPedales(),
	GirarVolante(),
	// lo que hiciera falta
}

class Coche implements IConducible
class Furgoneta implements IConducible
class Carroza implements IConducible

class Conductor
{
	Conducir(IConducible conducible);
}

Ahora tu conductor puede conducir “todo lo que se pueda conducir”. Esto incluye coches, furgonetas e, incluso, (me encanta decirlo) carrozas de carnaval.

Para ello la única condición es que sean IConducible. Que significa, que tienen que tener unos pedales, un volante y (lo que sea que haga falta, es un ejemplo).

A los interface se les suele denominar con una ‘I’ al principio.
Por ejemplo: IConducible, IVolador, IPagador…

Interface en programación

Se suele usar para explicar los interface que son como un “contrato” que una clase puede cumplir. Puede ser útil para entender el concepto pero, la verdad, a mi no me gusta especialmente esta explicación.

Un interface se llama interface porque, literalmente, es un interface. ¿Qué es el interface de una máquina? Los elementos, o dispositivos, o los programas, que usas para interactuar con él.

En el caso de programación, es lo mismo,

Un interface es la declaración de una forma de interactuar con una clase

Esto viene de una revelación que vas a tener en algún momento (espero que ahora) ¿para que necesita un método recibir una clase? Para hacer cosas con él. Pero… ¿que cosas?

Pues la mayoría de las veces un método recibe un objeto para hacer esto:

void MiFuncion(MiObjeto objeto)
{
	objeto.MiVariable = 35;
	objeto.MiMetodo("parametro_inventado");
}

Es decir, la gran gran mayoría de métodos reciben un objeto simplemente para hacer . y después lo que sea (.MiVariable, .MiMetodo(...))

Y para eso, la gran gran mayoría de métodos, no necesitan un objeto de tipo MiObjeto. Les vale igual de bien con saber su interface.

interface IMiInterface 
{
	string MiVariable,
	void MiMetodo(string),
}

MiObjeto implements IMiInterface

void MiFuncion(IMiInterface objeto)
{
	objeto.MiVariable = 35;
	objeto.MiMetodo("parametro_inventado");
}

Porque lo único que necesitan es saber que lo que tienen tiene la variable .MiVariable, y el método .MiMetodo(...) disponible.

Comparación con las clases abstractas

Una interfaz es similar a una clase abstracta, en el sentido de que define un conjunto de métodos que deben ser implementados por las clases que la utilicen. De hecho, un interface es como una “¡¡super clase abstracta definitiva!!“.

Pero en el fondo tienen diferencias muy importantes, tanto en funcionamiento como en concepto.

En cuanto a funcionamiento, la clase abstracta puede contener implementaciones de algunos métodos. De hecho, esta es su utilidad principal, proporcionar una base. Por contra, un interface es siempre full abstracto.

En cuanto a concepto, la clase abstracta supone una relación de herencia, y un interface de implementación. Más detalles en el siguiente punto 👇.

Cuando usar herencia y cuando interface

Otra de las dudas que os van a surgir más frecuentemente es cuándo debéis usar un interface y cuando usar herencia (da igual que sea una clase abstracta, o no).

A veces os liaréis porque parecen parecidas, pero en realidad son dos cosas totalmente distintas. La herencia significa pertenencia, y el interface significa comportamiento.

Es algo difícil de explicar, así que os doy un truco bueno, que os ayudará a diferenciar uno de otro (y de paso a entender el concepto)

  • Es herencia si puedes decir es un
  • Es interface si puedes decir puede / se puede

Vamos a verlo con dos ejemplos:

Ejemplo de herencia

Una furgoneta es un vehiculo

Un coche es un vehiculo

Entonces hay una relación de herencia (pertenencia). furgoneta y coche son clases derivadas, y vehículo es una clase base.

Ejemplo de interfaces

Una furgoneta se puede conducir

Un pajaro puede volar

Esto son relaciones de implementación (comportamiento), con IConducible o IVolador.

Ejemplos en distintos lenguajes

Veamos como distintos lenguajes implementan el concepto de interface.

En C#, las interfaces se declaran con la palabra reservada interface.

public interface IAnimal
{
    void HacerSonido();
}

public class Perro : IAnimal
{
    public void HacerSonido()
    {
        Console.WriteLine("Guau");
    }
}

public class Gato : IAnimal
{
    public void HacerSonido()
    {
        Console.WriteLine("Miau");
    }
}

// Uso
IAnimal perro = new Perro();
IAnimal gato = new Gato();

perro.HacerSonido(); // Salida: Guau
gato.HacerSonido();  // Salida: Miau

En C++, las interfaces se logran mediante clases abstractas con al menos una función virtual pura.

#include <iostream>

class IAnimal {
public:
    virtual void HacerSonido() const = 0;
    virtual ~IAnimal() = default;
};

class Perro : public IAnimal {
public:
    void HacerSonido() const override {
        std::cout << "Guau" << std::endl;
    }
};

class Gato : public IAnimal {
public:
    void HacerSonido() const override {
        std::cout << "Miau" << std::endl;
    }
};

// Uso
int main() {
    IAnimal* perro = new Perro();
    IAnimal* gato = new Gato();

    perro->HacerSonido(); // Salida: Guau
    gato->HacerSonido();  // Salida: Miau

    delete perro;
    delete gato;

    return 0;
}

En JavaScript, no hay una sintaxis nativa para definir interfaces como en otros lenguajes. Sin embargo, podríamos lograr un comportamiento similar utilizando clases abstractas o simplemente utilizando objetos que compartan un conjunto común de métodos.

// Definimos un objeto que actúa como una "interfaz" en JavaScript
const IAnimal = {
    hacerSonido: function() {
        throw new Error("Método hacerSonido no implementado");
    }
};

// Implementación de la clase Perro
class Perro {
    hacerSonido() {
        console.log("Guau");
    }
}

// Implementación de la clase Gato
class Gato {
    hacerSonido() {
        console.log("Miau");
    }
}

// Uso
const perro = new Perro();
const gato = new Gato();

perro.hacerSonido(); // Salida: Guau
gato.hacerSonido();  // Salida: Miau

En TypeScript, las interfaces se definen usando la palabra reservada interface.

interface IAnimal {
    hacerSonido(): void;
}

class Perro implements IAnimal {
    hacerSonido(): void {
        console.log("Guau");
    }
}

class Gato implements IAnimal {
    hacerSonido(): void {
        console.log("Miau");
    }
}

// Uso
const perro: IAnimal = new Perro();
const gato: IAnimal = new Gato();

perro.hacerSonido(); // Salida: Guau
gato.hacerSonido();  // Salida: Miau

En Python, las interfaces se logran mediante clases abstractas utilizando el módulo abc.

from abc import ABC, abstractmethod

class IAnimal(ABC):
    @abstractmethod
    def hacer_sonido(self):
        pass

class Perro(IAnimal):
    def hacer_sonido(self):
        print("Guau")

class Gato(IAnimal):
    def hacer_sonido(self):
        print("Miau")

# Uso
perro = Perro()
gato = Gato()

perro.hacer_sonido() # Salida: Guau
gato.hacer_sonido()  # Salida: Miau

Buenas prácticas Consejos

Los interface son una de las mejores herramientas para mantener la limpieza de nuestro programa. Nos permiten reducir el acopamiento de clases, que ya sabemos que es uno de los principales problemas del POO.

Además favorecen la reutilización del código, promueven la abstracción, reducen la herencia entre clases… en fin, una maravilla. ¡A usarlos!

Peeeero (en esta vida siempre hay un pero) hay que saber usarlos. El principal problema que tendréis es cuando usar un interface y cuando herencia. Ya lo hemos visto en un punto anterior.

Por otro lado, la otra dificultad va a ser cómo definir un interface. Para eso recordemos, que un interface modela comportamientos, no entidades.

Ejemplo, os veréis muy tentados de hacer un interface para todo. Toda clase acaba teniendo un interface. Tienes una clase User, y tiene su IUser. Tienes una clase ShoppingCart y le haces su interface IShoppingCart.

Eso lo hemos hecho todos, y alguna vez lo harás tu también. Pero en realidad, haciendo un interface por clase, te estás “cargando” la mitad de la gracia de los interface. Los interface deben modelizar comportamiento. Así que en el caso de User tendrás Iidentificable, Iemailable, o cosas así.

Pero claro, si no separo los interface, acabo incumpliendo el principio de segregación de interfaces, con interfaces con demasiados métodos. Pero si los separo mucho, acabo casi en duck typing.

¿Entonces, donde separar? Pues no hay una receta mágica. Variará respecto a vuestro modelo de objetos, vuestra experiencia, lo que te pida el cuerpo… y aún así a veces meterás la cambia (pues no pasa nada, se refactoriza y punto).