En quoi l'héritage prototypique est-il pratiquement différent de l'héritage classique?

27

L'héritage, le polymorphisme et l'encapsulation sont les trois caractéristiques les plus importantes et les plus distinctes de la POO, et d'après eux, l'héritage a de fortes statistiques d'utilisation de nos jours. J'apprends JavaScript, et ici, ils disent tous qu'il a un héritage prototypique, et les gens partout dans le monde disent que c'est quelque chose de très différent de l'héritage classique.

Cependant, je ne peux pas comprendre quelle est leur différence du point de vue pratique? En d'autres termes, lorsque vous définissez une classe de base (prototype) puis que vous en dérivez des sous-classes, vous avez tous deux accès aux fonctionnalités de votre classe de base et vous pouvez augmenter les fonctions sur les classes dérivées. Si nous considérons ce que j'ai dit être le résultat prévu de l'héritage, alors pourquoi devrions-nous nous soucier si nous utilisons une version prototypique ou classique?

Pour me clarifier davantage, je ne vois aucune différence dans l'utilité et les modèles d'utilisation de l'héritage prototypique et classique. Cela ne m'intéresse pas à savoir pourquoi ils sont différents, car ils aboutissent tous deux à la même chose, OOAD. En quoi l'héritage prototypique est-il pratiquement (théoriquement) différent de l'héritage classique?

Saeed Neamati
la source

Réponses:

6

Article de blog récent sur JS OO

Je crois que votre comparaison est une émulation OO classique en JavaScript et OO classique et bien sûr, vous ne voyez aucune différence.

Avertissement: remplacez toutes les références à "OO prototypique" par "OO prototypique en JavaScript". Je ne connais pas les spécificités de Self ou de toute autre implémentation.

Cependant, l'OO prototypique est différent. Avec les prototypes, vous n'avez que des objets et vous ne pouvez injecter des objets que dans d'autres chaînes de prototypes d'objets. Chaque fois que vous accédez à une propriété sur un objet, vous recherchez cet objet et tous les objets de la chaîne de prototypes.

Pour l'OO prototypique, il n'y a aucune notion d'encapsulation. L'encapsulation est une caractéristique de la portée, des fermetures et des fonctions de première classe mais n'a rien à voir avec l'OO prototypique. Il n'y a pas non plus de notion d'héritage, ce que les gens appellent "héritage" n'est en fait que du polymorphisme.

Il présente cependant un polymorphisme.

Par exemple

var Dog = {
  walk: function() { console.log("walks"); }
}

var d = Object.create(Dog);
d.walk();

A clairement daccès à la méthode Dog.walket cela présente un polymorphisme.

Donc, en réalité, il y a une grande différence . Vous n'avez que du polymorphisme.

Cependant, comme mentionné, si vous le souhaitez (je ne sais pas pourquoi), vous pouvez émuler OO classique en JavaScript et avoir accès à l'encapsulation et à l'héritage (restreints).

Raynos
la source
1
@Rayons, Google pour l' héritage prototypique et vous verrez que l' héritage prototypique apparaît. Même de Yahoo!
Saeed Neamati
L'héritage @Saeed est un terme vague et couramment utilisé à mauvais escient. Ce qu'ils signifient par «héritage», j'appelle «polymorphisme». Les définitions sont trop vagues.
Raynos
Non @ Rayons, je voulais dire que le mot prototypal est le terme correct, pas prototypique . Je n'ai pas parlé d'héritage :)
Saeed Neamati
1
@Saeed, c'est l'une de ces fautes de frappe que mon esprit efface. Je ne fais pas attention à ça
Raynos
1
Hmm .. terminologie. ick. J'appellerais la façon dont les objets javascript sont liés à leurs prototypes «délégation». Le polymorphisme me semble être une préoccupation de niveau inférieur, basée sur le fait qu'un nom donné peut pointer vers un code différent pour différents objets.
Sean McMillan
15

L'héritage classique hérite du comportement, sans aucun état, de la classe parente. Il hérite du comportement au moment où l'objet est instancié.

L'héritage prototypique hérite du comportement et de l'état de l'objet parent. Il hérite du comportement et de l'état au moment où l'objet est appelé. Lorsque l'objet parent change au moment de l'exécution, l'état et le comportement des objets enfants sont affectés.

L '«avantage» de l'héritage prototypique est que vous pouvez «patcher» l'état et le comportement une fois tous vos objets instanciés. Par exemple, dans le framework Ext JS, il est courant de charger des "remplacements" qui corrigent les composants principaux du framework après que le framework a été instancié.

