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 ArrayListparametro as e passiamo Nodeinvece 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' Listinterfaccia (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 valuenon possa contribuire al hashCode()e non possa influenzare equals(). L' Listinterfaccia - 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 Care Motorcyclequelle ereditate da una Vehicleclasse? 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ù Persons, ognuna con un nome e un indirizzo. An Employeeè a Persone ha a Boss. A Bossè anche a Person. Quindi dovrei creare una Personclasse ereditata da Bosse Employee? Ora ho un problema: il capo è anche un dipendente e ha un altro superiore. Quindi sembra che Bossdovrebbe estendersi Employee. Ma il CompanyOwnerè un Bossma 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' Iterableinterfaccia per il tuo Nodeperché vuoi renderla iterabile, va benissimo. Se si implementa l' Collectioninterfaccia 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.