Por que usar uma interface quando a classe pode implementar diretamente as funções? [duplicado]

41

Como a maioria dos professores, minha faculdade de Java introduziu a interface sem explicar ou mesmo mencionar seu uso prático. Agora imagino que as interfaces têm um uso muito específico, mas não consigo encontrar a resposta.

Minha pergunta é: uma classe pode implementar diretamente as funções em uma interface. por exemplo:

interface IPerson{
    void jump(int); 
}

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

Qual diferença específica

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

fazer

    
por Somesh Mukherjee 21.04.2012 / 12:17
fonte

10 respostas

13

Lembre-se de que o Java é meticuloso em relação ao tipo.

Vamos estender um pouco seu exemplo e renomear a classe para 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
}

Note que aqui temos uma variedade de classes, organizadas em alguma hierarquia. Nem todos eles podem pular, e a habilidade de pular não é universalmente aplicável a qualquer categoria - cada classe pai ( Animal , Mammal , Insect , Fish ).

Digamos que queremos realizar uma competição de saltos. Sem interfaces , teríamos que fazer algo assim:

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.
}

Aqui, como não há "classe envolvente" que contenha todas as classes que podem pular, temos que invocá-las individualmente. Lembre-se, não podemos apenas ter uma matriz Animal[] ou Mammal[] , pois o compilador não nos permitirá chamar jump() ; e nem todos Animal s / Mammal s podem jump() .

Além disso, isso se torna impossível de se estender. Vamos dizer que você quer adicionar outra aula de salto.

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

Agora, você precisa modificar competition(.........) para aceitar também um parâmetro Bob[] . Você também precisa modificar todas as instâncias de competition[] para criar seus próprios Bob[] s ou passar os parâmetros Bob[] vazios. E fica nojento.

Se você usou interfaces, seu método de competição se torna assim:

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

Isso pode ser estendido sem problemas também.

Basicamente, as interfaces permitem que o compilador conheça o esperado comportamento do objeto - que métodos / dados o compilador pode assumir. Dessa forma, podemos escrever programas gerais.

Além disso, você pode herdar várias interfaces, você não pode fazer isso com extends em Java.

Alguns exemplos do mundo real: O primeiro que vem à mente é o manipulador de eventos do AWT. Isso nos permite passar um método como um parâmetro e torná-lo um manipulador de eventos. Isso só funcionará se o compilador tiver certeza de que o método existe e, portanto, usarmos interfaces.

    
por 22.04.2012 / 04:26
fonte
93

Começa com um cachorro. Em particular, um pug.

O pug tem vários comportamentos:

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;
    }
}

E você tem um Labrador, que também tem um conjunto de comportamentos.

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 criar alguns pugs e laboratórios:

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

E podemos invocar seus comportamentos:

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

Digamos que eu corra um canil e eu preciso acompanhar todos os cães que estou abrigando. Eu preciso armazenar meus pugs e labradores em matrizes 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
    }
}

Mas isso claramente não é o ideal. Se eu quiser também abrigar alguns poodles, preciso alterar minha definição de Kennel para adicionar uma matriz de Poodles . Na verdade, preciso de um array separado para cada tipo de cachorro.

Insight: tanto pugs como labradors (e poodles) são tipos de cães e têm o mesmo conjunto de comportamentos. Ou seja, podemos dizer (para os propósitos deste exemplo) que todos os cães podem latir, ter um nome e podem ou não ter uma cauda encaracolada. Podemos usar uma interface para definir o que todos os cães podem fazer , mas deixar que os tipos específicos de cães implementem esses comportamentos específicos. A interface diz "aqui estão as coisas que todos os cães podem fazer", mas não diz como cada comportamento é feito.

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

Depois, altero ligeiramente as classes Pug e Lab para implementar os comportamentos Dog . Podemos dizer que um Pug é um Dog e um Lab é um 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
}

Ainda posso instanciar Pug s e Lab s como anteriormente, mas agora também tenho uma nova maneira de fazer isso:

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

Isso diz que d1 não é apenas Dog , mas especificamente Pug . E d2 também é Dog , especificamente Lab .

Podemos invocar os comportamentos e eles funcionam como antes:

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

Aqui é onde todo o trabalho extra vale a pena. A classe Kennel se torna muito mais simples. Eu preciso apenas de um array e um método addDog . Ambos funcionarão com qualquer objeto que seja um cão; isto é, objetos que implementam a interface Dog .

public class Kennel 
{
    Dog[] dogs = new Dog[20];

    public void addDog(Dog d)
    {
        ...
    }

    public void printDogs()
    {
        // Display names of all the dogs
    }
 }

Veja como usá-lo:

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

A última declaração seria exibida:

 Spot
 Fido

Uma interface permite que você especifique um conjunto de comportamentos que todas as classes que implementam a interface compartilharão em comum. Conseqüentemente, podemos definir variáveis e coleções (como arrays) que não precisam saber de antemão que tipo de objeto específico elas manterão, apenas que elas conterão objetos que implementam a interface.

    
por 22.04.2012 / 00:51
fonte
28

Ter uma interface IPerson permite que você tenha vários implementadores ( Man , Woman , Employee etc ...), mas ainda os trata por meio da interface em outras classes.

Então, em outra classe, você simplesmente afirma:

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

Você não precisa ter um método separado para cada implementador.

    
por 21.04.2012 / 12:21
fonte
14

