JavaRush /Blog Java /Random-FR /L'héritage comme phénomène
articles
Niveau 15

L'héritage comme phénomène

Publié dans le groupe Random-FR
À vrai dire, je n’avais pas prévu cet article au départ. J’ai considéré les questions que je souhaite aborder ici comme triviales, ne valant même pas la peine d’être mentionnées. Cependant, en train de rédiger des articles pour ce site, j'ai soulevé une discussion sur l'héritage multiple dans l'un des forums. En conséquence, il s'est avéré que la plupart des développeurs ont une compréhension très vague de l'héritage. Et par conséquent, il fait beaucoup d’erreurs. L'héritage étant l'une des caractéristiques les plus importantes de la POO (sinon la plus importante !), j'ai décidé de consacrer un article séparé à ce phénomène. * * * Tout d'abord, je souhaite faire la distinction entre deux concepts : l'objet et la classe. Ces concepts sont constamment confus. Pendant ce temps, ils sont au cœur de la POO. Et, à mon avis, il faut connaître les différences entre eux. Donc l'objet. En gros, c'est n'importe quoi. Voici le cube. En bois, bleu. La longueur du bord est de 5 cm. Ceci est un objet. Et il y a une pyramide. Plastique, rouge. Côte de 10 cm. C'est aussi un objet. Qu'est-ce qu'ils ont en commun? Des tailles différentes. Forme différente. Matériau différent. Pourtant, ils ont quelque chose en commun. Tout d’abord, le cube et la pyramide sont des polyèdres réguliers. Ceux. la somme du nombre de sommets et du nombre de faces est 2 de plus que le nombre d'arêtes. Plus loin. Les deux formes ont des faces, des arêtes et des sommets. Les deux chiffres ont une caractéristique telle que la taille des côtes. Les deux formes peuvent être tournées. Les deux figures peuvent être dessinées. Les deux dernières propriétés sont déjà du comportement. Et ainsi de suite. La pratique de la programmation montre qu'il est beaucoup plus facile d'opérer avec des objets homogènes qu'avec des objets hétérogènes. Et comme il y a encore quelque chose en commun entre ces figures, il y a une volonté de mettre en valeur d'une manière ou d'une autre ce point commun. C’est là qu’intervient la notion de classe. Donc la définition.
Une classe est un descripteur des propriétés communes d'un groupe d'objets. Ces propriétés peuvent être aussi bien des caractéristiques des objets (taille, poids, couleur, etc.), que des comportements, des rôles, etc.
Commentaire. Le mot « tout » (descripteur de toutes les propriétés) n'a pas été prononcé. Ce qui signifie que n’importe quel objet peut appartenir à plusieurs classes différentes. L'héritage comme phénomène - 1 Reprenons le même exemple avec comme base des formes géométriques. La description la plus générale est celle du polyèdre régulier . Indépendamment de la taille du bord, du nombre de faces et de sommets. La seule chose que nous savons, c’est que cette figure a des sommets, des arêtes et des faces, et que les longueurs des arêtes sont égales. Plus loin. Nous pouvons rendre la description plus précise. Disons que nous voulons dessiner ce polyèdre . Introduisons un concept tel que celui de polyèdre régulier dessiné . De quoi avons-nous besoin pour dessiner ? Description d'une méthode de dessin générale qui ne dépend pas de coordonnées de sommet spécifiques. Peut-être la couleur de l'objet. Présentons maintenant les classes Cube et Tetrahedron . Les objets appartenant à ces classes sont certainement des polyèdres réguliers. La seule différence est que le nombre de sommets, d'arêtes et de faces est déjà strictement fixé pour chacune des nouvelles classes. De plus, connaissant le type d'une figure spécifique, nous pouvons donner une description de la méthode de dessin. Cela signifie que tout objet des classes Cube ou Tetrahedron est également un objet de la classe des polyèdres réguliers dessinés . Il existe une hiérarchie de classes. Dans cette hiérarchie, nous descendons de la description la plus générale à la plus spécifique. Notez qu'un objet de n'importe quelle classe correspond également à la description de n'importe quelle classe plus générale dans la hiérarchie. Cette relation de classe est appelée héritage . Chaque classe enfant hérite de toutes les propriétés de la classe parent, plus générales, et ajoute (éventuellement) certaines des siennes à ces propriétés. Ou bien, il remplace certaines propriétés de la classe parent. Ici, je veux citer le livre classique de Gradi Bucha sur la conception orientée objet :
L'héritage définit donc une hiérarchie "est une" entre les classes, dans laquelle la sous-classe hérite d'une ou plusieurs superclasses. C’est en fait le test décisif pour l’héritage. Étant donné les classes A et B, si A "n'est pas une" sorte de B, alors A ne devrait pas être une sous-classe de B.
Traduit, cela ressemble à ceci :
L'héritage définit ainsi une hiérarchie « est » entre les classes, dans laquelle une sous-classe hérite d'une ou plusieurs superclasses. Il s’agit en fait du test déterminant (littéralement, un test décisif, ma note) pour l’héritage. Si nous avons les classes A et B, et si la classe A « n’est pas » une variante de la classe B, alors A ne doit pas être une sous-classe de B.
Ceux qui ont lu jusqu’ici tourneront probablement leur doigt vers leur tempe avec perplexité. La première pensée est que c’est trivial ! C'est vrai. Mais si vous saviez combien de hiérarchies d’héritage folles j’ai vues ! Dans cette discussion que j'évoquais au tout début, l'un des participants a très sérieusement hérité d'un char de... d'une mitrailleuse !!! Pour la simple raison que le char A une mitrailleuse. Et c'est l'erreur la plus courante. L'héritage est confondu avec l'agrégation - l'inclusion d'un objet dans un autre. Le char n'est pas une mitrailleuse, il en contient une. Et à cause de cette erreur, on souhaite le plus souvent utiliser l'héritage multiple. Passons maintenant directement à Java. Qu'y a-t-il en matière d'héritage ? Il existe deux types de classes dans le langage : celles capables de contenir une implémentation et celles incapables de le faire. Ces dernières sont appelées interfaces, bien qu’il s’agisse essentiellement de classes complètement abstraites. L'héritage comme phénomène - 2 Ainsi, le langage vous permet d'hériter d'une classe d'une autre classe contenant potentiellement une implémentation. MAIS UNIQUEMENT D'UN SEUL ! Laissez-moi vous expliquer pourquoi cela a été fait. Le fait est que chaque implémentation ne peut gérer que sa part - avec les variables et les méthodes qu'elle connaît. Et même si nous héritons de la classe C de A et B , alors la méthode processA , héritée de la classe A, ne peut fonctionner qu'avec la variable interne a , car elle ne sait rien de b , tout comme elle ne sait rien de c et de la méthode processC . De même la méthode processB ne peut fonctionner qu'avec la variable b. Autrement dit, les parties héritées sont essentiellement isolées. La classe C peut certainement fonctionner avec eux, mais elle peut tout aussi bien fonctionner avec ces parties si elles sont simplement incluses dans le cadre plutôt que héritées. Cependant, il y a ici un autre problème, à savoir le chevauchement des noms. Si les méthodes processA et processB portaient le même nom, disons process, quel effet aurait l'appel de la méthode process de la classe C ? Laquelle des deux méthodes serait appelée ? Bien sûr, C++ dispose de contrôles dans cette situation, mais cela n’ajoute pas d’harmonie au langage. Ainsi, l’héritage d’implémentation n’offre pas d’avantages, mais il présente des inconvénients. Pour cette raison, cet héritage d’implémentation en Java a été abandonné. Cependant, les développeurs se sont retrouvés avec une option d'héritage multiple telle que l'héritage d'une interface. En termes Java, une implémentation d'interface. Quelle est l'interface ? Un ensemble de méthodes. (Nous n'envisageons pas actuellement la définition des constantes dans les interfaces ; plus d'informations à ce sujet ici .) Qu'est-ce qu'une méthode ? Et une méthode, à la base, détermine le comportement d’un objet. Ce n'est pas un hasard si le nom de presque toutes les méthodes contient une action - getXXX , drawXXX , countXXX , etc. Et puisqu’une interface est un ensemble de méthodes, l’interface est en fait un déterminant du comportement . Une autre option pour utiliser une interface consiste à définir le rôle d'un objet. Observateur, auditeur , etc. Dans ce cas, la méthode est en fait l’incarnation d’une réaction à un événement extérieur. C’est encore une fois un comportement. Un objet peut bien entendu avoir plusieurs comportements différents. S'il doit être rendu, il est rendu. S'il a besoin d'être sauvé, il est sauvé. Eh bien, etc. En conséquence, la possibilité d’hériter de classes qui définissent le comportement est très, très utile. De même, un objet peut avoir plusieurs rôles différents. Cependant, la mise en œuvrele comportement dépend entièrement de la conscience de la classe d’enfants. L'héritage d'une interface (son implémentation) dit qu'un objet de cette classe doit être capable de faire ceci et cela. Et COMMENT il fait cela est déterminé indépendamment par chaque classe implémentant l'interface. Revenons aux erreurs d'héritage. Mon expérience dans le développement de divers systèmes montre qu'avec l'héritage des interfaces, vous pouvez implémenter n'importe quel système sans utiliser l'héritage d'implémentations multiples. Et par conséquent, lorsque je rencontre des plaintes concernant le manque d'héritage multiple sous la forme sous laquelle il existe en C++, c'est pour moi un signe certain d'une conception incorrecte. L’erreur la plus courante est celle que j’ai déjà mentionnée : l’héritage est confondu avec l’agrégation. Parfois, cela se produit en raison d'hypothèses incorrectes. Ceux. Prenons, par exemple, un compteur de vitesse. On prétend que la vitesse ne peut être mesurée qu'en mesurant la distance et le temps, après quoi le compteur de vitesse est hérité avec succès de la règle et de l'horloge, devenant ainsi la règle et l'horloge, selon la définition de héritage. (Mes demandes de mesurer le temps avec un compteur de vitesse recevaient généralement des plaisanteries. Ou alors elles ne répondaient pas du tout.) Quelle est l'erreur ici ? Dans la prémisse. Le fait est que le compteur de vitesse ne mesure pas le temps. Et les distances aussi, d’ailleurs. Le compteur kilométrique, que l'on retrouve dans tout compteur de vitesse, est un exemple classique d'un deuxième appareil dans le même boîtier, c'est-à-dire agrégation. Il n’est pas nécessaire de mesurer la vitesse. Il peut être complètement supprimé - cela n'affectera en rien la mesure de la vitesse. Parfois, ces erreurs sont commises délibérément. C'est bien pire. "Oui, je sais que c'est mal, mais c'est plus pratique pour moi." A quoi cela pourrait-il conduire ? Mais voici quoi : nous hériterons d'un char d'un canon et d'une mitrailleuse. C'est plus pratique ainsi. En conséquence, le char devient un canon et une mitrailleuse. Ensuite, nous équiperons l'avion de deux mitrailleuses et d'un canon. Qu'obtenons-nous ? Un avion avec des armes suspendues sous la forme de trois chars ! Parce qu'il y a DÉFINITIVEMENT une personne qui, sans le comprendre, utilise un char comme mitrailleuse. Exclusivement selon la hiérarchie successorale. Et il aura tout à fait raison, car celui qui a conçu une telle hiérarchie a commis une erreur.
En général, je ne comprends pas vraiment l’approche « c’est plus pratique pour moi comme ça ». C'est pratique d'écrire comme un auditeur, et ceux qui disent la grammaire de base sont kazly. J'exagère, bien sûr, mais l'idée principale demeure : en plus de la commodité momentanée, l'alphabétisation existe. Ce concept est défini à partir de l'expérience d'un très grand nombre de personnes. En fait, c’est ce qu’on appelle en anglais « best practices » : la meilleure solution. Et le plus souvent, des solutions qui semblent plus simples entraînent de nombreux problèmes à l’avenir.
Bien entendu, cet exemple est grandement exagéré et donc absurde. Il existe cependant des cas moins évidents qui entraînent néanmoins des conséquences catastrophiques. En héritant d'un objet, plutôt que de l'agréger, le développeur donne à chacun la possibilité d'utiliser directement les fonctionnalités de l'objet parent. Avec tout ce que cela implique. Imaginez que vous ayez une classe qui fonctionne avec une base de données, DBManager . Vous créez une autre classe qui fonctionnera avec vos données à l'aide de DBManager - DataManager . Cette classe effectuera le contrôle des données, les transformations, les actions supplémentaires, etc. En général, une couche entre la couche métier et la couche de base. Si vous héritez de DataManager de DBManager, toute personne l'utilisant aura accès directement à la base de données. Et, par conséquent, il sera capable d’effectuer n’importe quelle action en contournant le contrôle, les transformations, etc. D’accord, supposons que personne ne veuille causer un préjudice intentionnel et que les actions directes seront compétentes. Mais! Supposons que la base ait changé. Je veux dire, certains principes de contrôle ou de transformation ont changé. DataManager a été modifié. Mais le code qui fonctionnait auparavant directement avec la base de données continuera à fonctionner. Ils ne se souviendront probablement pas de lui. Le résultat sera une erreur d’une telle classe que ceux qui la rechercheront deviendront gris. Il ne viendrait jamais à l’esprit de quiconque de travailler avec la base de données en contournant le DataManager. Au fait, un exemple tiré de la vraie vie. Il a fallu TRÈS longtemps pour trouver l'erreur. Enfin, je le répète. L'héritage ne doit être utilisé QUE lorsqu'il existe une relation « est ». Parce que c'est l'essence même de l'héritage : la possibilité d'utiliser les objets d'une classe enfant comme objets d'une classe de base. S'il n'y a pas de relation « est » entre les classes, il NE DEVRAIT PAS y avoir d'héritage !!! Jamais et en aucun cas. Et encore plus – simplement parce que c’est très pratique. Lien vers la source originale : http://www.skipy.ru/philosophy/inheritance.html
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION