Avantages de l'héritage prototypique sur classique?

J'ai donc finalement arrêté de me traîner les pieds toutes ces années et j'ai décidé d'apprendre JavaScript "correctement". L'un des éléments les plus égratignants de la conception des langages est sa mise en œuvre de l'héritage. Ayant de l'expérience dans Ruby, j'étais vraiment heureux de voir des fermetures et un typage dynamique; mais pour la vie de moi, Je ne peux pas comprendre quels avantages doivent être tirés des instances d'objet utilisant d'autres instances pour l'héritage.

247
demandé sur HerbalMart 2010-05-10 11:22:04

5 réponses

Je sais que cette réponse a 3 ans de retard mais je pense vraiment que les réponses actuelles ne fournissent pas assez d'informations sur Comment l'héritage prototypique est meilleur que l'héritage classique.

Voyons D'abord les arguments les plus courants des programmeurs JavaScript pour défendre l'héritage prototypique (je prends ces arguments du pool de réponses actuel):

  1. C'est simple.
  2. c'est puissant.
  3. cela conduit à une plus petite, moins redondante code.
  4. c'est dynamique et donc c'est mieux pour les langages dynamiques.

Maintenant, ces arguments sont tous valables, mais personne n'a pris la peine d'expliquer pourquoi. C'est comme dire à un enfant qu'étudier les mathématiques est important. Bien sûr, mais l'enfant ne s'en soucie certainement pas; et vous ne pouvez pas faire un enfant comme les mathématiques en disant que c'est important.

Je pense que le problème avec l'héritage prototypique est qu'il est expliqué du point de vue de JavaScript. J'adore JavaScript, mais l'héritage prototypique en JavaScript est faux. Contrairement à l'héritage classique, il existe deux modèles d'héritage prototypique:

  1. le modèle prototypique de l'héritage prototypique.
  2. le modèle constructeur de l'héritage prototypique.

Malheureusement, JavaScript utilise le modèle constructeur de l'héritage prototypique. C'est parce que lorsque JavaScript a été créé, Brendan Eich (le créateur de JS) voulait qu'il ressemble à Java (qui a Classique héritage):

Et nous le poussions comme un petit frère à Java, car un langage complémentaire comme Visual Basic était au C++ dans les familles de langages de Microsoft à L'époque.

C'est mauvais parce que lorsque les gens utilisent des constructeurs en JavaScript, ils pensent que les constructeurs héritent d'autres constructeurs. Ce qui est faux. Dans l'héritage prototypique, les objets héritent d'autres objets. Les constructeurs ne viennent jamais dans l'image. C'est ce qui confond le plus gens.

Les gens de langages comme Java, qui a un héritage classique, sont encore plus confus parce que bien que les constructeurs ressemblent à des classes, ils ne se comportent pas comme des classes. Comme Douglas Crockford a déclaré:

Cette indirection était destinée à rendre le langage plus familier aux programmeurs formés classiquement, mais n'a pas réussi à le faire, comme nous pouvons le voir à partir de L'opinion très faible des programmeurs Java sur JavaScript. Le modèle de constructeur de JavaScript a fait pas d'appel à la classique de la foule. Il a également obscurci la véritable nature prototypique de JavaScript. En conséquence, il y a très peu de programmeurs qui savent comment utiliser le langage efficacement.

Voilà. Directement de la bouche des chevaux.

Véritable Héritage Prototypique

L'héritage prototypique est tout au sujet des objets. Les objets héritent des propriétés d'autres objets. C'est tout là est à lui. Il existe deux façons de créer des objets en utilisant prototypal héritage:

  1. créez un nouvel objet.
  2. cloner un objet existant et l'étendre.

Remarque: JavaScript vous propose deux moyens pour cloner un objet - délégation et concaténation. Désormais, j'utiliserai le mot "clone" pour désigner exclusivement l'héritage via la délégation, et le mot "copie" pour désigner exclusivement l'héritage via la concaténation.

Assez de parler. Nous allons voir quelques exemples. Dites que j'ai un cercle de rayon 5:

var circle = {
    radius: 5
};

On peut calculer l'aire et la circonférence du cercle à partir de son rayon:

circle.area = function () {
    var radius = this.radius;
    return Math.PI * radius * radius;
};

circle.circumference = function () {
    return 2 * Math.PI * this.radius;
};

Maintenant, je veux créer un autre cercle de rayon 10. Une façon de le faire serait:

var circle2 = {
    radius: 10,
    area: circle.area,
    circumference: circle.circumference
};

Cependant, JavaScript fournit un meilleur moyen- délégation . Crockford Object.create la fonction est utilisée pour faire ceci:

var circle2 = Object.create(circle);
circle2.radius = 10;

C'est tout. Vous venez de faire un héritage prototypique en JavaScript. N'était-ce pas simple? Vous prenez un objet, clonez-le, changez tout ce dont vous avez besoin, et bon presto - vous avez vous-même un tout nouvel objet.

Maintenant, vous pourriez demander, "Comment est-ce simple? Chaque fois que je veux créer un nouveau cercle, je dois cloner circle et lui attribuer manuellement un rayon". Eh bien, la solution est d'utiliser une fonction pour faire le levage de charges lourdes pour vous:

function createCircle(radius) {
    var newCircle = Object.create(circle);
    newCircle.radius = radius;
    return newCircle;
}

var circle2 = createCircle(10);

, En fait, vous pouvez combiner tout cela en un seul objet littéral comme suit:

var circle = {
    radius: 5,
    create: function (radius) {
        var circle = Object.create(this);
        circle.radius = radius;
        return circle;
    },
    area: function () {
        var radius = this.radius;
        return Math.PI * radius * radius;
    },
    circumference: function () {
        return 2 * Math.PI * this.radius;
    }
};

var circle2 = circle.create(10);

Héritage prototypique en JavaScript

Si vous remarquez dans ce qui précède programme la fonction create crée un clone de circle, lui assigne un nouveau radius, puis le renvoie. C'est exactement ce qu'un constructeur fait en JavaScript:

function Circle(radius) {
    this.radius = radius;
}

Circle.prototype.area = function () {
    var radius = this.radius;
    return Math.PI * radius * radius;
};

Circle.prototype.circumference = function () {         
    return 2 * Math.PI * this.radius;
};

var circle = new Circle(5);
var circle2 = new Circle(10);

Le modèle constructeur en JavaScript est le modèle prototypique inversé. Au lieu de créer un objet, vous créez un constructeur. Le mot clé new lie le pointeur this à l'intérieur du constructeur à un clone du prototype du constructeur.

Ça semble déroutant? C'est parce que le modèle constructeur dans JavaScript complique inutilement les choses. C'est ce que la plupart des programmeurs trouvent difficile à comprendre.

Au lieu de penser à des objets héritant d'autres objets, ils pensent à des constructeurs héritant d'autres constructeurs et deviennent alors complètement confus.

Il y a tout un tas d'autres raisons pour lesquelles le modèle constructeur en JavaScript devrait être évité. Vous pouvez lire à leur sujet dans mon blog ici: Constructeurs vs Prototypes


Donc quels sont les avantages de l'héritage prototypique par rapport à l'héritage classique? Passons en revue les arguments les plus courants et expliquons pourquoi.

1. L'héritage prototypique est Simple

CMS déclare dans sa réponse:

À mon avis, le principal avantage de l'héritage prototypique est sa simplicité.

Considérons ce que nous venons de faire. Nous avons créé un objet circle, qui avait un rayon de 5. Ensuite, nous l'avons cloné et donné le cloner un rayon de 10.