O propósito de uma interface não é realocar ou reutilizar o código, mas mais para garantir que alguns métodos que operam em uma ampla gama de tipos possam usar esses tipos (que serão passados para eles como argumentos) exatamente da mesma maneira embora possam não ter classes parentais comuns.

A interface é usada para fazer o contrato - a funcionalidade esperada para existir em uma determinada classe.

Por exemplo:

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());
   }
}

Observe que isso não é como uma herança em que o objeto de uma classe herda (ou substitui) um método com o mesmo nome do pai. As classes que implementam uma interface não precisam ser descendentes da mesma classe pai, mas outras classes que desejam assegurar que um contrato exista, podem chamar os mesmos métodos em objetos de todas as classes que implementam a interface. A interface garante que este contrato esteja disponível.

    
por 21.04.2012 / 12:44
fonte
3

Acho que todos os desenvolvedores podem entender sua confusão - tentar entender o uso de interfaces nem sempre é bem explicado. Comecei a realmente entender o uso de interfaces quando comecei a trabalhar como desenvolvedor em projetos do mundo real.

Existe um ótimo artigo no BlackWasp, que explica o uso de uma interface com um exemplo brilhante - leia e experimente, realmente me ajudou a entender melhor:

link

    
por 21.04.2012 / 12:42
fonte
1

Acho que sua confusão se resume a classes e interfaces mal nomeadas no exemplo.

Pessoalmente, acho que seria menos confuso se você nomeasse a interface melhor como IJumper . Essa é a interface que permite que as coisas pulem (como um grupo distinto que pode ser maior que o grupo Person).

Se você quisesse ficar com a mesma analogia. Eu mudaria o nome de Person para EarthPerson. Então você pode ter classes alternativas que implementam a interface IPerson, como MarsPerson, JupitorPerson, etc.

Todas essas pessoas diferentes serão capazes de pular, mas como elas pularão dependerão de como elas evoluíram. No entanto, seu código funcionará para pessoas de outros planetas sem qualquer modificação, já que você forneceu uma interface genérica que funciona com o IPerson em vez de uma pessoa de um planeta explícito.

Isso também facilita o teste do seu código.

Se, por exemplo, o objeto Person for muito caro para criar (ele procura todos os detalhes de uma pessoa no banco de dados do Earth Wide). Ao testar, você não deseja usar um objeto Person real como dados (demore um pouco para criar) e pode mudar com o tempo e interromper seu teste. Mas você pode criar um objeto TestPerson (que implementa a interface IPerson) (uma simulação de uma Pessoa) e passá-la para qualquer interface que aceite uma pessoa e verifique se ela está correta.

    
por 21.04.2012 / 18:38
fonte
1

Existem casos de uso muito interessantes de ter uma interface. Aqui está um:

Suponha que eu esteja criando um sistema de monitoramento de SO e você me diga o que fazer após a ocorrência de um determinado evento. Diga quando o espaço em disco for > 90% cheio ou o uso da cpu estiver muito alto ou quando algum usuário estiver logado, etc. Ainda estou monitorando, mas é responsabilidade do código do cliente fornecer a funcionalidade que acontece agora.

No meu código (que é o sistema de monitoramento do sistema operacional), esperarei que você me forneça um objeto com determinados métodos implementados. Diga algum método como void OnDiskUsageHigh() etc. No meu código, eu simplesmente chamaria esse método quando o espaço em disco cair.

Este é um mecanismo de retorno de chamada e, sem a interface e o conjunto definido de políticas entre você e eu, meu código não poderá atender a um conjunto geral de clientes.

Aqui está a interface que você deve implementar (ou seja, fazer uma aula concreta):

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

E você inicializa minha OSMonitoring classe com um objeto cuja classe implementa Callback como em

new OSMonitoringTool(new ConcreteCallbackClass());

Você deve ler mais sobre programação baseada em contratos / políticas.

    
por 21.04.2012 / 13:03
fonte
1

O uso de uma interface permite que você conjugue um número de classes diferentes como um tipo comum, independentemente de como as classes individuais possam ser implementadas.

Aqui está um exemplo rápido e sujo:

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

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

Conforme o exemplo, cada uma das classes implementa a interface IMovement, direta ou indiretamente. No entanto, o importante a ser observado é que qualquer uma das classes no exemplo pode ser convertida como o mesmo tipo de interface comum:

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

As interfaces permitem que você introduza facilmente novo comportamento em suas classes sem alterar suas interfaces existentes. Assim, uma interface permite o polimorfismo independente da herança. Isso é muito útil quando você está tentando manipular um número de tipos completamente diferentes de objetos de maneira semelhante.

    
por 21.04.2012 / 12:44
fonte
0

Além dos comentários de outras pessoas, as interfaces facilitam a realização de testes, pois você definiu claramente quais partes críticas da interface devem ser para qualquer classe de simulação / stub. O TDD deve fazer interfaces em todo o lugar.

    
por 22.04.2012 / 01:03
fonte
0

Não há necessidade de fazê-lo, a menos que você queira ter mais de uma classe fornecendo esse comportamento.

Um bom exemplo é a interface JDBC Connection , que representa uma conexão com um banco de dados SQL, na qual você, como programador, pode enviar comandos SQL e recuperar o resultado. Você não se importa como o driver subjacente fala com o banco de dados, mas você se importa que a implementação implemente a interface Connection, para que você possa usar apenas qualquer conexão que o DriverManager escolha dar a você.

    
por 22.04.2012 / 01:52
fonte