Perché definire un oggetto Java utilizzando l'interfaccia (ad esempio mappa) anziché l'implementazione (HashMap)

15

Nella maggior parte del codice Java, vedo persone dichiarare oggetti Java come questo:

Map<String, String> hashMap = new HashMap<>();
List<String> list = new ArrayList<>();

invece di:

HashMap<String, String> hashMap = new HashMap<>();
ArrayList<String> list = new ArrayList<>();

Perché esiste una preferenza per definire l'oggetto Java utilizzando l'interfaccia piuttosto che l'implementazione che verrà effettivamente utilizzata?

    
posta Suman 27.01.2014 - 17:48
fonte

4 risposte

24

Il motivo è che l'implementazione di queste interfacce di solito non è rilevante quando le gestiamo, quindi se obbliga il chiamante a passare un HashMap a un metodo, allora sei essenzialmente obbligato a quale implementazione usare. Quindi, come regola generale, dovresti gestire la sua interfaccia piuttosto che l'effettiva implementazione ed evitare il dolore e la sofferenza che potrebbero comportare la necessità di cambiare tutte le firme dei metodi usando HashMap quando decidi di utilizzare LinkedHashMap invece .

Va detto che ci sono eccezioni a questo quando l'implementazione è rilevante. Se hai bisogno di una mappa quando l'ordine è importante, allora puoi richiedere un TreeMap o un LinkedHashMap da passare, o meglio ancora SortedMap che non specifica un'implementazione specifica. Questo obbliga il chiamante a passare necessariamente un certo tipo di implementazione di Map e suggerisce strongmente che l'ordine è importante. Detto questo, potresti sovrascrivere SortedMap e passare uno non selezionato? Sì, certo, comunque ti aspetti che succedano cose brutte.

Tuttavia, le migliori pratiche impongono ancora che, se non è importante, non si dovrebbero usare implementazioni specifiche. Questo è vero in generale. Se hai a che fare con Dog e Cat che derivano da Animal , per utilizzare al meglio l'ereditarietà, dovresti generalmente evitare di avere metodi specifici per Dog o Cat . Piuttosto tutti i metodi in Dog o Cat dovrebbero sovrascrivere i metodi in Animal e questo ti farà risparmiare problemi a lungo termine.

    
risposta data 27.01.2014 - 17:56
fonte
9

Nelle parole di Layman:

La stessa ragione per cui i produttori di impianti elettrici hanno costruito i loro prodotti con le prese elettriche invece dei semplici cavi sbucciati e le case sono dotate di prese a muro invece di staccare i cavi che sporgono dal muro.

Usando invece le spine standard, consentono di collegare le stesse installazioni in qualsiasi presa compatibile intorno alla casa.

Dal punto di vista della presa a muro, non importa se si collega un televisore o uno stereo.

Ciò rende più utili sia l'appliance che il socket.

Prendi ad esempio un metodo che accetta una mappa come argomento.

Il metodo funzionerà indipendentemente dal fatto che passi una HashMap o una LinkedHashMap, a patto che sia una sottoclasse di Map.

Questo è principio di sostituzione di Liskov .

Nel codice di esempio che hai fornito, significa che puoi in un secondo momento, per qualche motivo, modificare l'implementazione concreta di Hash e non dovrai modificare il resto del codice.

Il problema con il software è che, dal momento che è relativamente facile cambiare le cose in seguito senza spreco di mattoni o malta, la gente presume che quel tipo di pensiero non valga la pena. Ma la realtà ci ha mostrato che la manutenzione del software è molto costosa.

    
risposta data 27.01.2014 - 19:05
fonte
4

È necessario seguire il principio di segregazione dell'interfaccia (il "I" in SOLID ). Impedisce al codice che utilizza tali oggetti di dipendere dai metodi di quegli oggetti di cui non ha bisogno, il che rende il codice meno accoppiato e quindi più facile da cambiare.

Ad esempio, se scopri in seguito che hai davvero bisogno di un LinkedHashMap , puoi tranquillamente apportare tale modifica senza influire su nessun altro codice.

Tuttavia, c'è un compromesso, perché stai limitando artificialmente il codice che può prendere il tuo oggetto come parametro. Diciamo che c'è una funzione da qualche parte che richiede un HashMap per qualche motivo. Se restituisci un Map , non puoi passare il tuo oggetto in quella funzione. Devi bilanciare la probabilità che in futuro ci sia bisogno della funzionalità extra presente nella classe più concreta con il desiderio di limitare l'accoppiamento e mantenere la tua interfaccia pubblica il più piccola possibile.

    
risposta data 27.01.2014 - 18:20
fonte
3

Avendo la variabile vincolata a un'interfaccia garantisce che nessuno degli usi di quella variabile utilizzi HashMap funzionalità specifiche che potrebbero non esistere sull'interfaccia, quindi l'istanza può essere modificata senza problemi in seguito a un'implementazione diversa a patto che la nuova istanza implementa anche l'interfaccia.

Per questo motivo, ogni volta che vuoi usare un'interfaccia oggetti, è sempre buona norma dichiarare le variabili come l'interfaccia e non la particolare implementazione, questo vale per tutti i tipi di oggetti che puoi usare che hanno un'interfaccia. Il motivo per cui lo vedi spesso è che molte persone hanno costruito questo come abitudine.

Detto questo, a volte non è pericoloso saltare l'uso delle interfacce e la maggior parte di noi non sempre fa seguire questa regola, senza alcun danno reale. È semplicemente una buona pratica attenersi a quando si ha la sensazione che il codice possa essere cambiato e necessiti di manutenzione / crescita in futuro. È meno preoccupante quando stai hackerando codice che non sospetti abbia una lunga vita o abbia molta importanza. Anche rompere questa regola di solito ha una piccola conseguenza che cambiare l'implementazione in un'altra può richiedere un po 'di refactoring, quindi se non lo segui sempre non ti farai molto male, anche se non c'è alcun danno nel seguirlo .

    
risposta data 27.01.2014 - 17:55
fonte

Leggi altre domande sui tag