Perché utilizzare un'interfaccia quando la classe può implementare direttamente le funzioni? [duplicare]

41

Come la maggior parte dei docenti, la mia java facoltà ha introdotto un'interfaccia senza spiegarne o menzionarne l'uso pratico. Ora immagino che le interfacce abbiano un uso molto specifico, ma non riesco a trovare la risposta.

La mia domanda è: una classe può implementare direttamente le funzioni in un'interfaccia. ad esempio:

interface IPerson{
    void jump(int); 
}

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

Che differenza specifica

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

? make

    
posta Somesh Mukherjee 21.04.2012 - 12:17
fonte

10 risposte

13

Ricorda, Java è pignolo per il tipo.

Estendiamo un po 'il tuo esempio e rinominiamo la classe in 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
}

Si noti che qui abbiamo una varietà di classi, organizzate in una certa gerarchia. Non tutti possono saltare e l'abilità di salto non è universalmente applicabile a nessuna categoria: ogni classe genitore ( Animal , Mammal , Insect , Fish ).

Diciamo che vogliamo tenere una gara di salto. Senza interfacce , dovremmo fare qualcosa di simile:

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

Qui, poiché non esiste una "classe di busta" che contenga tutte le classi che possono saltare, dobbiamo invocarle singolarmente. Ricorda, non possiamo semplicemente avere una matrice Animal[] o Mammal[] , poiché il compilatore non ci permetterà di invocare jump() ; e non tutto Animal s / Mammal s può jump() .

Inoltre, diventa impossibile estenderlo. Diciamo che vuoi aggiungere un'altra classe di salto.

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

Ora, devi modificare competition(.........) per accettare anche un parametro Bob[] . Devi anche modificare tutte le istanze di competition[] per creare il proprio Bob[] s o passare i parametri Bob[] vuoti. E diventa icky.

Se hai usato le interfacce, il tuo metodo di competizione diventa così:

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

Anche questo può essere esteso senza problemi.

In sostanza, le interfacce consentono al compilatore di conoscere il comportamento previsto dell'oggetto - quali metodi / dati può assumere il compilatore. In questo modo possiamo scrivere programmi generali.

Inoltre puoi ereditare più interfacce, non puoi farlo con extends in Java.

Alcuni esempi del mondo reale: il primo che viene in mente sono i gestori di eventi di AWT. Questi ci permettono di passare un metodo come parametro e renderlo un gestore di eventi. Questo funzionerà solo se il compilatore è sicuro che il metodo esiste, e quindi usiamo le interfacce.

    
risposta data 22.04.2012 - 04:26
fonte
93

Inizia con un cane. In particolare, un pug.

Il carlino ha vari comportamenti:

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 tu hai un Labrador, che ha anche una serie di comportamenti.

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

Possiamo creare carlini e laboratori:

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

E possiamo invocare i loro comportamenti:

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

Diciamo che gestisco un canile e ho bisogno di tenere traccia di tutti i cani che sto ospitando. Ho bisogno di conservare i miei carlini e labrador in array separati:

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

Ma chiaramente non è ottimale. Se voglio ospitare anche alcuni barboncini, devo modificare la mia definizione di Kennel per aggiungere un array di Poodles . In effetti, ho bisogno di un array separato per ogni tipo di cane.

Insight: sia i carlini che i labrador (e i barboncini) sono tipi di cani e hanno lo stesso insieme di comportamenti. Cioè, possiamo dire (ai fini di questo esempio) che tutti i cani possono abbaiare, avere un nome e possono o meno avere una coda riccia. Possiamo usare un'interfaccia per definire ciò che tutti i cani possono fare , ma lasciare che siano i tipi specifici di cani a implementare quei comportamenti particolari. L'interfaccia dice "ecco le cose che tutti i cani possono fare" ma non dice come ogni comportamento è fatto.

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

Quindi modifico leggermente le classi Pug e Lab per implementa i comportamenti di Dog . Possiamo dire che un Pug è un Dog e un Lab è 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
}

