¿De qué sirve tener una interfaz como tipo de retorno en Java?

Fácil:

Imagine que tiene un método, llamémoslo getProducts . Ahora, el tipo de retorno ingenuo para este método es ArrayList .

Pero esto es, cuando lo piensas, un tipo de retorno extraño. ¿Qué significa, en este contexto, escribir código como:

getProducts (). add (foo)

La respuesta es: no tiene sentido.

También es quebradizo. Imagine que tengo 100 lugares en mi código que llaman a getProducts . Ahora, llega el día en que quiero cambiar la implementación a una diferida, donde devuelvo un iterador que solo funciona cuando se extraen los datos. Ahora necesito ir y cambiar, potencialmente, otras 100 piezas de código que pueden depender del hecho de que estoy devolviendo un objeto demasiado robusto.

El principio general del diseño del software es devolver, desde cualquier método, el tipo de objeto menos capaz y menos específico que pueda. No queremos que nadie confíe en el hecho de que estamos usando internamente una ArrayList, si es que lo hacemos, porque eso hace que el software sea frágil al acoplar llamadas a los que llaman .

Al devolver una interfaz, por ejemplo, IEnumerable, aún puede devolver la ArrayList, pero ahora ninguna persona que llama puede confiar en los detalles de su implementación. Esto deja su código interno libre de cambiar, por ejemplo, usar una LinkedList en su lugar, sin afectar ningún código que sea cliente suyo.

También mejora la claridad: al devolver la interfaz más específica posible, hace que la intención del código sea mucho más clara al hacer que las acciones sin sentido sean imposibles.

HTH.

Las interfaces se pueden usar como un objeto normal. Tienen métodos a los que puedes llamar. Devolver una interfaz no es diferente a devolver un objeto porque una interfaz es un objeto.

Si un método devuelve una interfaz, significa que el método de retorno está decidiendo qué implementación darle y la interfaz le dice cómo interactuar con esa implementación de una manera común. Todo lo que necesita hacer es llamar a los métodos en la interfaz que necesita. No necesita conocer los detalles de lo que está haciendo el objeto que implementa la interfaz.

La interfaz de imagen de Java es un buen ejemplo. Si llama a un método que devuelve una imagen, ¿le importa qué tipo de imagen es? ¿O le importan los detalles de la imagen, como su resolución, profundidad de color, etc.? La interfaz proporciona esos detalles que son comunes a todas las imágenes.

La clase de implementación que subyace a la referencia de su interfaz de imagen puede ser PNG o GIF, pero no le importa. Puede pasar la imagen a un componente para representarla y puede ocuparse de los detalles.

Y puede crear una instancia de una interfaz instanciando un objeto que implemente la interfaz y declarando una variable del tipo de interfaz. Un ejemplo simple es:

Lista myList = new ArrayList ();

Usted se comunica con la colección List través de la interfaz List . No le importa que sea una implementación de ArrayList . Si luego necesita cambiar a una implementación diferente, todo el código que hace referencia a myList no cambia en absoluto porque se limita a la definición común de una Lista en lugar de la implementación específica de ArrayList . Una buena práctica, por cierto.

Las interfaces en Java incorporan el concepto de diseño de: Usted programa para interfaces, no implementaciones. También conocido como “programación por contrato”. La interfaz es el contrato. Los detalles no importan a menos que usted sea el implementador del código para el contrato.

Una interfaz describe una herramienta para hacer algo. La función puede devolver cualquier objeto que implemente esa interfaz sin que tenga que conocer la implementación de ese objeto.

Un ejemplo podría ser un identificador de base de datos. La interfaz describe las operaciones que puede realizar en una base de datos: buscar, insertar, eliminar, etc. Luego llama a un Objeto de fábrica que devuelve un identificador a la clase particular de base de datos que está utilizando hoy. Pero la implementación de la base de datos se puede cambiar sin preocuparse. Lo único que le preocupa a usted, el usuario, son las operaciones que puede realizar, que se describen en la interfaz. Entonces, si MySQLHandle, PostgresHandle, OracleHandle implementan DatabaseInterface, no tiene que saber qué se le ha dado.

Java es un lenguaje de tipo estático. Significa que el compilador javac y el tiempo de ejecución JVM verifican los tipos de variables en tiempo de compilación y tiempo de ejecución.

Entonces, cuando declara que su función devuelve algún tipo de datos, no puede devolver otro tipo.

Por ejemplo, si declaras algo como esto:

public Car getTransportation () {
// algún código aquí
}

su método solo puede devolver objetos de tipo Car y sus subclases.

Ahora, si quieres regresar Avión o Tren, ¿qué haces?

Tienes varias opciones. Uno estúpido está usando sobrecarga, define otro método getTransportation () que devuelve Airplane, otro getTransportation () que devuelve Train … etc.

Algunos otros idiotas más estúpidos incluso definirán una jerarquía de objetos completa con Abstract Factory Pattern o Simple Factory Pattern solo para crear y devolver los tipos correctos de objetos. Abusan de esos patrones de diseño.

Qué tal esto:

Definimos Coche, Tren, Avión como clases que implementan la interfaz “Transporte”, por lo que su método se verá así:

Transporte público getTransportation () {
// algún código aquí
}

Por lo tanto, puede devolver Car, subclases de Car, Train, subclases de Train, Airplane, subclases de trenes.

Y en el futuro, si desea vender boletos para Space Rocket y dispositivos de transporte futuristas que no existen ahora, ni siquiera tiene que volver a escribir la firma del método “getTransportation ()”. Simplemente haga que los nuevos dispositivos de transporte implementen la interfaz “Transporte”

Además, Java no admite la herencia múltiple por clases, por lo que no debe usar “extender clases” a la ligera. En OOP, también preferimos la composición sobre la herencia (en términos generales, si desea saber por qué, lea un libro decente de OOP o vaya a la escuela). Por lo tanto, es mejor usar interfaces.

Y las interfaces también separan las firmas / funcionalidades de las clases de las implementaciones concretas. (Quiere saber por qué es bueno: lea un libro decente de OOP o vaya a la escuela).

Sin embargo, mi opinión sobre Java es: es un muy buen lenguaje para aprender sobre programación y conceptos de diseño. Pero en la producción, es demasiado detallado, poco productivo. Y los marcos empresariales escritos en Java son absolutamente estúpidos, porque las personas abusan demasiado de los patrones de diseño y las inyecciones de dependencia.

Entonces, después de aprender Java a dominar los conceptos básicos de Ciencias de la Computación, las personas deberían pasar a otra cosa.

Hay muchos beneficios. Una de ellas es proporcionar una gran flexibilidad para que el proveedor del método cambie el tipo de instancia devuelto en el futuro. Por ejemplo, tiene un método chooseFromZoo (), cuando lo desee puede admitir todos los tipos de animales, será mejor que use un tipo de retorno Animal en lugar de un tipo más específico como Monkey. En realidad, no hay diferencia para usar la interfaz como un tipo de retorno con un tipo de argumento. La gente cree que la interfaz se prefiere sobre la clase cuando necesariamente.

Cuando el tipo de retorno de un método es una interfaz, lo que se devuelve es una instancia de una clase que “implementa” esa interfaz.

Lo mismo funciona para un método con clase abstracta que el tipo de retorno. Se devuelve una instancia de una clase concreta que hereda la clase abstracta.

Considere otro caso de asignar algo a una variable cuyo tipo se especifica como una interfaz. Por ejemplo, un comparador de retorno de TreeMap donde este último es una interfaz. Lo que sucede es que se devuelve y se asigna una instancia de una clase que implementa Comparator. Recuerde que solo puede usar los métodos proporcionados por la interfaz en ese caso.

Esto se alinea bien con el principio de programar una interfaz, en lugar de una clase concreta.

El tipo de retorno puede ser una interfaz, pero eso no significa que el objeto devuelto no pueda ser mucho más. El objeto podría ser cualquier cosa siempre que implemente la interfaz mencionada. El diseñador del método quería ocultar los detalles de implementación de la persona que llama (o el usuario en este escenario, que es otro desarrollador. No el usuario final), y solo le dice lo suficiente para poder usar el objeto para lo que necesita , y nada más. Un ejemplo se muestra a continuación.

Colección pública getAllWidgets ()

Supongamos que mi clase contiene una colección de widgets y quiero permitir que el usuario pueda iterar a través de ella. Se implementa internamente como una matriz, pero no quiero exponer ese detalle de implementación al mundo exterior. El usuario solo necesita saber que estoy devolviendo una Colección, y solo puede hacer con ella lo que dice la interfaz.

¿Por qué pasar por todos los problemas? Bueno, esto se llama desacoplamiento. Mientras las diferentes partes sepan lo mínimo que necesitan entre sí, será más fácil hacer cambios en cada parte individual sin romper la otra. En el ejemplo dado, podría usar cualquier otro tipo de colección interna, y estaría seguro sabiendo que no importa cuántos otros lugares se esté usando mi clase, el código no se rompería.

Además, ayuda a la legibilidad de su código. Cuanto menor es el área de superficie de su clase (la cantidad de métodos que expone), más fácil es para mí saber qué puedo hacer con ella (legalmente). Por eso también tenemos métodos públicos / privados. Solo mantenemos en público los métodos mínimos que el usuario necesitaría para poder trabajar con nuestra clase.

Puede tener su propia implementación para admitir código débilmente acoplado