CodeGym /Java Blog /Random-IT /Perché abbiamo bisogno di interfacce in Java
John Squirrels
Livello 41
San Francisco

Perché abbiamo bisogno di interfacce in Java

Pubblicato nel gruppo Random-IT
CIAO! Oggi parleremo di un concetto importante in Java: le interfacce. Probabilmente la parola ti è familiare. Ad esempio, la maggior parte dei programmi e dei giochi per computer dispone di interfacce. In senso lato, un'interfaccia è una sorta di "telecomando" che collega due parti interagenti. Un semplice esempio di interfaccia nella vita di tutti i giorni è il telecomando di una TV. Collega due oggetti - una persona e una TV - e svolge diverse attività: alzare o abbassare il volume, cambiare canale e accendere o spegnere la TV. Una parte (la persona) deve accedere all'interfaccia (premere un pulsante sul telecomando) per fare in modo che la seconda parte esegua l'azione. Ad esempio, per far passare la TV al canale successivo. Inoltre, l'utente non t è necessario sapere come è organizzata la TV o come viene implementato internamente il processo di cambio canale. L'unica cosa a cui l'utente ha accesso è l'interfaccia. L'obiettivo principale è quello di ottenere il risultato desiderato. Cosa c'entra questo con la programmazione e Java? Tutto :) La creazione di un'interfaccia è molto simile alla creazione di una normale classe, ma usa invece la parolaclass , indichiamo la parola interfaccia . Diamo un'occhiata all'interfaccia Java più semplice, vediamo come funziona e perché ne avremmo bisogno:

public interface CanSwim {

     public void swim();
}
Abbiamo creato un'interfaccia CanSwim . È un po' come il nostro telecomando, ma con un 'pulsante': il metodo swim() . Ma come si usa questo telecomando? Per fare ciò, dobbiamo implementare un metodo, ovvero il nostro pulsante di controllo remoto. Per utilizzare un'interfaccia, alcune classi nel nostro programma devono implementarne i metodi. Inventiamo una classe i cui oggetti "possono nuotare". Ad esempio, una classe Duck si adatta a:

public class Duck implements CanSwim {

    public void swim() {
        System.out.println("Duck, swim!");
    }

    public static void main(String[] args) {

        Duck duck = new Duck();
        duck.swim();
    }
}
"Cosa vediamo qui? La classe Duck è 'associata' all'interfaccia CanSwim tramite la parola chiave implements . Ricorderete che abbiamo usato un meccanismo simile per associare due classi attraverso l'ereditarietà, ma in quel caso abbiamo usato la parola extends. Per Per chiarezza, possiamo tradurre ' public class Duck implements CanSwim ' letteralmente come: 'La public class Duck implementa l' interfaccia CanSwim ". Ciò significa che una classe associata ad un'interfaccia deve implementare tutti i suoi metodi. Nota: la nostra Duckclasse, proprio come l' CanSwiminterfaccia, ha un swim()metodo e contiene una certa logica.Questo è un requisito obbligatorio.Se ci limitiamo a scriverepublic class Duck implements CanSwimsenza creare un swim()metodo nella Duckclasse, il compilatore ci darà un errore: Duck non è astratto e non sovrascrive il metodo astratto swim() in CanSwim Perché? Perché succede? Se spieghiamo l'errore utilizzando l'esempio della TV, sarebbe come consegnare a qualcuno un telecomando TV con un pulsante "cambia canale" che non può cambiare canale. Puoi premere il pulsante quanto vuoi, ma non funzionerà. Il telecomando non cambia canale da solo: invia solo un segnale al televisore, che implementa il complesso processo di cambio canale. E così è con la nostra papera: deve saper nuotare per poter essere richiamata tramite l' CanSwiminterfaccia. Se non sa come, ilCanSwiml'interfaccia non collega le due parti: la persona e il programma. La persona non potrà utilizzare il swim()metodo per fare una Ducknuotata all'interno del programma. Ora capisci più chiaramente a cosa servono le interfacce. Un'interfaccia descrive il comportamento che devono avere le classi che implementano l'interfaccia. 'Comportamento' è una raccolta di metodi. Se vogliamo creare diversi messenger, la cosa più semplice da fare è creare un'interfaccia Messenger. Di cosa ha bisogno ogni messaggero? A livello base, devono essere in grado di ricevere e inviare messaggi.

public interface Messenger{

     public void sendMessage();

     public void getMessage();
}
Ora possiamo semplicemente creare le nostre classi messenger che implementano l'interfaccia corrispondente. Il compilatore stesso ci "costringerà" a implementarli nelle nostre classi. Telegramma:

public class Telegram implements Messenger {

    public void sendMessage() {

        System.out.println("Sending a Telegram message!");
    }

     public void getMessage() {
         System.out.println("Receiving a Telegram message!");
     }
}
WhatsApp:

public class WhatsApp implements Messenger {

    public void sendMessage() {

        System.out.println("Sending a WhatsApp message!");
    }

     public void getMessage() {
         System.out.println("Reading a WhatsApp message!");
     }
}
Vibro:

public class Viber implements Messenger {

    public void sendMessage() {

        System.out.println("Sending a Viber message!");
    }

     public void getMessage() {
         System.out.println("Receiving a Viber message!");
     }
}
Quali vantaggi offre questo? Il più importante di questi è l'accoppiamento libero. Immagina di progettare un programma che raccolga i dati dei clienti. La Clientclasse ha sicuramente bisogno di un campo per indicare quale specifico messenger sta usando il client. Senza interfacce, questo sembrerebbe strano:

public class Client {

