Domanda

durante la programmazione in Java ho praticamente sempre, solo per abitudine, scrivere qualcosa di simile a questo:

public List<String> foo() {
    return new ArrayList<String>();
}

Il più delle volte senza nemmeno pensarci.Ora, la domanda è:devo sempre specificare l'interfaccia come il tipo di ritorno?O è consigliabile utilizzare l'effettiva implementazione dell'interfaccia, e se sì, in quali circostanze?

È ovvio che usando l'interfaccia ha un sacco di vantaggi (che è il motivo c'è).Nella maggior parte dei casi, non importa che cosa concreta attuazione è utilizzato da una funzione di libreria.Ma forse ci sono casi in cui non importa.Per esempio, se so che ci sarà soprattutto l'accesso ai dati della lista in modo casuale, un LinkedList sarebbe un male.Ma se la mia libreria di funzione restituisce solo l'interfaccia, io semplicemente non lo so.Per essere sul sicuro, potrei anche bisogno di copiare l'elenco in modo esplicito ad un ArrayList:

List bar = foo();
List myList = bar instanceof LinkedList ? new ArrayList(bar) : bar;

ma che sembra orribile e i miei colleghi sarebbe probabilmente lynch me nella caffetteria.E giustamente così.

Cosa ne pensate voi ragazzi?Quali sono le vostre linee guida, quando si tende verso l'astratto soluzione, e quando rivelare i dettagli di implementazione per i potenziali guadagni in prestazioni?

È stato utile?

Soluzione

  

Per esempio, se io so che lo farò   accedere in primo luogo i dati nell'elenco   in modo casuale, un LinkedList sarebbe male.   Ma se la mia funzione di libreria solo   restituisce l'interfaccia, semplicemente non lo faccio   conoscere. Per essere sul sicuro potrei   nemmeno bisogno di copiare l'elenco in modo esplicito   oltre a un ArrayList.

Come tutti gli altri hanno detto, è solo che non deve preoccuparsi di come la biblioteca ha implementato la funzionalità, per ridurre l'accoppiamento e aumentando la manutenibilità della biblioteca.

Se si, come un client di libreria, si può dimostrare che l'attuazione sta eseguendo male per il vostro caso d'uso, è possibile contattare il responsabile e discutere circa il miglior percorso da seguire (un nuovo metodo per questo caso o semplicemente cambiando l'implementazione).

Detto questo, il tuo esempio puzza di ottimizzazione prematura.

Se il metodo è o può essere critica, si potrebbe parlare dei dettagli di implementazione nella documentazione.

Altri suggerimenti

Torna l'interfaccia appropriata per nascondere i dettagli implementativi. I vostri clienti dovrebbero preoccuparsi solo di ciò che le vostre offerte di oggetti, non come si implementato. Se si inizia con un ArrayList privato, e decidere in seguito che qualcos'altro (per esempio, LinkedLisk, saltare la lista, ecc) è più appropriato è possibile cambiare l'implementazione senza influenzare i clienti se si torna l'interfaccia. Nel momento in cui si restituisce un tipo concreto l'occasione è persa.

Nella programmazione OO, vogliamo incapsulare il più possibile i dati. Nascondere il più possibile l'effettiva attuazione, astraendo i tipi più in alto possibile.

In questo contesto, vorrei rispondere a solo restituire ciò che è significativo . Lo fa ha un senso a tutti per il valore di ritorno di essere la classe concreta? Aka nel tuo esempio, chiedetevi: sarà qualcuno usare un metodo specifico per LinkedList sul valore di ritorno di foo

  • Se no, basta usare l'interfaccia di livello superiore. E 'molto più flessibile, e consente di modificare il backend
  • Se sì, chiedetevi: non posso refactoring il mio codice per tornare all'interfaccia di livello superiore? :)

Il più astratto è il codice, i meno modifiche vostro sono tenuti a fare quando si cambia un backend. E 'così semplice.

Se, d'altra parte, si finisce per lanciare i valori di ritorno alla classe concreta, bene che è un segno forte che probabilmente si dovrebbe tornare al posto della classe concreta. I suoi utenti / compagni di squadra non dovrebbero sapere sui contratti più o meno implicite:. Se è necessario utilizzare i metodi concreti, basta restituire la classe concreta, per chiarezza

In poche parole: codice abstract , ma esplicitamente :)

Senza essere in grado di giustificare con risme di citazioni CS (io sono autodidatta), sono sempre andato dal mantra di "Accettare il minimo derivato, restituire il più derivata," quando si progettano le classi e si è fermato mi bene nel corso degli anni.

Credo che significa in termini di interfaccia rispetto al ritorno concreto è che se si sta cercando di ridurre le dipendenze e / o disaccoppiare, restituendo l'interfaccia è generalmente più utile. Tuttavia, se gli attrezzi classe concreta più di tale interfaccia, di solito è più utile per i chiamanti del vostro metodo per ottenere la classe concreta posteriore (cioè la "più derivata") piuttosto che aribtrarily li limita a un sottoinsieme di funzionalità che di oggetto restituito - a meno che effettivamente necessità per limitare loro. Poi di nuovo, si potrebbe anche solo aumentare la copertura dell'interfaccia. restrizioni inutili come questo paragono a tenuta sconsiderato delle classi; non si sa mai. Tanto per parlare un po 'l'ex parte di quel mantra (per gli altri lettori), accettando il meno deriva anche offre la massima flessibilità per i chiamanti del vostro metodo.

-Oisin

In generale, per un'interfaccia pubblico di fronte, come le API, restituendo l'interfaccia (come List) sopra la concreta attuazione (come ad esempio ArrayList) sarebbe meglio.

L'uso di una o di ArrayList LinkedList è un dettaglio di implementazione della biblioteca che dovrebbe essere preso in considerazione per il caso d'uso più comune di quella libreria. E, naturalmente, internamente, con metodi private consegnare a mano LinkedLists sarebbe non essere necessariamente una cosa negativa, se fornisce servizi che renderebbero il processo più facile.

Non c'è ragione per cui una classe concreta non deve essere utilizzato per l'attuazione, a meno che non ci sia una buona ragione per credere che qualche altra classe List sarebbe stato utilizzato in seguito. Ma poi di nuovo, cambiando i dettagli di implementazione non dovrebbe essere così doloroso fino a quando la porzione di rivestimento pubblico è ben progettato.

La libreria stessa dovrebbe essere una scatola nera per i suoi consumatori, in modo che non hanno veramente a preoccuparsi di quello che sta succedendo internamente. Ciò significa anche che la biblioteca dovrebbe essere progettato in modo che è stato progettato per essere utilizzato nel modo in cui è destinato.

Come una regola, ho solo passo indietro implementazioni interne se sono in qualche privato, funzionamento interno di una libreria, e anche in questo caso solo con parsimonia.Per tutto ciò che è pubblico e in grado di essere chiamato dall'esterno del mio modulo uso di interfacce, e anche la Fabbrica modello.

Attraverso l'utilizzo di interfacce in modo ha dimostrato di essere molto affidabile per scrivere codice riutilizzabile.

La domanda principale è stata risolta già e si dovrebbe sempre utilizzare l'interfaccia. Io però vorrei solo commentare

  

E 'evidente che utilizzando l'interfaccia ha un sacco di vantaggi (è per questo che è lì). Nella maggior parte dei casi non importa cosa concreta attuazione viene utilizzato da una funzione di libreria. Ma forse ci sono casi in cui non importa. Per esempio, se so che sarà principalmente accedere ai dati nella lista in modo casuale, un LinkedList sarebbe male. Ma se la mia funzione di libreria restituisce solo l'interfaccia, io semplicemente non lo so. Per essere sul sicuro potrei nemmeno bisogno di copiare l'elenco esplicitamente sopra a un ArrayList.

Se si restituisce una struttura dati che si sa ha scarse prestazioni ad accesso casuale - O (n) e in genere un sacco di dati - ci sono altre interfacce si dovrebbe essere specificando invece di lista, come Iterable in modo che chiunque utilizzi la biblioteca sarà pienamente consapevoli del fatto che è disponibile solo accesso sequenziale.

Picking il giusto tipo di tornare non è solo di interfaccia rispetto concreta attuazione, si tratta anche di selezionare l'interfaccia giusta.

Non importa più di tanto se un metodo API restituisce un'interfaccia o una classe concreta; nonostante quello che tutti qui dice, è quasi mai cambiare la classe implementiation una volta che il codice è scritto.