Par conséquent, nous n'avons besoin que de deux choses pour faire fonctionner l'héritage prototypique:

  1. un moyen de créer un nouvel objet (par exemple des littéraux d'objet).
  2. Un moyen d'étendre un objet existant (par exemple, Object.create).

En revanche, l'héritage classique est beaucoup plus compliqué. En héritage classique, vous avez:

  1. Classes.
  2. objet.
  3. Interfaces.
  4. Classes Abstraites.
  5. Finale Classe.
  6. Classes De Base Virtuelles.
  7. constructeurs.
  8. destructeurs.

Vous avez l'idée. Le fait est que l'héritage prototypique est plus facile à comprendre, plus facile à mettre en œuvre et plus facile à raisonner.

Comme Steve Yegge le dit dans son billet de blog classique " Portrait d'un N00b":

Les métadonnées sont tout type de description ou de modèle d'autre chose. Les commentaires dans votre code ne sont qu'une description en langage naturel du calcul. Ce qui rend les métadonnées méta-données, c'est que ce n'est pas strictement nécessaire. Si j'ai un chien avec des papiers pedigree, et que je perds les papiers, j'ai toujours un chien parfaitement valide.

Dans le même sens, les classes ne sont que des méta-données. Les Classes ne sont pas strictement requises pour l'héritage. Cependant, certaines personnes (généralement n00bs) trouvent les classes plus confortables avec lesquelles travailler. Il leur donne un faux sentiment de sécurité.

Eh Bien, nous savons aussi que les types statiques sont juste des métadonnées. Ils sont un genre de commentaire spécialisé destiné à deux types de lecteurs: les programmeurs et les compilateurs. Les types statiques racontent une histoire sur le calcul, probablement pour aider les deux groupes de lecteurs à comprendre l'intention du programme. Mais les types statiques peuvent être jetés à l'exécution, car à la fin ils ne sont que des commentaires stylisés. Ils sont comme pedigree paperwork: il pourrait faire un certain type de personnalité précaire plus heureux au sujet de leur chien, mais le chien ne certainement pas soins.

Comme je l'ai dit plus tôt, les cours donnent aux gens un faux sentiment de sécurité. Par exemple, vous obtenez trop de NullPointerExceptions en Java, même lorsque votre code est parfaitement lisible. Je trouve que l'héritage classique entrave généralement la programmation, mais peut-être que C'est juste Java. Python a un système d'héritage classique incroyable.

2. L'héritage prototypique est puissant

La plupart des programmeurs qui viennent d'un arrière-plan classique soutiennent que l'héritage classique est plus puissant que l'héritage prototypique parce qu'il a:

  1. variables privées.
  2. héritage Multiple.

Cette affirmation est fausse. Nous savons déjà que JavaScript prend en charge les variables privées via des fermetures, mais qu'en est-il de l'héritage multiple? Les objets en JavaScript n'ont qu'un seul prototype.

La vérité est que l'héritage prototypique prend en charge l'héritage de plusieurs prototypes. L'héritage prototypique signifie simplement qu'un objet hérite d'un autre objet. Il y a en fait deux façons d'implémenter l'héritage prototypique :

  1. délégation ou héritage différentiel
  2. clonage ou héritage Concaténatif

Oui JavaScript permet uniquement aux objets de déléguer à un autre objet. Cependant, il vous permet de copier les propriétés d'un nombre arbitraire d'objets. Par exemple _.extend fait juste cela.

Bien sûr, beaucoup de programmeurs ne considèrent pas cela comme un véritable héritage parce que instanceof et isPrototypeOf dire le contraire. Cependant, cela peut être facilement résolu en stockant un tableau de prototypes sur chaque objet qui hérite d'un prototype via la concaténation:

function copyOf(object, prototype) {
    var prototypes = object.prototypes;
    var prototypeOf = Object.isPrototypeOf;
    return prototypes.indexOf(prototype) >= 0 ||
        prototypes.some(prototypeOf, prototype);
}

Par conséquent, l'héritage prototypique est tout aussi puissant que l'héritage classique. En fait c'est beaucoup plus puissant que l'héritage classique car dans l'héritage prototypique vous pouvez choisir à la main quelles propriétés copier et quelles propriétés omettre de différentes prototype.

Dans l'héritage classique, il est impossible (ou du moins très difficile) de choisir les propriétés que vous voulez hériter. Ils utilisent des classes de base virtuelles et des interfaces pour résoudre le problème du diamant .

En JavaScript cependant, vous n'entendrez probablement jamais parler du problème du diamant car vous pouvez contrôler exactement les propriétés que vous souhaitez hériter et à partir de quels prototypes.

3. L'héritage prototypique est moins redondant

Ce point est un peu plus difficile à expliquer car l'héritage classique ne conduit pas nécessairement à plus de code redondant. En fait, l'héritage, qu'il soit classique ou prototypique, est utilisé pour réduire la redondance dans le code.

Un argument pourrait être que la plupart des langages de programmation avec héritage classique sont typés statiquement et nécessitent que l'utilisateur déclare explicitement les types (contrairement à Haskell qui a un typage statique implicite). Par conséquent, cela conduit à un code plus verbeux.

Java est connu pour ce comportement. Je me souviens distinctement de Bob Nystrom mentionnant l'anecdote suivante dans son billet de blog à propos de Pratt Parsers :

Vous devez aimer le niveau de bureaucratie de Java "signez-le en quadruplicate" ici.

Encore une fois, je pense que C'est seulement parce que Java craint tellement.

Un argument valide est que toutes les langues qui ont un héritage classique ne supportent pas l'héritage multiple. Encore une fois Java vient à l'esprit. Oui Java a des interfaces, mais ce n'est pas suffisant. Parfois, vous avez vraiment besoin d'héritage multiple.

Puisque l'héritage prototypique permet l'héritage multiple, le code qui nécessite l'héritage multiple est moins redondant s'il est écrit en utilisant l'héritage prototypique plutôt que dans un langage qui a un héritage classique mais pas d'héritage multiple.

4. L'héritage prototypique est dynamique

L'un des avantages les plus importants de l'héritage prototypique est que vous pouvez ajouter de nouvelles propriétés aux prototypes après leur création. Cela vous permet d'ajouter de nouvelles méthodes à un prototype qui sera automatiquement mis à la disposition de tous les objets qui délèguent à ce prototype.

Ce n'est pas possible dans l'héritage classique car une fois qu'une classe est créée, vous ne pouvez pas la modifier lors de l'exécution. C'est probablement le plus grand avantage de l'héritage prototypique par rapport à l'héritage classique, et il aurait dû être au sommet. Cependant j'aime sauver le meilleur pour le fin.

Conclusion

L'héritage prototypique compte. Il est important d'éduquer les programmeurs JavaScript sur les raisons d'abandonner le modèle de constructeur de l'héritage prototypique en faveur du modèle prototypique de l'héritage prototypique.

Nous devons commencer à enseigner JavaScript correctement et cela signifie montrer aux nouveaux programmeurs comment écrire du code en utilisant le modèle prototypique au lieu du modèle constructeur.

Non seulement ce sera plus facile à expliquer héritage prototypique en utilisant le modèle prototypique, mais il fera également de meilleurs programmeurs.

Si vous avez aimé cette réponse, vous devriez également lire mon billet de blog sur " pourquoi L'héritage prototypique compte ". Faites-moi confiance, vous ne serez pas déçu.

506
répondu Aadit M Shah 2018-08-06 13:01:32

Permettez-moi de répondre réellement à la question en ligne.

L'héritage Prototype a les vertus suivantes:

  1. Il est mieux adapté aux langages dynamiques car l'héritage est aussi dynamique que l'environnement dans lequel il se trouve. (L'applicabilité à JavaScript devrait être évidente ici.) Cela vous permet de faire des choses rapidement à la volée comme la personnalisation des classes sans énormes quantités de code d'infrastructure.
  2. il est plus facile d'implémenter un schéma d'objet de prototypage que schémas de dichotomie classe/objet classiques.
  3. il élimine le besoin des arêtes vives complexes autour du modèle d'objet comme les "métaclasses"(Je n'ai jamais aimé les métaclasses... Pardon!) ou "valeurs propres" ou similaire.

Il présente cependant les inconvénients suivants:

  1. la vérification de Type d'un langage prototype n'est pas impossible, mais c'est très, très difficile. La plupart des" contrôles de type " des langages prototypiques sont des contrôles de style "Duck typing"à l'exécution. Ce n'est pas adapté à tous les environnement.
  2. il est également difficile de faire des choses comme optimiser l'envoi de méthode par statique (ou, souvent, même dynamique!) analyse. Il peut (j'insiste: peut) être très inefficace très facilement.
  3. de même, la création d'objets peut être (et est généralement) beaucoup plus lente dans un langage de prototypage que dans un schéma de dichotomie classe / objet plus conventionnel.

Je pense que vous pouvez lire entre les lignes ci-dessus et trouver les avantages correspondants et les inconvénients des schémas de classe/objet traditionnels. Il y a, bien sûr, plus dans chaque zone, donc je vais laisser le reste à d'autres personnes qui répondent.

34
répondu JUST MY correct OPINION 2010-05-10 08:11:04

IMO le principal avantage de l'héritage prototypique est sa simplicité.

La nature prototypique de la langue peut confondre les gens qui sontclassiquement formés, mais il s'avère qu'en fait c'est unvraiment concept simple et puissant, héritage différentiel .

Vous n'avez pas besoin de faire classification , votre code est plus petit, moins redondant, les objets héritent d'autres objets plus généraux.

Si vous pensez prototypiquement vous remarquerez bientôt que vous n'avez pas besoin de classes...

L'héritage prototypique sera beaucoup plus populaire dans un proche avenir, la spécification ECMAScript 5th Edition a introduit le Object.create méthode, qui vous permet de produire une nouvelle instance d'objet qui hérite d'une autre d'une manière très simple:

var obj = Object.create(baseInstance);

Cette nouvelle version de la norme est mise en œuvre par tous les fournisseurs de navigateurs, et je pense que nous allons commencer à voir plus pur héritage prototypique...

26
répondu CMS 2012-03-11 11:11:31