Posso ancora creare un'istanza di Pug se Lab s come ho fatto in precedenza, ma ora ho anche un nuovo modo per farlo:

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

Questo indica che d1 non è solo un Dog , è specificamente un Pug . E d2 è anche un Dog , in particolare un Lab .

Possiamo invocare i comportamenti e funzionano come prima:

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

Ecco dove tutto il lavoro extra paga. La classe Kennel diventa molto più semplice. Mi serve solo un array e un metodo addDog . Entrambi funzioneranno con qualsiasi oggetto che è un cane; cioè, gli oggetti che implementano l'interfaccia Dog .

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

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

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

Ecco come usarlo:

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

L'ultima istruzione mostrerebbe:

 Spot
 Fido

Un'interfaccia ti dà la possibilità di specificare un insieme di comportamenti che tutte le classi che implementano l'interfaccia condivideranno in comune. Di conseguenza, possiamo definire variabili e collezioni (come gli array) che non devono sapere in anticipo quale tipo di oggetto specifico conserveranno, ma solo che terranno gli oggetti che implementano l'interfaccia.

    
risposta data 22.04.2012 - 00:51
fonte
28

Avere un'interfaccia IPerson ti consente di avere più implementatori ( Man , Woman , Employee ecc ...), ma li tratterai ancora attraverso l'interfaccia in altre classi.

Quindi, in un'altra classe devi semplicemente indicare:

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

Non è necessario disporre di un metodo separato per ciascun implementatore.

    
risposta data 21.04.2012 - 12:21
fonte
14

Lo scopo di un'interfaccia non è quello di spostare o riutilizzare il codice ma altro per garantire che alcuni metodi che operano su una vasta gamma di tipi possano usare questi tipi (che saranno passati a loro come argomenti) nello stesso modo anche anche se potrebbero non avere classi genitore comuni.

L'interfaccia è utilizzata per rendere il contratto - la funzionalità prevista per esistere in una determinata classe.

Ad esempio:

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

Si noti che questo non è come l'ereditarietà in cui l'oggetto di una classe eredita (o sovrascrive) un metodo con lo stesso nome dal genitore. Le classi che implementano un'interfaccia non devono necessariamente discendere dalla stessa classe genitore, ma altre classi che vogliono garantire che un contratto esista, possono chiamare gli stessi metodi sugli oggetti di tutte le classi che implementano l'interfaccia. L'interfaccia garantisce che questo contratto sia disponibile.

    
risposta data 21.04.2012 - 12:44
fonte
3

Penso che tutti gli sviluppatori possano comprendere la tua confusione: non è sempre spiegato bene come cercare di capire come utilizzare le interfacce. Ho iniziato a capire davvero l'uso delle interfacce quando ho iniziato a lavorare come sviluppatore su progetti del mondo reale.

C'è un grande articolo su BlackWasp, che spiega l'uso di un'interfaccia con un brillante esempio: leggi e provalo, mi ha davvero aiutato a capirlo meglio:

link

    
risposta data 21.04.2012 - 12:42
fonte
1

Penso che la tua confusione si traduca in classi e interfacce mal menzionate nell'esempio.

Personalmente ritengo che sarebbe meno complicato se tu definissi l'interfaccia come IJumper . Questa è l'interfaccia che consente alle cose di saltare (come un gruppo distinto che può essere più grande del gruppo Persona).

Se volevi rimanere con la stessa analogia. Vorrei rinominare Persona in EarthPerson. Quindi puoi avere classi alternative che implementano l'interfaccia IPerson come MarsPerson, JupitorPerson ecc.

Tutte queste persone diverse potranno saltare ma il modo in cui salteranno dipenderà da come si sono evolute. Tuttavia, il tuo codice funzionerà per le persone di altri pianeti senza alcuna modifica, dal momento che hai fornito un'interfaccia generica che funziona con IPerson piuttosto che con una persona proveniente da un pianeta esplicito.