Cosa c'è di molto più importante: utilizzare sempre le interfacce di minima portata per il metodo parametri ! In questo modo, i clienti hanno la libertà massima e possono utilizzare le classi il codice non sa nemmeno circa.

Quando un metodo API restituisce ArrayList, non ho assolutamente scrupoli con quella, ma quando si richiede un ArrayList (o, tutto a comuni, Vector) parametro, ritengo caccia programmatore e fargli male, perché significa che ho non è possibile utilizzare Arrays.asList(), Collections.singletonList() o Collections.EMPTY_LIST.

Mi dispiace di non essere d'accordo, ma penso che la regola di base è la seguente:

  • ingresso argomenti utilizzano la più generico .
  • per valori di uscita, il più specifica .

Quindi, in questo caso si desidera dichiarare l'attuazione come:

public ArrayList<String> foo() {
  return new ArrayList<String>();
}

Motivazioni : Il caso ingresso è già noto e spiegato da tutti: utilizzare l'interfaccia, periodo. Tuttavia, nel caso di uscita può guardare intuitivo. Si vuole tornare l'attuazione perché si vuole il cliente di avere il maggior numero di informazioni su ciò che sta ricevendo. In questo caso, la conoscenza è più potere .

Esempio 1: il cliente vuole ottenere il quinto elemento:

  • ritorno Collezione: deve iterare fino al 5 elemento vs lista di ritorno:
  • Lista di ritorno: list.get(4)

Esempio 2: il cliente vuole per rimuovere il 5 ° elemento:

  • Lista di ritorno:. Deve creare un nuovo elenco, senza l'elemento specificato (list.remove() è facoltativo)
  • ritorno ArrayList: arrayList.remove(4)

Quindi è una grande verità che utilizzando interfacce è grande perché promuove la riusabilità, riduce l'accoppiamento, migliora la manutenibilità e rende le persone felici ... ma solo quando viene utilizzato come ingresso .

Quindi, ancora una volta, la regola può essere indicato come:

  • Essere flessibile per quello che offrono.
  • essere informativo con quello che si esprime.

Quindi, la prossima volta, si prega di restituire l'attuazione.

È possibile utilizzare l'interfaccia di astrarre dalla effettiva attuazione. L'interfaccia è fondamentalmente solo un progetto per quello che l'implementazione può fare.

Le interfacce sono una buona progettazione perché consentono di modificare i dettagli di implementazione, senza dover temere che uno qualsiasi dei suoi consumatori sono colpiti direttamente, fino a quando si implementazione ancora fa quello che la vostra interfaccia dice che lo fa.

Per lavorare con le interfacce cui li un'istanza in questo modo:

IParser parser = new Parser();

Ora IParser sarebbe l'interfaccia, e Parser sarebbe l'implementazione. Ora, quando si lavora con l'oggetto parser dall'alto, si lavorerà contro l'interfaccia (IParser), che a sua volta lavorare contro l'implementazione (Parser).

Ciò significa che è possibile modificare il funzionamento interno del parser per quanto si vuole, non potrà mai influenzare il codice che funziona contro l'interfaccia parser IParser.

In uso generale l'interfaccia in tutti i casi se non si ha bisogno della funzionalità della classe concreta. Si noti che per gli elenchi, Java ha aggiunto una RandomAccess class marcatore principalmente per distinguere un caso comune in cui un algoritmo potrebbe aver bisogno di sapere se get (i) è costante di tempo oppure no.

Per usi di codice, Michael sopra è giusto che essendo più generico possibile nei parametri di metodo è spesso ancora più importante. Questo è particolarmente vero quando si verifica un tale metodo.

Troverete (o hanno trovato) che al tuo ritorno interfacce, che permeano attraverso il vostro codice. per esempio. si torna un'interfaccia da metodo A e sono per poi passare un'interfaccia per il metodo B.

Quello che stai facendo è la programmazione per contratto, anche se in modo limitato.

Questo ti dà enormi possibilità di cambiare le implementazioni sotto le coperte (purché questi nuovi oggetti soddisfano i contratti esistenti / comportamenti attesi).

Dato tutto questo, avete benefici in termini di scelta del vostro implementazione, e in che modo è possibile sostituire i comportamenti (tra cui test - utilizzando beffardo, per esempio). Nel caso in cui non l'aveste capito, sono tutti a favore di questo e cercare di ridurre al (o introdurre) interfacce, ove possibile.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top