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 ArrayList
comme 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' List
interface (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 value
ne puisse ni contribuer hashCode()
ni influencer equals()
. L' List
interface - 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 Car
et Motorcycle
qui héritent d'une Vehicle
classe? 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 Person
s, chacun avec un nom et une adresse. Un Employee
est un Person
et a un Boss
. A Boss
est aussi un Person
. Donc, devrais-je créer une Person
classe qui est héritée par Boss
et Employee
? Maintenant, j'ai un problème: le patron est aussi un employé et a un autre supérieur. Donc, il semble que Boss
devrait s'étendre Employee
. Mais CompanyOwner
c'est un Boss
mais 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' Iterable
interface pour vous Node
parce que vous voulez la rendre itérable, c'est très bien. Si vous implémentez l' Collection
interface 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.