CodeGym /Blog Java /Random-ES /¿Por qué necesitamos interfaces en Java?
Autor
Aditi Nawghare
Software Engineer at Siemens

¿Por qué necesitamos interfaces en Java?

Publicado en el grupo Random-ES
¡Hola! Hoy vamos a hablar de un concepto importante en Java: las interfaces. La palabra probablemente te resulte familiar. Por ejemplo, la mayoría de los programas y juegos de computadora tienen interfaces. En un sentido amplio, una interfaz es una especie de 'control remoto' que conecta a dos partes que interactúan. Un ejemplo simple de una interfaz en la vida cotidiana es un control remoto de TV. Conecta dos objetos, una persona y un televisor, y realiza diferentes tareas: subir o bajar el volumen, cambiar de canal y encender o apagar el televisor. Una de las partes (la persona) necesita acceder a la interfaz (presionar un botón en el control remoto) para que la otra parte realice la acción. Por ejemplo, para hacer que la TV cambie al siguiente canal. Además, el usuario no Necesita saber cómo está organizada la televisión o cómo se implementa internamente el proceso de cambio de canal. Lo único a lo que el usuario tiene acceso es a la interfaz. El objetivo principal es conseguir el resultado deseado. ¿Qué tiene esto que ver con la programación y Java? Todo :) Crear una interfaz es muy similar a crear una clase regular, pero en lugar de usar la palabraclase , indicamos la palabra interfaz . Veamos la interfaz Java más simple, veamos cómo funciona y por qué la necesitaríamos:

public interface CanSwim {

     public void swim();
}
Hemos creado una interfaz CanSwim . Es un poco como nuestro control remoto, pero con un 'botón': el método nadar() . Pero, ¿cómo usamos este control remoto? Para hacer esto, necesitamos implementar un método, es decir, nuestro botón de control remoto. Para usar una interfaz, algunas clases en nuestro programa deben implementar sus métodos. Inventemos una clase cuyos objetos 'puedan nadar'. Por ejemplo, una clase Duck se ajusta a:

public class Duck implements CanSwim {

    public void swim() {
        System.out.println("Duck, swim!");
    }

    public static void main(String[] args) {

        Duck duck = new Duck();
        duck.swim();
    }
}
"¿Qué vemos aquí? La clase Duck está 'asociada' con la interfaz CanSwim por la palabra clave implements . Puede recordar que usamos un mecanismo similar para asociar dos clases a través de la herencia, pero en ese caso usamos la palabra extends. Para con total claridad, podemos traducir ' la clase pública Duck implementa CanSwim ' literalmente como: 'La clase pública Duck implementa la interfaz CanSwim '. Esto significa que una clase asociada a una interfaz debe implementar todos sus métodos. Nota: nuestra Duckclase, al igual que la CanSwiminterfaz, tiene un swim()método y contiene algo de lógica. Este es un requisito obligatorio. Si solo escribimospublic class Duck implements CanSwimsin crear un swim()método en la Duckclase, el compilador nos dará un error: Duck no es abstracto y no anula el método abstracto nadar() en CanSwim ¿Por qué? ¿Por qué pasó esto? Si explicamos el error usando el ejemplo de la TV, sería como darle a alguien un control remoto de TV con un botón de 'cambio de canal' que no puede cambiar de canal. Puedes presionar el botón tanto como quieras, pero no funcionará. El control remoto no cambia de canal por sí mismo: solo envía una señal al televisor, que implementa el complejo proceso de cambio de canal. Y así es con nuestro pato: debe saber nadar para poder llamarlo a través de la CanSwiminterfaz. Si no sabe cómo, elCanSwimLa interfaz no conecta a las dos partes: la persona y el programa. La persona no podrá usar el swim()método para nadar Duckdentro del programa. Ahora entiendes más claramente para qué sirven las interfaces. Una interfaz describe el comportamiento que deben tener las clases que implementan la interfaz. 'Comportamiento' es una colección de métodos. Si queremos crear varios mensajeros, lo más fácil es crear una Messengerinterfaz. ¿Qué necesita todo mensajero? En un nivel básico, deben poder recibir y enviar mensajes.

public interface Messenger{

     public void sendMessage();

     public void getMessage();
}
Ahora podemos simplemente crear nuestras clases de mensajería que implementen la interfaz correspondiente. El propio compilador nos 'obligará' a implementarlos en nuestras clases. Telegrama:

public class Telegram implements Messenger {

    public void sendMessage() {

        System.out.println("Sending a Telegram message!");
    }

     public void getMessage() {
         System.out.println("Receiving a Telegram message!");
     }
}
WhatsApp:

public class WhatsApp implements Messenger {

    public void sendMessage() {

        System.out.println("Sending a WhatsApp message!");
    }

     public void getMessage() {
         System.out.println("Reading a WhatsApp message!");
     }
}
Viber:

public class Viber implements Messenger {

    public void sendMessage() {

        System.out.println("Sending a Viber message!");
    }

     public void getMessage() {
         System.out.println("Receiving a Viber message!");
     }
}
¿Qué ventajas proporciona esto? El más importante de ellos es el acoplamiento flojo. Imagine que estamos diseñando un programa que recopilará datos de clientes. La Clientclase definitivamente necesita un campo para indicar qué mensajero específico está usando el cliente. Sin interfaces, esto se vería raro:

public class Client {

    private WhatsApp whatsApp;
    private Telegram telegram;
    private Viber viber;
}
Creamos tres campos, pero un cliente solo puede tener un mensajero. Simplemente no sabemos cuál. Entonces tenemos que agregar todas las posibilidades a la clase para poder comunicarnos con el cliente. Resulta que uno o dos de ellos siempre serán nullcompletamente innecesarios para el programa. Es mejor usar nuestra interfaz en su lugar:

public class Client {

    private Messenger messenger;
}
¡Este es un ejemplo de acoplamiento flojo! En lugar de especificar una clase de mensajero específica en la Clientclase, simplemente indicamos que el cliente tiene un mensajero. Cuál se determinará exactamente mientras se ejecuta el programa. Pero, ¿por qué necesitamos interfaces para esto? ¿Por qué incluso se agregaron al idioma? Esa es una buena pregunta, ¡y la pregunta correcta! ¿No podemos lograr el mismo resultado utilizando la herencia ordinaria? La Messengerclase como padre, y Viber, Telegramy WhatsAppcomo hijos. De hecho, eso es posible. Pero hay un inconveniente. Como ya sabes, Java no tiene herencia múltiple. Pero hay soporte para múltiples interfaces. Una clase puede implementar tantas interfaces como desee. Imagina que tenemos una Smartphoneclase que tiene unoAppcampo, que representa una aplicación instalada en el teléfono inteligente.

public class Smartphone {

    private App app;
}
Por supuesto, una aplicación y un mensajero son similares, pero siguen siendo cosas diferentes. Puede haber versiones móviles y de escritorio de un mensajero, pero la aplicación representa específicamente una aplicación móvil. Este es el trato: si usáramos la herencia, no podríamos agregar un Telegramobjeto a la Smartphoneclase. Después de todo, la Telegramclase no puede heredar Appy Messenger! Y ya lo hicimos heredar Messengery lo agregamos a la Clientclase. ¡ Pero la Telegramclase puede implementar fácilmente ambas interfaces! En consecuencia, podemos darle a la Clientclase un Telegramobjeto como Messenger, y podemos dárselo a la Smartphoneclase como App. Así es como lo haces:

public class Telegram implements Application, Messenger {

    // ...methods
}

public class Client {

    private Messenger messenger;

    public Client() {
        this.messenger = new Telegram();
    }
}


public class Smartphone {

    private Application application;

    public Smartphone() {
        this.application = new Telegram();
    }
}
Ahora estamos usando la Telegramclase como queremos. En algunos lugares, actúa como un App. En otros lugares, actúa como un Messenger. Seguramente ya habrá notado que los métodos de la interfaz siempre están 'vacíos', es decir, no tienen implementación. La razón de esto es simple: la interfaz describe el comportamiento, pero no lo implementa. 'Todos los objetos que implementan la CanSwiminterfaz deben poder nadar': eso es todo lo que nos dice la interfaz. La forma específica en que nadan los peces, los patos y los caballos es una pregunta para Fish, DuckyHorseclases, no la interfaz. Al igual que cambiar el canal es una tarea para el televisor. El control remoto simplemente le da un botón para esto. Sin embargo, apareció una adición interesante en Java 8: los métodos predeterminados. Por ejemplo, su interfaz tiene 10 métodos. 9 de ellos tienen diferentes implementaciones en diferentes clases, pero uno se implementa igual para todos. Previamente, antes de Java 8, los métodos de interfaz no tenían implementación alguna: el compilador inmediatamente daba un error. Ahora puedes hacer algo como esto:

public interface CanSwim {

   public default void swim() {
       System.out.println("Swim!");
   }

   public void eat();

   public void run();
}
Usando la defaultpalabra clave, hemos creado un método de interfaz con una implementación predeterminada. Necesitamos proporcionar nuestra propia implementación para otros dos métodos, eat()y run()en todas las clases que implementan CanSwim. No necesitamos hacer esto con el swim()método: la implementación será la misma en todas las clases. Por cierto, ya te has encontrado con interfaces en tareas anteriores, incluso si no te habías dado cuenta :) He aquí un ejemplo vívido: ¡ Por qué las interfaces son necesarias en Java - 2Has trabajado con las interfaces Listy ! SetMás precisamente, ha trabajado con sus implementaciones: ArrayList, LinkedList, HashSet, etc. El mismo diagrama ofrece claramente un ejemplo en el que una clase implementa múltiples interfaces al mismo tiempo. Por ejemplo, LinkedListimplementa ListyDeque(cola de dos extremos) interfaces. Estás familiarizado con la Mapinterfaz, o mejor dicho, con su HashMapimplementación. Por cierto, este diagrama ilustra una característica: las interfaces pueden heredar otras interfaces. La SortedMapinterfaz hereda Map, mientras que Dequehereda Queue. Esto es necesario si desea mostrar la relación entre interfaces, donde una interfaz es una versión extendida de otra. Consideremos un ejemplo con la Queueinterfaz. Todavía no hemos revisadoQueues, pero es bastante simple y funciona como una fila ordinaria en una tienda. Solo puede agregar elementos al final de la cola y solo puede tomarlos desde el principio. En algún momento, los desarrolladores necesitaron una versión mejorada de la cola para agregar y tomar elementos en ambos extremos. Así que crearon una Dequeinterfaz, que es una cola de dos extremos. Tiene todos los métodos de una cola ordinaria. Después de todo, es el padre de la cola doble, pero también agrega nuevos métodos.
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION