¿Por qué usar una interfaz cuando la clase puede implementar directamente las funciones? [duplicar]

41

Como la mayoría de los profesores, mi facultad de Java introdujo la interfaz sin explicar o incluso mencionar su uso práctico. Ahora imagino que las interfaces tienen un uso muy específico, pero parece que no pueden encontrar la respuesta.

Mi pregunta es: una clase puede implementar directamente las funciones en una interfaz. por ejemplo:

interface IPerson{
    void jump(int); 
}

class Person{
int name;
    void jump(int height){
        //Do something here
    }
}

¿Qué diferencia específica hace

class Person implements IPerson{
    int name;
    void jump(int height){
        //Do something here
    }
}

? hacer

    
pregunta Somesh Mukherjee 21.04.2012 - 12:17

10 respuestas

13

Recuerda, Java es meticuloso sobre el tipo.

Extendamos un poco tu ejemplo y cambiemos el nombre de la clase a Jumpable .

interface Jumpable {
    void jump(int);
}

class Person extends Mammal implements Jumpable {
    //other stuff
    void jump(int howHigh) {
        //jump method
    }
}

class Dog extends Mammal implements Jumpable {
    //other stuff
    void jump(int howHigh) {
        //jump method
        //also make him bark when he jumps
    }
}

class Cat extends Mammal implements Jumpable {
    //other stuff
    void jump(int howHigh) {
        //jump method
        //make it stretch its legs as well
    }
}

class FlyingFish extends Fish implements Jumpable {
    //other stuff
    void jump(int howHigh) {
        //jump method
        //make it come out of water
    }
}

class Mantis extends Insect implements Jumpable {
    //other stuff
    void jump(int howHigh) {
        //jump method
        //make it come out of water
    }
}

class Ant extends Insect { //Cannot jump
    //other stuff
}

class Whale extends Mammal { //Cannot jump (hopefully)
    //other stuff
}

Tenga en cuenta que aquí tenemos una variedad de clases, organizadas en alguna jerarquía. No todos pueden saltar, y la habilidad de salto no es universalmente aplicable a ninguna categoría: cada clase principal ( Animal , Mammal , Insect , Fish ).

Digamos que queremos celebrar una competición de saltos. Sin interfaces , tendríamos que hacer algo como esto:

void competition(
    Person[] pCompetitors,
    Dog[] dCompetitors,
    Cat[] cCompetitors,
    FlyingFish[] fCompetitors,
    Mantis[] mCompetitors
) {
    for(int i=0; i<pCompetitors.length; i++) {
        pCompetitors[i].jump((int)Math.rand() * 10);
    }
    //Do the same for ALL the other arrays.
}

Aquí, ya que no hay una "clase envolvente" que contenga todas las clases que pueden saltar, tenemos que invocarlas individualmente. Recuerde, no podemos tener una matriz Animal[] o Mammal[] , ya que el compilador no nos permitirá invocar jump() ; y no todos Animal s / Mammal s pueden jump() .

Además, esto se vuelve imposible de extender. Digamos que quieres agregar otra clase de salto.

class Bob extends Animal { //Bob is NOT a human. Bob is something....else....
    void jump(int howHigh) {
        //...
    }
}

Ahora, tienes que modificar competition(.........) para aceptar también un parámetro Bob[] . También tiene que modificar todas las instancias de competition[] para crear su propio Bob[] s, o pasar los parámetros Bob[] vacíos. Y se pone icky.

Si usaste interfaces, tu método de competencia se convierte así en:

void competition(Jumpable[] j) {
    for(int i=0; i<j.length; i++) {
        j[i].jump((int)Math.rand() * 10);
    }
}

Esto también se puede extender sin problemas.

Básicamente, las interfaces le permiten al compilador conocer el comportamiento esperado del objeto, qué métodos / datos puede asumir el compilador. De esa manera podemos escribir programas generales.

También puedes heredar múltiples interfaces, no puedes hacer eso con extends en Java.

Algunos ejemplos del mundo real: el primero que viene a la mente son los controladores de eventos de AWT. Esto nos permite pasar un método como parámetro y convertirlo en un controlador de eventos. Esto solo funcionará si el compilador está seguro de que el método existe y, por lo tanto, usamos interfaces.

    
respondido por el Manishearth 22.04.2012 - 04:26
93

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"
lab.getName()        -> "Fido"

Digamos que dirijo una perrera y necesito hacer un seguimiento de todos los perros que albergo. 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 también quiero alojar algunos caniches, debo cambiar mi definición Kennel para agregar una matriz de Poodles . De hecho, necesito una matriz separada para cada tipo de perro.

Insight: tanto los pug como los labradores (y los poodles) son tipos de perros y tienen el mismo conjunto de comportamientos. Es decir, podemos decir (a los efectos 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 dejarlo en manos de los tipos específicos de perros para implementar esos comportamientos particulares. La interfaz dice "aquí están las cosas que todos los perros pueden hacer", pero no dice cómo se realiza cada comportamiento.

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

Luego modifico ligeramente las clases Pug y Lab para implementar los comportamientos Dog . Podemos decir que un Pug es un Dog y un Lab es un Dog .

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 crear una instancia de Pug sy Lab s como lo hice anteriormente, 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 Dog , es específicamente un Pug . Y d2 también es un Dog , específicamente un Lab .

Podemos invocar los comportamientos y funcionan como antes:

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

Aquí es donde todo el trabajo extra se amortiza. La clase Kennel se vuelve mucho más simple. Solo necesito una matriz y un método addDog . Ambos trabajarán con cualquier objeto que sea un perro; es decir, los 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 declaración se mostraría:

 Spot
 Fido

Una interfaz le brinda la capacidad 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 mantendrán, solo que contendrán objetos que implementan la interfaz.

    
respondido por el Barry Brown 22.04.2012 - 00:51
28

Tener una interfaz IPerson le permite tener varios implementadores ( Man , Woman , Employee etc ...), pero aún así tratarlos a todos a través de la interfaz en otras clases.

Entonces, en otra clase simplemente indicas:

void myMethod(IPerson person, Integer howHigh)
{
   person.jump(howHigh);
}

No es necesario que tenga un método separado para cada implementador.

    
respondido por el Oded 21.04.2012 - 12:21
14

El propósito de una interfaz no es reubicar o reutilizar el código, sino más para garantizar que algunos métodos que operan en una amplia gama de tipos puedan usar estos tipos (que se les pasarán como argumentos) de la misma manera, incluso aunque pueden no tener clases para padres comunes.

La interfaz se utiliza para realizar el contrato: la funcionalidad que se espera que exista en una clase determinada.

Por ejemplo:

interface Transmittable {
     public byte[] toBytes();
}

class Person implements Transmittable {
     public byte[] toBytes() {
         return this.name.getBytes()
    }
}

class Animal implements Transmittable {
     public byte[] toBytes() {
            return this.typeOfAnimal.getBytes()
    }
}

class NetworkTransmitter {
     public void transmit(Transmittable object) {
          byte data[] = object.toBytes();
         //do something....
     } 
}

class TestExample {
    public static void main(String args[]) {
           NetworkTransmitter trobj = new NetworkTransmitter();
           trobj.transmit(new Person());
           trobj.transmit(new Animal());
   }
}

Tenga en cuenta que esto no es una herencia donde el objeto de una clase hereda (o invalida) un método del mismo nombre del padre. Las clases que implementan una interfaz no tienen por qué ser descendientes de la misma clase principal, pero otras clases que desean asegurarse de que existe un contrato pueden llamar a los mismos métodos en los objetos de todas las clases que implementan la interfaz. La interfaz garantiza que este contrato esté disponible.

    
respondido por el mwallace 21.04.2012 - 12:44
3

Creo que todos los desarrolladores pueden entender su confusión: tratar de entender el uso de interfaces no siempre se explica bien. Comencé a entender realmente el uso de las interfaces cuando comencé a trabajar como desarrollador en proyectos del mundo real.

Hay un gran artículo en BlackWasp, que explica el uso de una interfaz con un ejemplo brillante: lea y pruébelo, realmente me ayudó a entenderlo mejor:

enlace

    
respondido por el Dal 21.04.2012 - 12:42
1

Creo que su confusión se reduce a clases e interfaces mal nombradas en el ejemplo.

Personalmente, creo que sería menos confuso si nombrara mejor la interfaz como IJumper . Esta es la interfaz que permite que las cosas salten (como un grupo distinto que puede ser más grande que el grupo de personas).

Si quisieras quedarte con la misma analogía. Me gustaría cambiar el nombre de persona a EarthPerson. Luego, puede tener clases alternativas que implementan la interfaz IPerson, como MarsPerson, JupitorPerson, etc.

Todas estas personas diferentes podrán saltar, pero su forma de saltar dependerá de cómo evolucionaron. Sin embargo, su código funcionará para personas de otros planetas sin ninguna modificación, ya que proporcionó una interfaz genérica que funciona con IPerson en lugar de una persona de un planeta explícito.

Esto también hace que sea más fácil probar tu código.

Si, por ejemplo, el objeto Person es muy costoso de crear (busca todos los detalles de una persona en la base de datos de Earth Wide). Al realizar la prueba, no desea utilizar un objeto Persona real como datos (demorar un poco en crearlo) y puede cambiar con el tiempo y romper su prueba. Pero puede crear un objeto TestPerson (que implementa la interfaz IPerson) (un simulacro de persona) y pasarlo a cualquier interfaz que acepte a una persona y asegúrese de que haga lo correcto.

    
respondido por el Martin York 21.04.2012 - 18:38
1

Hay casos de uso muy interesantes de tener una interfaz. Aquí hay uno:

Supongamos que estoy creando un sistema de monitoreo del sistema operativo y usted me dice qué hacer después de que ocurra un evento determinado. Decir cuándo el espacio en disco está > 90% lleno o el uso de la CPU es muy alto o cuando algún usuario ha iniciado sesión, etc. Todavía lo estoy controlando, pero es responsabilidad del código del cliente proporcionar la funcionalidad de qué sucede ahora. p>

En mi código (que es el sistema de monitoreo del sistema operativo), esperaré que me proporcione un objeto con ciertos métodos implementados. Diga algún método como void OnDiskUsageHigh() , etc. En mi código, simplemente llamaría a este método tuyo cuando el espacio en disco disminuye.

Este es un mecanismo de devolución de llamada, y sin la interfaz y el conjunto definido de políticas entre usted y yo, mi código no podrá atender a un conjunto general de clientes.

Aquí está la interfaz que debe implementar (es decir, hacer una clase concreta):

interface Callback { 
  void OnDiskUsageHigh();
  void OnCpuUsageHigh();
  ...
} 

Y usted inicializa mi clase OSMonitoring con un objeto cuya clase implementa Callback como en

new OSMonitoringTool(new ConcreteCallbackClass());

Debería leer más sobre la programación basada en políticas / contratos.

    
respondido por el Fanatic23 21.04.2012 - 13:03
1

El uso de una interfaz le permite emitir varias clases diferentes como un tipo común, independientemente de cómo se implementen las clases individuales.

Aquí hay un ejemplo rápido y sucio:

class Puppet : IMovement { ... }
class Vehicle : IMovement { ... }
class Car : Vehicle { ... }
class Bicycle : Vehicle { ... }

class Person : IMovement { ... }
class Child : Person { ... }
class Adult : Person { ... }

Según el ejemplo, cada una de las clases implementa la interfaz de movimiento, ya sea directa o indirectamente. Sin embargo, lo importante a tener en cuenta es que cualquiera de las clases en el ejemplo se podría convertir como el mismo tipo de interfaz común:

((IMovement)puppet).Move()
((IMovement)car).Move()
((IMovement)child).Move()

Las interfaces le permiten introducir fácilmente nuevos comportamientos en sus clases sin alterar sus interfaces existentes. Así, una interfaz permite el polimorfismo independiente de la herencia. Esto es muy útil cuando intentas manejar un número de tipos de objetos completamente diferentes de una manera similar.

    
respondido por el S.Robins 21.04.2012 - 12:44
0

Además de los comentarios de otros, las interfaces facilitan las pruebas, ya que ha definido claramente cuáles deberían ser las partes críticas de la interfaz para cualquier clase de simulacro / código auxiliar. TDD debería estar haciendo interfaces en todo el lugar.

    
respondido por el anon 22.04.2012 - 01:03
0

No es necesario que lo hagas, a menos que quieras tener más de una clase que proporcione ese comportamiento.

Un buen ejemplo es la interfaz JDBC Connection , que representa una conexión a una base de datos SQL, donde usted como programador puede enviar comandos SQL y recuperar el resultado. No le importa cómo el controlador subyacente se comunica con la base de datos, pero sí le importa que la implementación implemente la interfaz de Conexión, por lo que solo puede usar cualquier conexión que el DriverManager elija darle.

    
respondido por el user1249 22.04.2012 - 01:52

Lea otras preguntas en las etiquetas