L'argument «horrible pour la mémoire» est totalement faux, mais c'est objectivement une «mauvaise pratique». Lorsque vous héritez d'une classe, vous n'héritez pas seulement des champs et des méthodes qui vous intéressent. Au lieu de cela, vous héritez de tout . Chaque méthode qu’elle déclare, même si elle ne vous est pas utile. Et surtout, vous héritez également de tous ses contrats et garanties que la classe fournit.
L'acronyme SOLID fournit des heuristiques pour une bonne conception orientée objet. Ici, le I nterface Principe Ségrégation (ISP de) et le L Iskov Remplacement Pricinple (LSP) ont quelque chose à dire.
Le FAI nous dit de garder nos interfaces aussi petites que possible. Mais en héritant de ArrayList, vous obtenez beaucoup, beaucoup de méthodes. Est - il un sens get(), remove(), set()(remplacer), ou add()(insérer) un nœud enfant à un index particulier? Est-il judicieux de ensureCapacity()de la liste sous-jacente? Qu'est-ce que cela signifie pour sort()un nœud? Les utilisateurs de votre classe sont-ils vraiment supposés en avoir subList()? Étant donné que vous ne pouvez pas masquer les méthodes que vous ne voulez pas, la seule solution est d'avoir ArrayList en tant que variable membre et de transférer toutes les méthodes que vous voulez réellement:
private final ArrayList<Node> children = new ArrayList();
public void add(Node child) { children.add(child); }
public Iterator<Node> iterator() { return children.iterator(); }
Si vous voulez vraiment toutes les méthodes que vous voyez dans la documentation, nous pouvons passer au LSP. Le LSP nous dit que nous devons pouvoir utiliser la sous-classe partout où la classe parente est attendue. Si une fonction prend un ArrayListcomme paramètre et que nous passons le nôtre Node, rien n’est supposé changer.
La compatibilité des sous-classes commence par des éléments simples tels que les signatures de type. Lorsque vous substituez une méthode, vous ne pouvez pas rendre les types de paramètre plus stricts, car cela pourrait exclure les utilisations légales de la classe parent. Mais c’est quelque chose que le compilateur vérifie pour nous en Java.
Mais le LSP est beaucoup plus profond: nous devons maintenir la compatibilité avec tout ce qui est promis par la documentation de toutes les classes et interfaces parent. Dans leur réponse , Lynn a trouvé un cas de ce type où l' Listinterface (dont vous avez hérité via ArrayList) garantit le fonctionnement des méthodes equals()et hashCode(). Car hashCode()on vous donne même un algorithme particulier qui doit être implémenté exactement. Supposons que vous avez écrit ceci 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);
}
...
}
Cela nécessite que le valuene puisse ni contribuer hashCode()ni influencer equals(). L' Listinterface - que vous promettez d'honorer en en héritant - nécessite new Node(0).equals(new Node(123))d'être vraie.
En raison de l'héritage de classes, il est trop facile de rompre accidentellement une promesse faite par une classe parente, et parce qu'elle expose généralement plus de méthodes que prévu, il est généralement suggéré de préférer la composition à l'héritage . Si vous devez hériter de quelque chose, il est suggéré de n'hériter que des interfaces. Si vous souhaitez réutiliser le comportement d'une classe particulière, vous pouvez la conserver en tant qu'objet séparé dans une variable d'instance, afin que toutes ses promesses et exigences ne vous soient pas imposées.
Parfois, notre langage naturel suggère une relation d'héritage: une voiture est un véhicule. Une moto est un véhicule. Devrais-je définir des classes Caret Motorcyclequi héritent d'une Vehicleclasse? La conception orientée objet ne consiste pas à refléter exactement le monde réel dans notre code. Nous ne pouvons pas facilement encoder les riches taxonomies du monde réel dans notre code source.
Un exemple de ce type est le problème de modélisation employé-patron. Nous avons plusieurs Persons, chacun avec un nom et une adresse. Un Employeeest un Personet a un Boss. A Bossest aussi un Person. Donc, devrais-je créer une Personclasse qui est héritée par Bosset Employee? Maintenant, j'ai un problème: le patron est aussi un employé et a un autre supérieur. Donc, il semble que Bossdevrait s'étendre Employee. Mais CompanyOwnerc'est un Bossmais n'est pas un Employee? Tout type de graphique d'héritage se décompose d'une manière ou d'une autre ici.
La POO ne concerne pas les hiérarchies, l'héritage et la réutilisation de classes existantes, il s'agit de généraliser le comportement . OOP signifie «J'ai plusieurs objets et je veux qu'ils fassent quelque chose - et je me fiche de savoir comment.» C'est à cela que servent les interfaces . Si vous implémentez l' Iterableinterface pour vous Nodeparce que vous voulez la rendre itérable, c'est très bien. Si vous implémentez l' Collectioninterface parce que vous souhaitez ajouter / supprimer des nœuds enfants, etc., c'est très bien. Mais hériter d'une autre classe parce qu'il arrive à vous donner tout ce qui ne l'est pas, ou du moins pas à moins d'y avoir bien réfléchi.