Il n'y a vraiment pas beaucoup à choisir entre les deux méthodes. L'idée de base à saisir est que lorsque le moteur JavaScript reçoit une propriété d'un objet à lire, il vérifie d'abord l'instance et si cette propriété est manquante, il vérifie la chaîne du prototype. Voici un exemple qui montre la différence entre prototypique et classique:

Prototypes

var single = { status: "Single" },
    princeWilliam = Object.create(single),
    cliffRichard = Object.create(single);

console.log(Object.keys(princeWilliam).length); // 0
console.log(Object.keys(cliffRichard).length); // 0

// Marriage event occurs
princeWilliam.status = "Married";

console.log(Object.keys(princeWilliam).length); // 1 (New instance property)
console.log(Object.keys(cliffRichard).length); // 0 (Still refers to prototype)

Classique avec les méthodes d'instance (inefficace car chaque instance stocke sa propre propriété)

function Single() {
    this.status = "Single";
}

var princeWilliam = new Single(),
    cliffRichard = new Single();

console.log(Object.keys(princeWilliam).length); // 1
console.log(Object.keys(cliffRichard).length); // 1

Efficace classique

function Single() {
}

Single.prototype.status = "Single";

var princeWilliam = new Single(),
    cliffRichard = new Single();

princeWilliam.status = "Married";

console.log(Object.keys(princeWilliam).length); // 1
console.log(Object.keys(cliffRichard).length); // 0
console.log(cliffRichard.status); // "Single"

Comme vous pouvez le voir, puisqu'il est possible de manipuler le prototype des "classes" déclarées dans le style classique, il n'y a vraiment aucun avantage à utiliser l'héritage prototypique. C'est un sous-ensemble de la méthode classique.

9
répondu Noel Abrahams 2012-09-20 12:59:43

Développement Web: héritage prototypique vs héritage classique

Http://chamnapchhorn.blogspot.com/2009/05/prototypal-inheritance-vs-classical.html

Héritage classique Vs prototypal-débordement de pile

Héritage classique Vs prototypique

2
répondu ratty 2017-05-23 12:34:50