PDA

Visualizza versione completa : [JAVA] Funzione delle interfacce


Downloader
26-04-2007, 13.52.31
Domanda:

Perché esistono le interfacce??
Qual'è la loro utilità?'


Cioè, passi l'uso per la gestione degli eventi, ma ad esempio qual'è la logica che ha spinto i programmatori sun a rendere ad esempio "ResultSet" (presente nel package sql) una interfaccia anziché una classe??

Che logica c'è stata dietro?


tnx! ;)

Dav82
26-04-2007, 15.49.05
Ciao :)


Le interfacce permettono di dichiarare un insieme di signature di metodi (nome del metodo, tipo di parametro in uscita, parametri in ingresso con il loro tipo). Una interfaccia può essere implementata da una o più classi differenti, che - tramite la direttiva implements - si "impegnano" (pena la non compilazione) a fornire (almeno) i metodi dichiarati nell'interfaccia, rispettando le regole sui tipi (*).

Uno si può chiedere, in effetti, a che caspita servano queste interfacce: non è forse più comodo scrivere direttamente la classe piuttosto che dichiarare la signature dei metodi in un file e la loro implementazione in un altro? A prima vista sì, sembrerebbe più comodo, ma a ben guardare - almeno in alcuni casi - assolutamente no.

Il primo motivo è che una classe può estendere una sola altra classe (almeno in Java, non così in altri linguaggi dove esiste l'ereditarietà multipla), ma può implementare più interfacce. Si tratta quindi di una questione di "modularità", di divisione delle funzionalità: se, dato un insieme di metodi, tutto l'insieme dei metodi fosse contenuto in una classe A, ogni classe interessata a fornire/ridefinire/etc anche uno solo dei metodi di A, dovrebbe implementare anche tutti gli altri metodi. Se invece i vari metodi di A fossero divisi in più classi, si potrebbero scegliere in maniera più fine i metodi da implementare. Questa divisione però fa sì che una stessa classe non possa ridefinirli tutti (può estendere una sola classe), ma solo un sottoinsieme: da qui l'uso delle interfacce.


Un secondo motivo è invece molto più importante: definire un'interfaccia è - con le dovute differenze - come definire un protocollo: si danno delle regole, e chi vuole aderire al protocollo deve rispettare quelle regole. Allo stesso modo, chi vuole implementare un'interfaccia, deve fornire tutti i metodi dichiarati dall'interfaccia.
In questo modo si può definire una base comune, eventualmente adottata/implementata da più classi differenti.

Per esempio si può definire un'interfaccia per gestire un conto bancario, con i metodi apri, chiudi, preleva, deposita, e i relativi parametri. Ora tutti potranno implementare una propria classe (A, B, C, anche in maniera differente una dall'altra, a seconda delle possibili esigenze diverse) che aderisce alle specifiche dell'interfaccia; una classe terza X, sapendo che A, B, C implementano l'interfaccia del conto bancario, può senza timore chiamare metodi di una di queste classi, basandosi su quelli presenti nell'interfaccia.


(NB: il fatto che A e B implementino entrambe il metodo preleva() non dà alcuna garanzia sul fatto che i due metodi A.preleva e B.preleva si comportino allo stesso modo agli effetti esterni: magari uno effettua correttamente il prelievo, l'altro no. Implementare un'interfaccia è una questione sintattica, non semantica, anche se è ragionevole che venga rispettata anche la semantica del metodo)


Pensando a uno stesso servizio fornito da più gestori la convenienza di questo approccio risulta evidente :)



Non per questo però bisogna sempre utilizzare un'interfaccia: se non è necessario stabilire una base comune (eventualmente anche fra parti logicamente differenti di uno stesso progetto, non necessariamente fra progetti differenti) non occorre dichiarare un'interfaccia.


(*) Le regole sui tipi dei parametri sono le stesse che valgono quando si fa l'ovverride di un metodo: i parametri in ingresso devono essere un sopratipo di quelli originali (per esempio un generico numero rispetto a un intero, si è in sostanza "più permissivi"), mentre il parametro di uscita deve essere un sottotipo di quello originale (per esempio un intero al posto di un numero generico, si è in sostanza "meno permissivi"), questo per garantire il corretto funzionamento di altre classi che utilizzano i metodi di cui si è fatto l'override o che si sono implementati rispetto a un'interfaccia.



Ciao :)

Downloader
26-04-2007, 16.02.30
Caspita, super esauriente (Y)


