L'argomento "orribile per la memoria" è completamente sbagliato, ma è oggettivamente una "cattiva pratica". Quando erediti da una classe, non erediti solo i campi e i metodi che ti interessano. Invece, erediti tutto . Ogni metodo che dichiara, anche se non è utile per te. E, soprattutto, erediti anche tutti i suoi contratti e le garanzie che la classe fornisce.
L'acronimo SOLID fornisce alcune euristiche per un buon design orientato agli oggetti. Qui, I nterface Segregation Principle (ISP) e L iskov Substitution Pricinple (LSP) hanno qualcosa da dire.
L'ISP ci dice di mantenere le nostre interfacce il più piccolo possibile. Ma ereditando da ArrayList
, ottieni molti, molti metodi. E 'significativo get()
, remove()
, set()
(sostituzione), o add()
(inserto) un nodo figlio ad un particolare indice? È sensato ensureCapacity()
dall'elenco sottostante? Cosa significa sort()
un nodo? Gli utenti della tua classe dovrebbero davvero ottenere un subList()
? Dato che non puoi nascondere i metodi che non desideri, l'unica soluzione è avere ArrayList come variabile membro e inoltrare tutti i metodi che desideri effettivamente:
private final ArrayList<Node> children = new ArrayList();
public void add(Node child) { children.add(child); }
public Iterator<Node> iterator() { return children.iterator(); }
Se vuoi davvero tutti i metodi che vedi nella documentazione, possiamo passare a LSP. L'LSP ci dice che dobbiamo essere in grado di usare la sottoclasse ovunque ci si aspetti la classe genitore. Se una funzione accetta un ArrayList
parametro as e passiamo Node
invece il nostro , nulla dovrebbe cambiare.
La compatibilità delle sottoclassi inizia con cose semplici come le firme dei tipi. Quando si esegue l'override di un metodo, non è possibile rendere più rigorosi i tipi di parametro poiché ciò potrebbe escludere gli usi legali con la classe genitore. Ma questo è qualcosa che il compilatore controlla per noi in Java.
Ma l'SPL funziona molto più a fondo: dobbiamo mantenere la compatibilità con tutto ciò che è promesso dalla documentazione di tutte le classi e interfacce parent. Nella loro risposta , Lynn ha trovato uno di questi casi in cui l' List
interfaccia (che hai ereditato tramite ArrayList
) garantisce il funzionamento dei metodi equals()
e hashCode()
. Perché hashCode()
ti viene persino dato un algoritmo particolare che deve essere implementato esattamente. Supponiamo che tu abbia scritto questo Node
:
public class Node extends ArrayList<Node> {
public final int value;
public Node(int value, Node... children) {
this.value = Value;
for (Node child : children)
add(child);
}
...
}
Ciò richiede che il value
non possa contribuire al hashCode()
e non possa influenzare equals()
. L' List
interfaccia - che prometti di onorare ereditando da essa - richiede new Node(0).equals(new Node(123))
di essere vera.
Poiché ereditare dalle classi rende troppo facile infrangere accidentalmente una promessa fatta da una classe genitore, e poiché di solito espone più metodi di quelli previsti, in genere si suggerisce di preferire la composizione rispetto all'eredità . Se è necessario ereditare qualcosa, si consiglia di ereditare solo le interfacce. Se si desidera riutilizzare il comportamento di una determinata classe, è possibile mantenerlo come oggetto separato in una variabile di istanza, in questo modo tutte le sue promesse e requisiti non sono obbligati.
A volte, il nostro linguaggio naturale suggerisce una relazione ereditaria: un'auto è un veicolo. Una moto è un veicolo. Devo definire le classi Car
e Motorcycle
quelle ereditate da una Vehicle
classe? Il design orientato agli oggetti non riguarda il mirroring del mondo reale esattamente nel nostro codice. Non possiamo facilmente codificare le ricche tassonomie del mondo reale nel nostro codice sorgente.
Uno di questi esempi è il problema di modellazione dei dipendenti-boss. Abbiamo più Person
s, ognuna con un nome e un indirizzo. An Employee
è a Person
e ha a Boss
. A Boss
è anche a Person
. Quindi dovrei creare una Person
classe ereditata da Boss
e Employee
? Ora ho un problema: il capo è anche un dipendente e ha un altro superiore. Quindi sembra che Boss
dovrebbe estendersi Employee
. Ma il CompanyOwner
è un Boss
ma non è un Employee
? Qualsiasi tipo di grafico dell'eredità si romperà in qualche modo qui.
OOP non riguarda gerarchie, ereditarietà e riutilizzo di classi esistenti, si tratta di generalizzare il comportamento . OOP riguarda "Ho un sacco di oggetti e voglio che facciano una cosa particolare - e non mi interessa come". Ecco a cosa servono le interfacce . Se implementi l' Iterable
interfaccia per il tuo Node
perché vuoi renderla iterabile, va benissimo. Se si implementa l' Collection
interfaccia perché si desidera aggiungere / rimuovere nodi figlio ecc., Va bene. Ma ereditare da un'altra classe perché capita di darti tutto ciò che non lo è, o almeno non a meno che tu non abbia riflettuto attentamente come indicato sopra.