Ciò semplifica anche il test del codice.

Se ad esempio l'oggetto Person è molto costoso da creare (cerca tutti i dettagli di una persona nel database di Earth Wide). Quando si esegue il test, non si desidera utilizzare un oggetto Persona reale come dati (richiedere un po 'di tempo per la creazione) e potrebbe cambiare nel tempo e interrompere il test. Ma puoi creare un oggetto TestPerson (che implementa l'interfaccia IPerson) (una simulazione di una persona) e passarlo a qualsiasi interfaccia che accetti una persona e assicurati che faccia la cosa giusta.

    
risposta data 21.04.2012 - 18:38
fonte
1

Ci sono casi d'uso molto interessanti di avere un'interfaccia. Eccone uno:

Supponiamo che sto creando un sistema di monitoraggio del sistema operativo e tu mi dici cosa fare dopo il verificarsi di un determinato evento. Dì quando lo spazio su disco è > 90% pieno o l'utilizzo della cpu è molto alto o quando qualche utente ha effettuato l'accesso ecc. Lo sto ancora monitorando, ma è responsabilità del codice client fornire la funzionalità che ora accade.

Nel mio codice (che è il sistema di monitoraggio del sistema operativo), mi aspetto che tu mi fornisca un oggetto con determinati metodi implementati. Dì un metodo come void OnDiskUsageHigh() ecc. Nel mio codice chiamerei semplicemente questo tuo metodo quando lo spazio su disco diminuisce.

Questo è un meccanismo di callback, e senza l'interfaccia e l'insieme definito di politiche tra te e me il mio codice non sarà in grado di servire un insieme generale di client.

Ecco l'interfaccia che dovresti implementare (cioè creare una classe concreta):

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

E tu inizializzi la mia classe OSMonitoring con un oggetto la cui classe implementa Callback come in

new OSMonitoringTool(new ConcreteCallbackClass());

Dovresti leggere un po 'di più sulla programmazione basata su criteri / contratti.

    
risposta data 21.04.2012 - 13:03
fonte
1

L'utilizzo di un'interfaccia consente di eseguire il cast di un numero di classi diverse come tipo comune, indipendentemente dal modo in cui le singole classi potrebbero essere implementate.

Ecco un esempio rapido e sporco:

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

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

Come nell'esempio, ognuna delle classi implementa l'interfaccia IMovement, direttamente o indirettamente. Tuttavia, la cosa importante da notare è che qualsiasi classe nell'esempio potrebbe essere lanciata come lo stesso tipo di interfaccia comune:

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

Le interfacce ti permettono di introdurre facilmente un nuovo comportamento alle tue classi senza alterare le loro interfacce esistenti. Pertanto un'interfaccia consente il polimorfismo indipendentemente dall'ereditarietà. Questo è molto utile quando stai cercando di gestire un numero di tipi di oggetti completamente diversi in modo simile.

    
risposta data 21.04.2012 - 12:44
fonte
0

Oltre ai commenti degli altri, le interfacce rendono più facile fare test poiché hai chiaramente definito quali parti critiche dell'interfaccia dovrebbero essere per qualsiasi classe di simulazione / stub. TDD dovrebbe fare interfacce dappertutto.

    
risposta data 22.04.2012 - 01:03
fonte
0

Non c'è bisogno di farlo, a meno che tu non voglia essere in grado di avere più di una classe che fornisce quel comportamento.

Un buon esempio è l'interfaccia JDBC Connection che rappresenta una connessione a un database SQL, in cui il programmatore può inviare comandi SQL e ottenere il risultato. Non ti interessa come il driver sottostante parla al database, ma tieni a mente che l'implementazione implementa l'interfaccia di connessione, quindi puoi semplicemente utilizzare qualsiasi connessione che DriverManager ha scelto di darti.

    
risposta data 22.04.2012 - 01:52
fonte

Leggi altre domande sui tag