Ottimo, grazie 1000! :)

Downloader
08-05-2007, 16.19.58
Ok, ma c'è ancora na cosetta che non mi è chiara:

come mai alcune interfacce le implemento nella classe con la parola chiave implements mentre ad esempio l'interfaccia ResultSet non viene implementata (quindi è dichiarata come una normalissima variabile) ma posso comunque usare tutti i metodi che contiene??

Dav82
08-05-2007, 22.07.28
Puoi fare un esempio? (con codice tuo e codice che usi, nel caso)
Credo di aver capito cosa intendi, ma non vorrei scrivere un papiro per nulla :p

Downloader
08-05-2007, 23.41.12
Mettiamo il caso che io voglia scrivere una mia interface.
Questa ad esempio:

public interface DownloadersInterface
{
public void metodoUno();
public void metodoDue();
}


Normalmente una interfaccia tramite implements viene implementata in una classe all'interno della quale devono essere ridefiniti tutti i metodi, quindi:


public class ClasseProva implements DownloadersInterface
{
public static void main(String[] args)
{

}

public void metodoUno()
{
System.out.println("Metodo 1");
}

public void metodoDue(){}

}



Ok, adesso però l'interfaccia viene usata in modo totalmente diverso:


public class AltraClasse
{
public metodoDB()
{
.
.
.

ResultSet rs = st.executeQuery("SELECT * FROM tabella");
int i = 0;
String s[] = new String[10];

while(rs.next())
{
s[i] = rs.getString("Nome");
i++;
}
}
}


Ora:
il metodo executeQuery() torna un valore ResultSet ovvero una interfaccia e già qua qualcosa non mi torna.
Cioè se fosse tornato un int, String...sarebbe andato bene, ma un valore ResultSet proprio non riesco ad immaginarmelo.
Ma il succo della questione è: come diavolo si possono usare metodi di una interfaccia (in questo caso next() e getString()) dal momento che questa non è istanziabile e dal momento che non sono stati ridefiniti i metodi visto che non l'abbiamo implementata??


tnx! ;)

Dav82
09-05-2007, 14.29.31
Ok, è come pensavo :)

La domanda è più che lecita, e anche spontanea: tutti martellano con la regola "non puoi istanziare un'interfaccia", "non puoi istanziare un'interfaccia", "chi istanzia un'interfaccia verrà dato in pasto al garbage collector" :D etc etc... che quando uno si vede davanti una cosa così rimane un po' stranito.

In effetti la spiegazione è semplice: la regola non è violata, nessuno istanzia un'interfaccia, ma semplicemente restituisce un oggetto A che è di tipo X, dove X è un'interfaccia. Ciò vuol dire che in generale l'oggetto A sarà di una classe B, anche non nota a priori, ma che per chi se lo ritrova fra le mani, questo avrà disponibili solo e soltanto i metodi dichiarati dall'interfaccia X. Non c'è quindi nessun oggetto che è stato creato con new ResultSet(), semplicemente a te questo oggetto viene mostrato come aderente a una determinata specifica formale.

Questo per rispondere alla prima parte della domanda; la risposta alla seconda parte ora vien da sé.
Quando il metodo executeQuery dell'oggetto st viene invocato, questo costruisce un oggetto - sa lui quale - che appartiene a una classe che implementa l'interfaccia resultSet, e poi te lo restituisce dichiarandotelo di tipo resultSet, e quindi garantendoti su questo oggetto tutti e soli i metodi dichiarati nell'interfaccia. Quando poi tu invochi metodi su quest'oggetto, verranno chiamate in causa le implementazioni che di questi metodi sono state fatte nella classe cui veramente fa capo l'oggetto rs.

Una delle utilità delle interfacce sta proprio qui: chi ha scritto la classe di cui st è istanza ha potuto scegliere come tipo di risultato quello che a lui sembrava più congeniale ed efficiente (un array, una lista ordinata, uno heap, un albero, ...), senza preoccupazioni sul tipo di dato restituito e su possibili differenze da un tipo all'altro, e - cosa oltremodo importante - su eventuali modifiche nella scelta del tipo di risultato restituito: st potrà scegliere una volta un array, una volta un albero, ma tu riceverai sempre un resultSet, e non avrai problemi di cambiamento di gestione del tuo risultato a seconda della scelta di st.


Ora devo scappare, ho qualche altra roba da mettere ma la butto giù quando torno da lezione ;)