2012-03-09 16 views
6

Recentemente ho guardato molto codice (a mio vantaggio, mentre sto ancora imparando a programmare), e ho notato una serie di progetti Java (da quelli che sembrano essere programmatori di tutto rispetto) in cui usano una sorta di down-casting immediato.Qual è il vantaggio del down-casting immediato?

Io in realtà sono molteplici esempi, ma qui è uno che ho tirato dritto dal codice:

public Set<Coordinates> neighboringCoordinates() { 
    HashSet<Coordinates> neighbors = new HashSet<Coordinates>(); 
    neighbors.add(getNorthWest()); 
    neighbors.add(getNorth()); 
    neighbors.add(getNorthEast()); 
    neighbors.add(getWest()); 
    neighbors.add(getEast()); 
    neighbors.add(getSouthEast()); 
    neighbors.add(getSouth()); 
    neighbors.add(getSouthWest()); 
    return neighbors; 
} 

E dallo stesso progetto, ecco un altro (forse più conciso) Esempio:

private Set<Coordinates> liveCellCoordinates = new HashSet<Coordinates>(); 

Nel primo esempio, è possibile vedere che il metodo ha un tipo restituito di Set<Coordinates> - tuttavia, quel metodo specifico restituirà sempre solo un HashSet e nessun altro tipo di Set.

Nel secondo esempio, liveCellCoordinates viene inizialmente definito come Set<Coordinates>, ma viene immediatamente convertito in HashSet.

E non è solo questo singolo, specifico progetto: ho trovato che questo è il caso in più progetti.

Sono curioso di sapere qual è la logica dietro questo? Esistono alcune convenzioni sul codice che prenderebbero in considerazione questa buona pratica? Rende il programma più veloce o più efficiente in qualche modo? Che vantaggio avrebbe?

+2

Questi sono in realtà esempi di up-casting (da un tipo specifico a un tipo più ampio). Vedi, ad esempio, [la voce di Wikipedia su downcasting] (http://en.wikipedia.org/wiki/Downcasting). –

+0

@TedHopp Prendiamo il secondo esempio che ho fornito, in quell'esempio comincio come un 'Set', e poi converto in un' HashSet' (credo che 'HashSet' sia più specifico del' Set'). Non sarebbe down-casting? Dal momento che sto passando da un tipo generico di 'Set' a un tipo specifico di' HashSet'? – Bob

+1

Nel secondo esempio, si crea un 'HashSet' e lo si converte in un' Set' quando lo si assegna a 'liveCellCoordinates'. Nessun oggetto 'Set' esiste fino a quando non viene valutata l'espressione' new' e nessun 'Set' viene convertito in un' HashSet'. –

risposta

12

Quando si progetta una firma del metodo, di solito è meglio definire solo ciò che deve essere bloccato. Nel primo esempio, specificando solo che il metodo restituisce uno Set (anziché uno HashSet in modo specifico), l'implementatore è libero di modificare l'implementazione se risulta che una struttura di dati corretta non è la HashSet. Se il metodo era stato dichiarato per restituire un valore HashSet, anche tutto il codice che dipendeva dall'oggetto che era specificamente un HashSet invece del più generale tipo Set avrebbe anche bisogno di essere rivisto.

Un esempio realistico sarebbe se fosse stato deciso che neighboringCoordinates() fosse necessario per restituire un oggetto thread-safe Set.Come scritto, questo sarebbe molto semplice da fare — sostituire l'ultima riga del metodo con:

return Collections.synchronizedSet(neighbors); 

Come si è visto, l'oggetto restituito da SetsynchronizedSet() non è l'assegnazione-compatibile con HashSet. Per fortuna il metodo è stato dichiarato per restituire un Set!

Una considerazione simile si applica al secondo caso. Il codice nella classe che utilizza liveCellCoordinates non dovrebbe aver bisogno di sapere altro che è un Set. (In effetti, nel primo esempio, mi sarei aspettato di vedere:.

Set<Coordinates> neighbors = new HashSet<Coordinates>(); 

nella parte superiore del metodo)

+2

+1: si tratta di essere deliberato sulle proprie scelte in modo che i requisiti minimi siano chiari. Usare tipi che implicano cose che non sono effettivamente necessarie è più difficile da capire e mantenere. –

4

Perché ora se cambiano il tipo in futuro, non è necessario aggiornare alcun codice a seconda delle coordinate vicine.

Let aveste:

HashedSet<Coordinates> c = neighboringCoordinates() 

Ora, diciamo cambiano il loro codice per utilizzare una diversa implementazione di set. Indovina, devi anche cambiare il tuo codice.

Ma, se si dispone di:

Set<Coordinates> c = neighboringCoordinates() 

Finché la loro collezione ancora implementa impostata, possono cambiare tutto quello che vogliono internamente senza influenzare il vostro codice.

Fondamentalmente, è solo il meno specifico possibile (entro limiti ragionevoli) per nascondere i dettagli interni. Il tuo codice si preoccupa solo che possa accedere alla collezione come un set. Non gli importa quale tipo specifico di set è, se questo ha senso. Quindi, perché rendere il tuo codice abbinato a un HashedSet?

1

E 'abbastanza semplice.

Nell'esempio, il metodo restituisce Set. Dal punto di vista di un progettista dell'API, questo ha un vantaggio significativo, rispetto alla restituzione di HashSet.

Se a un certo punto il programmatore decide di utilizzare SuperPerformantSetForDirections, può farlo senza modificare l'API pubblica, se la nuova classe estende Set.

2

Nel primo esempio, il metodo restituirà sempre solo un hashset è un dettaglio di implementazione che gli utenti della classe non dovrebbero conoscere. Ciò consente allo sviluppatore di utilizzare un'implementazione diversa se è desiderabile.

2

Il principio di progettazione in questo caso è "preferisci sempre specificare i tipi astratti".

Set è astratto; non esiste una tale classe di calcestruzzo Set - è un'interfaccia, che è per definizione astratta. Il contratto del metodo restituisce un Set - è lo sviluppatore scegliere quale tipo di Set restituire.

si dovrebbe fare con campi così, ad esempio:

private List<String> names = new ArrayList<String>; 

non

private ArrayList<String> names = new ArrayList<String>; 

In seguito, si può decidere di passare ad utilizzare un LinkedList - specificando il tipo astratto permette di fare questo senza modifiche al codice (eccetto per l'inizializzazione ovviamente).

1

Il trucco è "codice nell'interfaccia".

La ragione di ciò è che nel 99,9% dei casi si desidera semplicemente il comportamento da HashSet/TreeSet/WhateverSet conforme all'interfaccia Set implementata da tutti. Mantiene il tuo codice più semplice, e l'unica ragione per cui hai effettivamente bisogno di dire HashSet è specificare quale sia il comportamento del Set di cui hai bisogno.

Come forse sapete, HashSet è relativamente veloce ma restituisce gli articoli in ordine apparentemente casuale. TreeSet è un po 'più lento, ma restituisce gli elementi in ordine alfabetico. Il tuo codice non interessa, a patto che si comporti come un Set.

Questo dà un codice più semplice, più facile da lavorare.

Si noti che le scelte tipiche per un Set sono un HashSet, Map is HashMap e List is ArrayList. Se si utilizza un'implementazione non tipica (per te), ci dovrebbe essere una buona ragione per farlo (come richiedere l'ordinamento alfabetico) e tale motivo dovrebbe essere inserito in un commento accanto all'istruzione new. Rende la vita più facile per i futuri manutentori.

2

La domanda è come si desidera utilizzare la variabile. per esempio. è nel tuo contesto importante che è un HashSet? In caso contrario, dovresti dire quello che ti serve, e questo è solo un Set.

Le cose erano diverse se doveste usare per es. TreeSet qui. Quindi si perderebbe l'informazione che è stato ordinato il Set e se l'algoritmo si basa su questa proprietà, la modifica dell'implementazione su HashSet sarebbe un disastro. In questo caso la soluzione migliore sarebbe scrivere SortedSet<Coordinates> set = new TreeSet<Coordinates>();. Oppure immagina di scrivere List<String> list = new LinkedList<String>();: Va bene se vuoi usare list come lista, ma non potresti più usare lo LinkedList come deque, poiché metodi come offerFirst o peekLast non si trovano nell'interfaccia List.

Quindi la regola generale è: essere il più generale possibile, ma specifico come necessario. Chiediti di cosa hai veramente bisogno. Una determinata interfaccia fornisce tutte le funzionalità e le promesse che ti servono? Se sì, allora usalo. Altrimenti sii più specifico, usa un'altra interfaccia o la classe stessa come tipo.

2

Ecco un'altra ragione. È perché i tipi più generali (astratti) hanno meno comportamenti, il che è positivo perché c'è meno spazio per rovinare.

Ad esempio, supponiamo di aver implementato un metodo come questo: List<User> users = getUsers(); quando in realtà si poteva usare un tipo più astratto come questo: Collection<User> users = getUsers();. Ora Bob potrebbe assumere erroneamente che il tuo metodo restituisca gli utenti in ordine alfabetico e crei un bug. Se avessi usato Collection, non ci sarebbe stata una tale confusione.

Problemi correlati