Joeri Sebrechts
la source
4
En pratique, si vous héritez de l'état, vous vous préparez à un monde de souffrance. L'état hérité deviendra partagé s'il vit sur un autre objet.
Sean McMillan
1
Il me semble qu'en javascript, il n'y a vraiment pas de concept de comportement distinct de l'état. Mis à part la fonction constructeur, tous les comportements peuvent être attribués / modifiés après la création d'objet comme tout autre état.
Joeri Sebrechts
Donc, Python n'a pas d'héritage classique? Si je l'ai class C(object): def m(self, x): return x*2et puis instance = C()quand je cours instance.m(3)je reçois 6. Mais si je change Calors C.m = lambda s, x: x*xet que je cours, instance.m(3)je comprends maintenant 9. Il en va de même si je crée class D(C)et modifie une méthode, Calors toutes les instances de Dréception de la méthode modifiée sont également effectuées. Suis-je incompris ou cela signifie-t-il que Python n'a pas d'héritage classique selon votre définition?
mVChr
@mVChr: ​​Vous héritez toujours d'un comportement et ne déclarez pas dans ce cas, ce qui pointe vers l'héritage classique, mais cela pointe vers un problème avec ma définition de "hérite du comportement au moment où l'objet est instancié". Je ne sais pas trop comment corriger la définition.
Joeri Sebrechts
9

Premièrement: la plupart du temps, vous utiliserez des objets, pas les définir, et l'utilisation d'objets est la même dans les deux paradigmes.

Deuxièmement: la plupart des environnements prototypiques utilisent le même type de division que les environnements basés sur des classes - des données mutables sur l'instance, avec des méthodes héritées. Il y a donc encore très peu de différence. (Voir ma réponse à cette question de débordement de pile et les programmes d'organisation du papier sans classes . Consultez Citeseer pour une version PDF .)

Troisièmement: la nature dynamique de Javascript a une influence beaucoup plus grande que le type d'héritage. Le fait que je puisse ajouter une nouvelle méthode à toutes les instances d'un type en l'affectant à l'objet de base est bien, mais je peux faire la même chose dans Ruby, en rouvrant la classe.

Quatrièmement: les différences pratiques sont petites, tandis que le problème pratique d'oublier d'utiliser newest beaucoup plus important - c'est-à-dire que vous êtes beaucoup plus susceptible d'être affecté par le fait de manquer un newque d'être affecté par la différence entre le code prototypique et le code classique .

Cela dit, la différence pratique entre l'héritage prototypique et classique est que vos choses-qui-détiennent-méthodes (classes) sont les mêmes que vos choses-qui-détiennent-données (instances). Cela signifie que vous pouvez créer vos classes au coup par coup, en utilisant tous les mêmes outils de manipulation d'objets que vous utiliseriez sur n'importe quelle instance. (C'est en fait ainsi que toutes les bibliothèques d'émulation de classe procèdent. Pour une approche pas tout à fait comme les autres, regardez Traits.js ). Ceci est principalement intéressant si vous effectuez une métaprogrammation.

Sean McMillan
la source
yay, meilleure réponse IMO!
Aivar
0

L'héritage prototypique en JavaScript est différent des classes de ces manières importantes:

Les constructeurs sont simplement des fonctions que vous pouvez appeler sans new:

function Circle (r, x, y) { 
  //stuff here
}
Var c = new Circle();
Circle.call(c, x, y, z); //This works and you can do it over and over again.

Il n'y a pas de variables ou de méthodes privées, au mieux vous pouvez le faire:

function Circle (r, x, y) {
  var color = 'red';
  function drawCircle () {
    //some code here with x, y and r
  }
  drawCircle();
  this.setX = function (x_) {
    x = x_;
    drawCircle();
  }

}
Circle.prototype.getX = function () {
   //Can't access x!
}

Dans l'exemple précédent, vous ne pouvez pas étendre la classe de manière significative si vous avez recours à de fausses variables et méthodes privées et en outre, toutes les méthodes publiques que vous avez déclarées seront recréées chaque fois qu'une nouvelle instance est créée.

Bjorn
la source
Vous pouvez étendre la "classe" de manière significative. Vous ne pouvez pas accéder aux variables locales color, r, x, yet drawCirclequi sont liés à la portée lexicaleCircle
Raynos
C'est vrai, vous pouvez, mais vous ne pouvez pas le faire sans créer un tas de méthodes «publiques» ajoutées au contexte qui sont créées chaque fois que vous créez une instance.
Bjorn
C'est un modèle très étrange que vous utilisez là-bas. Circle.call()en tant que constructeur? On dirait que vous essayez de décrire des "objets fonctionnels" (un terme impropre ...)
Sean McMillan
Ce n'est pas quelque chose que je ferais jamais, je dis simplement qu'il est possible d'appeler le constructeur encore et encore avec JavaScript mais pas dans les langages avec des classes traditionnelles.
Bjorn
1
Mais en quoi ces différences sont-elles «importantes»? Je ne vois pas la construction d'objets en appelant une fonction, pas en utilisant 'new', comme 'important', juste une petite différence de syntaxe. De même, ne pas avoir de variables privées n'est pas fondamentalement différent, juste une petite différence. De plus, vous pouvez avoir (quelque chose de similaire à) des variables privées, comme vous le décrivez.
Roel