I tipi di dati astratti non sono buone cose in Java per passare oggetti o parametri complessi.
Digita sicurezza
Consente di confrontare due bit di codice
String foo(Integer a, Integer b, String s) {
if(a.compareTo(b) == 0) {
return s;
} else {
return "" + (a - b); // just as an example
}
}
vs
String foo(Map<String, Object> m) {
if(m.get("a").compareTo(m.get("b")) == 0) {
return (String)m.get("s");
} else {
return "" + (((Integer)m.get("a") - (Integer)m.get("b"));
}
}
Nel secondo esempio, dobbiamo usare Object
come valore perché è l'unico oggetto che String
e Integer
condividono in comune. Ciò significa che tutti gli oggetti più in basso devono essere lanciati. Questo ha la netta possibilità di lanciare un ClassCastException
( javadoc ) ovunque o in qualsiasi punto del codice ... a meno che non aggiunga una tonnellata di codice della piastra della caldaia per impedirlo (condizioni di protezione per instanceof
dappertutto).
Quel che è peggio è che quelle eccezioni (se non controlli tutto - e i percorsi che segui se lo fai) sono errori di runtime. Gli errori dei tuoi tipi non verranno trovati finché non eseguirai il programma anziché quando lo compili. Gli errori rilevati durante la compilazione sono più facili da correggere rispetto a quelli rilevati in fase di esecuzione. Del resto, gli strumenti di analisi statica e le ispezioni che la maggior parte degli IDE ti forniranno li prenderanno per te mentre li digiti (e lanciano orrendi avvertimenti sul tentativo di farlo con il tipo di dati astratti).
sovraccarichi
Passare tutti gli argomenti come un singolo ADT significa che hai un unico metodo. Nell'esempio sopra, cosa succede se hai:
String foo(Integer a, Integer b) { ... }
String foo(Integer a, Integer b, String s) { ... }
String foo(Integer a, Integer b, String s, String t) { ... }
come modi diversi per chiamare la funzione. Con il passaggio di un ADT, hai solo
String foo(Map<String, Object> m) { ... }
Non c'è modo di differenziare i sovraccarichi. Questo porta al codice che assomiglierà a:
String foo(Map<String, Object> m) {
if(m.containsKey("t")) { ... }
else if(m.containsKey("s")) { ... }
else { ... }
}
La complessità della funzione passerà attraverso il tetto (a meno che non inizi ad estrarre quelli ad altri metodi che in qualche modo indicano il tipo con cui hanno a che fare e la mia testa stia cominciando a ferire pensando alla notazione ungherese che stai per attenersi ai nomi dei metodi).
Questo ulteriore diventa problematico quando hai tipi diversi che sono argomenti validi.
String foo(Integer a, Integer b, String s) { ... }
String foo(BigInteger a, BigInteger b, String s) { ... }
Sicurezza di digitazione
Molto tempo fa, ero un programmatore di Perl - nei giorni precedenti alla scoperta di OOP in perl ( bless
come riferimento). Il modo solo di passare attorno a oggetti complessi era con %hashes
e @arrays
. E tu dovevi tirare fuori le chiavi dell'hash. C'erano numerosi bug che potevano essere rilevati solo in fase di esecuzione (e le gioie di autovivication hanno fatto in modo che a volte difficile).
Un semplice errore di battitura in una chiave significherebbe che alcuni parametri non sono stati trovati (quando erano lì) o sono stati appena creati nel punto sbagliato e passati a un altro metodo.
String plusOne(Map<String, Integer> m) {
if(m.containsKey("type") && m.containsKey("1") {
return m.get("typo") + m.get("I");
}
return 0;
}
Questi non sono bug divertenti da trovare. Nessun bug è davvero divertente, ma questi bug ti schizzano in faccia perché sono completamente prevenibili se tu avessi appena usato un elenco di parametri appropriato e lavorato con la lingua e il compilatore.
Chiamare il packaging
Finora, ho parlato dei pericoli nel metodo stesso. Che dire del lavoro che il callee deve fare.
System.out.println(foo(1,2,"bar"));
E abbiamo finito con la semplice lista dei parametri.
Map<String, Object> m = new HashMap<String, Object>();
m.put("a", 1);
m.put("b", 2);
m.put("s", "bar");
System.out.println(foo(m));
Ora, vuoi eseguire il debug di questo? Cosa stai passando in foo? Devi guardare indietro nel codice e seguirlo. Oltre ad essere un blocco di codice significativamente più grande per eseguire qualsiasi chiamata di metodo di questo tipo, è anche ovvio che cosa sta entrando in questo.
refactors
foo
ora prende Double
s anziché Integer
s. Si modifica la firma del metodo e si correggono tutti gli errori di compilazione. Li hai trovati tutti e compila correttamente (vedi tipo sicurezza sopra).
Tuttavia, la versione della mappa funziona altrettanto bene con Integer
come con Double
. non puoi scoprire se hai corretto il refactoring o meno.
Perché sono insieme in Map
?
Questa è più una domanda filosofica. Perché sono questi oggetti in una mappa insieme? Quale attributo comune condividono? Se realmente fanno parte di un'altra struttura di dati
class Cell {
int x, y;
String value;
}
trasformali in una struttura dati adeguata che si incastri. Se non sono cose che sono onestamente legate l'una all'altra, beh ... no.
Mettere le cose insieme in una struttura le rende legate l'una all'altra nella nostra mente, anche se non lo sono. Se non lo sono, aggiunge una significativa ginnastica mentale per mantenere le cose sono correlate tra loro e quelle che non sono a parte.