¿Hay más en una interfaz que tener los métodos correctos?

Entonces digamos que tengo esta interfaz:

public interface IBox { public void setSize(int size); public int getSize(); public int getArea(); //...and so on } 

Y tengo una clase que lo implementa:

 public class Rectangle implements IBox { private int size; //Methods here } 

Si quería usar la interfaz IBox, no puedo crear una instancia de la misma, en el camino:

 public static void main(String args[]) { Ibox myBox=new Ibox(); } 

¿derecho? Entonces realmente tendría que hacer esto:

 public static void main(String args[]) { Rectangle myBox=new Rectangle(); } 

Si eso es cierto, ¿entonces el único propósito de las interfaces es asegurarse de que la clase que implementa una interfaz tenga los métodos correctos como se describe en una interfaz? ¿O hay algún otro uso de interfaces?

Las interfaces son una forma de hacer que su código sea más flexible. Lo que haces es esto:

 Ibox myBox=new Rectangle(); 

Luego, más adelante, si decides que quieres usar un tipo de caja diferente (tal vez haya otra biblioteca, con un mejor tipo de caja), cambias tu código a:

 Ibox myBox=new OtherKindOfBox(); 

Una vez que te hayas acostumbrado, verás que es una excelente forma de trabajo (realmente esencial).

Otra razón es, por ejemplo, si desea crear una lista de cuadros y realizar alguna operación en cada uno, pero desea que la lista contenga diferentes tipos de cuadros. En cada caja puedes hacer:

 myBox.close() 

(suponiendo que IBox tiene un método close ()) aunque la clase real de myBox cambia dependiendo de la casilla en la que se encuentre en la iteración.

Lo que hace que las interfaces sean útiles no es el hecho de que “puedes cambiar de opinión y usar una implementación diferente más adelante y solo tienes que cambiar el lugar donde se crea el objeto”. Eso no es un problema.

El verdadero punto ya está en el nombre: definen una interfaz que cualquiera puede implementar para usar todo el código que opera en esa interfaz. El mejor ejemplo es java.util.Collections que proporciona todo tipo de métodos útiles que operan exclusivamente en interfaces, como sort() o reverse() para List . El punto aquí es que este código ahora se puede usar para ordenar o invertir cualquier clase que implemente las interfaces List , no solo ArrayList y LinkedList , sino también las clases que usted mismo escribe, que pueden implementarse de forma que las personas que escribieron java.util.Collections nunca imaginadas.

De la misma manera, puede escribir código que opere en interfaces bien conocidas, o interfaces que defina, y otras personas puedan usar su código sin tener que pedirle que respalde sus clases.

Otro uso común de las interfaces es para las devoluciones de llamada. Por ejemplo, java.swing.table.TableCellRenderer , que le permite influir en cómo una tabla Swing muestra los datos en una determinada columna. Implementa esa interfaz, pasa una instancia a JTable y, en algún momento durante el procesamiento de la tabla, se llamará a su código para que haga sus cosas.

Uno de los muchos usos que he leído es su dificultad sin interfaces de uso múltiple de herencia en Java:

 class Animal { void walk() { } .... .... //other methods and finally void chew() { } //concentrate on this } 

Ahora, imagina un caso donde:

 class Reptile extends Animal { //reptile specific code here } //not a problem here 

pero,

 class Bird extends Animal { ...... //other Bird specific code } //now Birds cannot chew so this would a problem in the sense Bird classes can also call chew() method which is unwanted 

Un mejor diseño sería:

 class Animal { void walk() { } .... .... //other methods } 

Animal no tiene el método chew () y en su lugar se coloca en una interfaz como:

 interface Chewable { void chew(); } 

y hacer que la clase Reptile implemente esto y no Birds (ya que Birds no puede masticar):

 class Reptile extends Animal implements Chewable { } 

y en caso de pájaros simplemente:

 class Bird extends Animal { } 

El propósito de las interfaces es el polymorphism , también conocido como sustitución de tipo . Por ejemplo, dado el siguiente método:

 public void scale(IBox b, int i) { b.setSize(b.getSize() * i); } 

Al llamar al método de scale , puede proporcionar cualquier valor que sea de un tipo que implemente la interfaz IBox . En otras palabras, si Rectangle y Square implementan IBox , puede proporcionar un Rectangle o un Square donde se espera un IBox .

Las interfaces permiten que los lenguajes de tipo estático apoyen el polymorphism. Un purista orientado a objetos insistiría en que un lenguaje debería proporcionar herencia, encapsulación, modularidad y polymorphism para ser un lenguaje orientado a objetos con todas las funciones. En lenguaje de tipo dynamic o tipado de pato (como Smalltalk), el polymorphism es trivial; sin embargo, en los lenguajes con tipado estático (como Java o C #), el polymorphism está lejos de ser trivial (de hecho, en la superficie parece estar en desacuerdo con la noción de tipado fuerte).

Déjame demostrar:

En un lenguaje de tipo dynamic (o tipo pato) (como Smalltalk), todas las variables son referencias a objetos (nada menos y nada más). Entonces, en Smalltalk, puedo hacer esto:

 |anAnimal| anAnimal := Pig new. anAnimal makeNoise. anAnimal := Cow new. anAnimal makeNoise. 

Ese código:

  1. Declara una variable local llamada animal (tenga en cuenta que NO especificamos el TYPE de la variable; todas las variables son referencias a un objeto, ni más ni menos).
  2. Crea una nueva instancia de la clase llamada “Cerdo”
  3. Asigna esa nueva instancia de Pig a la variable anAnimal.
  4. Envía el mensaje makeNoise al cerdo.
  5. Repite todo usando una vaca, pero asignándola a la misma variable exacta que el Cerdo.

El mismo código Java se vería así (suponiendo que Pato y Vaca son subclases de Animal:

 Animal anAnimal = new Pig(); duck.makeNoise(); anAnimal = new Cow(); cow.makeNoise(); 

Eso está muy bien, hasta que presentemos Vegetales de clase. Las verduras tienen el mismo comportamiento que Animal, pero no todas. Por ejemplo, tanto Animal como Vegetales podrían crecer, pero claramente las verduras no hacen ruido y los animales no pueden ser cosechados.

En Smalltalk, podemos escribir esto:

 |aFarmObject| aFarmObject := Cow new. aFarmObject grow. aFarmObject makeNoise. aFarmObject := Corn new. aFarmObject grow. aFarmObject harvest. 

Esto funciona perfectamente en Smalltalk porque es de pato (si camina como un pato y grazna como un pato, es un pato). En este caso, cuando se envía un mensaje a un objeto, se realiza una búsqueda en la lista de métodos del receptor, y si se encuentra un método de coincidencia, se llama. Si no, se genera algún tipo de excepción NoSuchMethodError, pero todo se hace en tiempo de ejecución.

Pero en Java, un lenguaje estáticamente tipado, ¿qué tipo podemos asignar a nuestra variable? El maíz necesita heredar de Vegetable, para ayudar a crecer, pero no puede heredar de Animal, porque no hace ruido. La vaca debe heredar de Animal para admitir makeNoise, pero no puede heredar de Vegetable porque no debe implementar harvest. Parece que necesitamos herencia múltiple : la capacidad de heredar de más de una clase. Pero eso resulta ser una característica del lenguaje bastante difícil debido a todos los casos extremos que aparecen (¿qué sucede cuando más de una superclase paralela implementan el mismo método ?, etc.)

A lo largo vienen las interfaces …

Si hacemos clases de animales y vegetales, con cada implementación Growable, podemos declarar que nuestra vaca es animal y nuestro maíz es vegetal. También podemos declarar que tanto Animal como Vegetable son Growable. Eso nos permite escribir esto para hacer crecer todo:

 List list = new ArrayList(); list.add(new Cow()); list.add(new Corn()); list.add(new Pig()); for(Growable g : list) { g.grow(); } 

Y nos permite hacer esto, para hacer ruidos de animales:

 List list = new ArrayList(); list.add(new Cow()); list.add(new Pig()); for(Animal a : list) { a.makeNoise(); } 

La ventaja del lenguaje de pato es que obtiene un polymorphism realmente agradable: todo lo que tiene que hacer una clase para proporcionar un comportamiento es proporcionar el método. Siempre que todos jueguen bien y solo envíen mensajes que coincidan con los métodos definidos, todo está bien. La desventaja es que el tipo de error siguiente no se detecta hasta el tiempo de ejecución:

 |aFarmObject| aFarmObject := Corn new. aFarmObject makeNoise. // No compiler error - not checked until runtime. 

Los lenguajes de tipo estático ofrecen mucho mejor “progtwigción por contrato”, ya que detectarán los dos tipos de error a continuación en tiempo de comstackción:

 // Compiler error: Corn cannot be cast to Animal. Animal farmObject = new Corn(); farmObject makeNoise(); 

 // Compiler error: Animal doesn't have the harvest message. Animal farmObject = new Cow(); farmObject.harvest(); 

Entonces … resumir:

  1. La implementación de la interfaz le permite especificar qué tipos de cosas pueden hacer los objetos (interacción) y la herencia de clases le permite especificar cómo se deben hacer las cosas (implementación).

  2. Las interfaces nos brindan muchos de los beneficios del polymorphism “verdadero”, sin sacrificar la verificación del tipo de comstackdor.

Normalmente, las interfaces definen la interfaz que debe usar (como su nombre lo dice ;-)). Muestra

 public void foo(List l) { ... do something } 

Ahora su función foo acepta ArrayList s, LinkedList s, … no solo un tipo.

Lo más importante en Java es que puedes implementar múltiples interfaces, ¡pero solo puedes extender UNA clase! Muestra:

 class Test extends Foo implements Comparable, Serializable, Formattable { ... } 

es posible pero

 class Test extends Foo, Bar, Buz { ... } 

¡no es!

Su código anterior también podría ser: IBox myBox = new Rectangle(); . Lo importante ahora es que myBox SOLAMENTE contiene los métodos / campos de IBox y no los otros métodos (posiblemente existentes) de Rectangle .

Creo que entiendes todo lo que hacen las interfaces, pero aún no estás imaginando las situaciones en las que una interfaz es útil.

Si está instanciando, usando y liberando un objeto todo dentro de un scope estrecho (por ejemplo, dentro de una llamada a un método), una interfaz realmente no agrega nada. Como notó, la clase concreta es conocida.

Donde las interfaces son útiles es cuando un objeto necesita ser creado en un lugar y devuelto a una persona que llama que puede no interesarse por los detalles de la implementación. Cambiemos su ejemplo de IBox a una Forma. Ahora podemos tener implementaciones de Shape como Rectangle, Circle, Triangle, etc., las implementaciones de los métodos getArea () y getSize () serán completamente diferentes para cada clase concreta.

Ahora puede usar una fábrica con una variedad de métodos createShape (params) que devolverán una forma apropiada dependiendo de los parámetros pasados. Obviamente, la fábrica sabrá sobre qué tipo de forma se está creando, pero la persona que llama no tendrá para importar si es un círculo, un cuadrado, etc.

Ahora, imagine que tiene una variedad de operaciones que debe realizar en sus formas. Tal vez necesite ordenarlos por área, configurarlos a un tamaño nuevo y luego mostrarlos en una IU. Las Formas son todas creadas por la fábrica y luego pueden pasarse fácilmente a las clases Clasificador, Clasificador y Visualización. Si necesita agregar una clase hexagonal en algún momento en el futuro, no tiene que cambiar nada más que la fábrica. Sin la interfaz, agregar otra forma se convierte en un proceso muy complicado.

Podrías hacerlo

 Ibox myBox = new Rectangle(); 

de esa manera estás usando este objeto como Ibox y no te importa que sea realmente Rectangle .

¿POR QUÉ INTERFAZ ??????

Comienza con un perro En particular, un pug .

El pug tiene varios comportamientos:

 public class Pug { private String name; public Pug(String n) { name = n; } public String getName() { return name; } public String bark() { return "Arf!"; } public boolean hasCurlyTail() { return true; } } 

Y tienes un Labrador, que también tiene un conjunto de comportamientos.

 public class Lab { private String name; public Lab(String n) { name = n; } public String getName() { return name; } public String bark() { return "Woof!"; } public boolean hasCurlyTail() { return false; } } 

Podemos hacer algunos pugs y laboratorios:

 Pug pug = new Pug("Spot"); Lab lab = new Lab("Fido"); 

Y podemos invocar sus comportamientos:

 pug.bark() -> "Arf!" lab.bark() -> "Woof!" pug.hasCurlyTail() -> true lab.hasCurlyTail() -> false pug.getName() -> "Spot" 

Digamos que tengo un criadero de perros y necesito hacer un seguimiento de todos los perros que estoy albergando. Necesito almacenar mis pugs y labradores en matrices separadas :

 public class Kennel { Pug[] pugs = new Pug[10]; Lab[] labs = new Lab[10]; public void addPug(Pug p) { ... } public void addLab(Lab l) { ... } public void printDogs() { // Display names of all the dogs } } 

Pero esto claramente no es óptimo. Si quiero alojar algunos caniches , también, tengo que cambiar mi definición Kennel para agregar una matriz de caniches. De hecho, necesito una matriz separada para cada tipo de perro.

Percepción: tanto los pugs como los labradores (y los caniches) son tipos de perros y tienen el mismo conjunto de comportamientos. Es decir, podemos decir (para los fines de este ejemplo) que todos los perros pueden ladrar, tener un nombre y pueden tener o no una cola rizada. Podemos usar una interfaz para definir lo que todos los perros pueden hacer, pero dejar que los perros específicos implementen esos comportamientos particulares. La interfaz dice “aquí están las cosas que todos los perros pueden hacer”, pero no dice cómo se hace cada comportamiento.

 public interface Dog { public String bark(); public String getName(); public boolean hasCurlyTail(); } 

Luego modifico ligeramente las clases de Pug y Lab para implementar las conductas de Perro. Podemos decir que un Pug es un perro y un laboratorio es un perro.

 public class Pug implements Dog { // the rest is the same as before } public class Lab implements Dog { // the rest is the same as before } 

Todavía puedo instanciar Pugs and Labs como lo hacía antes, pero ahora también tengo una nueva forma de hacerlo:

 Dog d1 = new Pug("Spot"); Dog d2 = new Lab("Fido"); 

Esto dice que d1 no es solo un perro, es específicamente un Pug. Y d2 también es un perro, específicamente un laboratorio. Podemos invocar los comportamientos y funcionan como antes:

 d1.bark() -> "Arf!" d2.bark() -> "Woof!" d1.hasCurlyTail() -> true d2.hasCurlyTail() -> false d1.getName() -> "Spot" 

Aquí es donde todo el trabajo extra da sus frutos. La clase Kennel se vuelve mucho más simple. Solo necesito una matriz y un método AddDog. Ambos funcionarán con cualquier objeto que sea un perro; es decir, objetos que implementan la interfaz Dog.

 public class Kennel { Dog[] dogs = new Dog[20]; public void addDog(Dog d) { ... } public void printDogs() { // Display names of all the dogs } } 

He aquí cómo usarlo:

 Kennel k = new Kennel(); Dog d1 = new Pug("Spot"); Dog d2 = new Lab("Fido"); k.addDog(d1); k.addDog(d2); k.printDogs(); 

La última statement mostraría: Spot Fido

Una interfaz le brinda la posibilidad de especificar un conjunto de comportamientos que todas las clases que implementan la interfaz compartirán en común. En consecuencia, podemos definir variables y colecciones (como matrices) que no tienen que saber de antemano qué tipo de objeto específico albergarán, solo que contendrán objetos que implementan la interfaz.

Interfaces

Hay una serie de situaciones en la ingeniería de software cuando es importante que grupos dispares de progtwigdores acuerden un “contrato” que explica cómo interactúa su software. Cada grupo debe poder escribir su código sin ningún conocimiento de cómo se escribe el código del otro grupo. En términos generales, las interfaces son tales contratos.

Por ejemplo, imagina una sociedad futurista donde los autos robóticos controlados por computadora transportan pasajeros a través de las calles de la ciudad sin un operador humano. Los fabricantes de automóviles escriben software (Java, por supuesto) que opera el automóvil: detener, iniciar, acelerar, girar a la izquierda, y así sucesivamente. Otro grupo industrial, los fabricantes de instrumentos electrónicos de orientación, fabrican sistemas informáticos que reciben datos de posición de GPS (Sistema de Posicionamiento Global) y transmisión inalámbrica de las condiciones del tráfico y usan esa información para conducir el automóvil.

Los fabricantes de automóviles deben publicar una interfaz estándar de la industria que detalla los métodos que se pueden invocar para mover el automóvil (cualquier automóvil, de cualquier fabricante). Los fabricantes de orientación pueden escribir software que invoque los métodos descritos en la interfaz para controlar el automóvil. Ninguno de los grupos industriales necesita saber cómo se implementa el software del otro grupo. De hecho, cada grupo considera que su software es altamente propietario y se reserva el derecho de modificarlo en cualquier momento, siempre y cuando siga cumpliendo con la interfaz publicada.

Un excelente ejemplo de cómo se usan las interfaces es en el marco de Colecciones. Si escribe una función que toma una List , entonces no importa si el usuario HashList un Vector o una lista de ArrayList o una lista HashList o lo que sea. Y puede pasar esa List a cualquier función que requiera una interfaz Collection o Iterable también.

Esto hace que funciones como Collections.sort(List list) posibles, independientemente de cómo se implemente la List .

Esta es la razón por la que los patrones de fábrica y otros patrones creacionales son tan populares en Java. Tiene razón en que, sin ellos, Java no proporciona un mecanismo listo para usar que facilite la abstracción de la creación de instancias. Aún así, obtienes abstracción en todas partes donde no creas un objeto en tu método, que debería ser la mayor parte de tu código.

Por otro lado, generalmente animo a las personas a no seguir el mecanismo “IRealname” para nombrar interfaces. Eso es algo de Windows / COM que pone un pie en la tumba de la notación húngara y realmente no es necesario (Java ya está fuertemente tipado, y el objective de tener interfaces es tenerlas tan indistinguibles como sea posible de los tipos de clase).

No olvides que en una fecha posterior puedes tomar una clase existente y hacer que implemente IBox , y luego estará disponible para todo tu código compatible con la caja.

Esto se vuelve un poco más claro si las interfaces se pueden nombrar -able . p.ej

 public interface Saveable { .... public interface Printable { .... 

etc. (Los esquemas de nombres no siempre funcionan, por ejemplo, no estoy seguro de que Boxable sea ​​apropiado aquí)

el único propósito de las interfaces es asegurarse de que la clase que implementa una interfaz tenga los métodos correctos como se describe en una interfaz. ¿O hay algún otro uso de interfaces?

Estoy actualizando la respuesta con las nuevas características de la interfaz, que se han introducido con la versión java 8 .

Desde la página de documentación de Oracle en el resumen de la interfaz :

Una statement de interfaz puede contener

  1. firmas de métodos
  2. métodos predeterminados
  3. métodos estáticos
  4. definiciones constantes.

Los únicos métodos que tienen implementaciones son los métodos predeterminados y estáticos.

Usos de la interfaz :

  1. Para definir un contrato
  2. Para vincular las clases no relacionadas con tiene una capacidad (por ejemplo, las clases que implementan la interfaz Serializable pueden o no tener ninguna relación entre ellas, excepto la implementación de esa interfaz
  3. Para proporcionar una implementación intercambiable , por ejemplo, un patrón de estrategia
  4. Los métodos predeterminados le permiten agregar nuevas funcionalidades a las interfaces de sus bibliotecas y garantizar la compatibilidad binaria con el código escrito para versiones anteriores de esas interfaces
  5. Organice métodos de ayuda en sus bibliotecas con métodos estáticos (puede mantener métodos estáticos específicos para una interfaz en la misma interfaz en lugar de en una clase separada)

Algunas preguntas relacionadas de SE con respecto a la diferencia entre clase abstracta e interfaz y casos de uso con ejemplos de trabajo:

¿Cuál es la diferencia entre una interfaz y una clase abstracta?

¿Cómo debería haber explicado la diferencia entre una interfaz y una clase abstracta?

Eche un vistazo a la página de documentación para comprender las nuevas características agregadas en java 8: métodos predeterminados y métodos estáticos .

El propósito de las interfaces es la abstracción o el desacoplamiento de la implementación.

Si introduce una abstracción en su progtwig, no le importan las posibles implementaciones. Le interesa saber qué puede hacer y no cómo y utiliza una interface para express esto en Java.

Si tiene CardboardBox y HtmlBox (ambos implementan IBox), puede pasarlos a cualquier método que acepte un IBox. Aunque ambos son muy diferentes y no completamente intercambiables, los métodos que no se preocupan por “abrir” o “cambiar el tamaño” todavía pueden usar sus clases (tal vez porque les importa cuántos píxeles se necesitan para mostrar algo en una pantalla).

Interfaces donde se agregó un fetativo a Java para permitir herencia múltiple. Los desarrolladores de Java, aunque me di cuenta de que tener herencia múltiple era una característica “peligrosa”, es por eso que surgió la idea de una interfaz.

la herencia múltiple es peligrosa porque puede tener una clase como la siguiente:

class Box{ public int getSize(){ return 0; } public int getArea(){ return 1; } } class Triangle{ public int getSize(){ return 1; } public int getArea(){ return 0; } } class FunckyFigure extends Box, Triable{ // we do not implement the methods we will used the inherited ones }
class Box{ public int getSize(){ return 0; } public int getArea(){ return 1; } } class Triangle{ public int getSize(){ return 1; } public int getArea(){ return 0; } } class FunckyFigure extends Box, Triable{ // we do not implement the methods we will used the inherited ones } 

¿Cuál sería el método que debería llamarse cuando usemos

FunckyFigure.GetArea();
FunckyFigure.GetArea(); 

Todos los problemas se resuelven con interfaces, porque sabes que puedes ampliar las interfaces y que no tienen métodos de clasificación … por supuesto, el comstackdor es bueno y te dice si no has implementado un método, pero me gusta pensar que es un efecto secundario de una idea más interesante.

Aquí está mi comprensión de la ventaja de la interfaz. Corrígeme si estoy equivocado. Imagina que estamos desarrollando SO y otro equipo está desarrollando los controladores para algunos dispositivos. Así que hemos desarrollado una interfaz StorageDevice. Tenemos dos implementaciones (FDD y HDD) proporcionadas por otro equipo de desarrolladores.

Luego tenemos una clase OperatingSystem que puede invocar métodos de interfaz como saveData al pasar una instancia de la clase implementada en la interfaz StorageDevice.

La ventaja aquí es que no nos importa la implementación de la interfaz. El otro equipo hará el trabajo implementando la interfaz StorageDevice.

 package mypack; interface StorageDevice { void saveData (String data); } class FDD implements StorageDevice { public void saveData (String data) { System.out.println("Save to floppy drive! Data: "+data); } } class HDD implements StorageDevice { public void saveData (String data) { System.out.println("Save to hard disk drive! Data: "+data); } } class OperatingSystem { public String name; StorageDevice[] devices; public OperatingSystem(String name, StorageDevice[] devices) { this.name = name; this.devices = devices.clone(); System.out.println("Running OS " + this.name); System.out.println("List with storage devices available:"); for (StorageDevice s: devices) { System.out.println(s); } } public void saveSomeDataToStorageDevice (StorageDevice storage, String data) { storage.saveData(data); } } public class Main { public static void main(String[] args) { StorageDevice fdd0 = new FDD(); StorageDevice hdd0 = new HDD(); StorageDevice[] devs = {fdd0, hdd0}; OperatingSystem os = new OperatingSystem("Linux", devs); os.saveSomeDataToStorageDevice(fdd0, "blah, blah, blah..."); } }