    private WhatsApp whatsApp;
    private Telegram telegram;
    private Viber viber;
}
Abbiamo creato tre campi, ma un client può avere un solo messenger. Solo che non sappiamo quale. Quindi dobbiamo aggiungere ogni possibilità alla classe per poter comunicare con il cliente. Si scopre che uno o due di loro saranno sempre null, del tutto non necessari al programma. È meglio usare invece la nostra interfaccia:

public class Client {

    private Messenger messenger;
}
Questo è un esempio di accoppiamento lento! Invece di specificare una classe messenger specifica nella Clientclasse, indichiamo semplicemente che il client ha un messenger. Quale esattamente verrà determinato durante l'esecuzione del programma. Ma perché abbiamo bisogno di interfacce per questo? Perché sono stati aggiunti alla lingua? Questa è una buona domanda - e la domanda giusta! Non possiamo ottenere lo stesso risultato usando l'eredità ordinaria? La Messengerclasse come padre e Viber, Telegrame WhatsAppcome figli. In effetti, è possibile. Ma c'è un intoppo. Come già sai, Java non ha ereditarietà multipla. Ma c'è il supporto per più interfacce. Una classe può implementare tutte le interfacce che desideri. Immagina di avere una Smartphoneclasse che ne ha unoAppcampo, che rappresenta un'app installata sullo smartphone.

public class Smartphone {

    private App app;
}
Ovviamente un'app e un messenger sono simili, ma sono comunque cose diverse. Possono esserci versioni mobile e desktop di un messenger, ma l'app rappresenta specificamente un'app mobile. Ecco il punto: se usassimo l'ereditarietà, non saremmo in grado di aggiungere un Telegramoggetto alla Smartphoneclasse. Dopotutto, la Telegramclasse non può ereditare contemporaneamente Appe Messenger! E l'abbiamo già fatto ereditare Messengere aggiunto alla Clientclasse. Ma la Telegramclasse può facilmente implementare entrambe le interfacce! Di conseguenza, possiamo dare alla Clientclasse un Telegramoggetto come Messenger, e possiamo darlo alla Smartphoneclasse come App. Ecco come farlo:

public class Telegram implements Application, Messenger {

    // ...methods
}

public class Client {

    private Messenger messenger;

    public Client() {
        this.messenger = new Telegram();
    }
}


public class Smartphone {

    private Application application;

    public Smartphone() {
        this.application = new Telegram();
    }
}
Ora stiamo usando la Telegramclasse come vogliamo. In alcuni punti, funge da App. In altri luoghi, funge da Messenger. Avrete sicuramente già notato che i metodi di interfaccia sono sempre 'vuoti', cioè non hanno implementazione. Il motivo è semplice: l'interfaccia descrive il comportamento, ma non lo implementa. 'Tutti gli oggetti che implementano l' CanSwiminterfaccia devono essere in grado di nuotare': questo è tutto ciò che ci dice l'interfaccia. Il modo specifico in cui nuotano pesci, anatre e cavalli è una domanda per Fish, Duck, eHorseclassi, non l'interfaccia. Proprio come cambiare canale è un compito per la TV. Il telecomando ti dà semplicemente un pulsante per questo. Tuttavia, in Java 8 è apparsa un'aggiunta interessante: i metodi predefiniti. Ad esempio, la tua interfaccia ha 10 metodi. 9 di essi hanno implementazioni diverse in classi diverse, ma uno è implementato uguale per tutti. In precedenza, prima di Java 8, i metodi di interfaccia non avevano alcuna implementazione: il compilatore dava immediatamente un errore. Ora puoi fare qualcosa del genere:

public interface CanSwim {

   public default void swim() {
       System.out.println("Swim!");
   }

   public void eat();

   public void run();
}
Utilizzando la defaultparola chiave, abbiamo creato un metodo di interfaccia con un'implementazione predefinita. Dobbiamo fornire la nostra implementazione per altri due metodi - eat()e run()- in tutte le classi che implementano CanSwim. Non abbiamo bisogno di farlo con il swim()metodo: l'implementazione sarà la stessa in ogni classe. A proposito, ti sei già imbattuto in interfacce in compiti passati, anche se non l'hai notato :) Ecco un vivido esempio: Perché le interfacce sono necessarie in Java - 2hai lavorato con le interfacce Liste ! SetPiù precisamente, hai lavorato con le loro implementazioni — ArrayList, LinkedList, HashSet, ecc. Lo stesso diagramma fornisce chiaramente un esempio in cui una classe implementa più interfacce contemporaneamente. Ad esempio, LinkedListimplementa l' ListandDeque(coda a doppia estremità). Hai familiarità con l' Mapinterfaccia, o meglio, con la sua HashMapimplementazione. A proposito, questo diagramma illustra una caratteristica: le interfacce possono ereditare altre interfacce. L' SortedMapinterfaccia eredita Map, mentre Dequeeredita Queue. Questo è necessario se vuoi mostrare la relazione tra le interfacce, dove un'interfaccia è una versione estesa di un'altra. Consideriamo un esempio con l' Queueinterfaccia. Non abbiamo ancora recensitoQueues, ma è piuttosto semplice e funziona come una normale coda, o fila, in un negozio. Puoi solo aggiungere elementi alla fine della coda e puoi prenderli solo dall'inizio. A un certo punto, gli sviluppatori avevano bisogno di una versione migliorata della coda per aggiungere e prendere elementi a entrambe le estremità. Quindi hanno creato un'interfaccia Deque, che è una coda a doppia estremità. Ha tutti i metodi di una normale coda. Dopotutto, è il genitore della coda a doppia estremità, ma aggiunge anche nuovi metodi.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION