héritage basé sur un prototype ou basé sur une classe

208

En JavaScript, chaque objet est à la fois une instance et une classe. Pour faire l'héritage, vous pouvez utiliser n'importe quelle instance d'objet comme prototype.

En Python, C ++, etc., il existe des classes et des instances, en tant que concepts séparés. Pour faire l'héritage, vous devez utiliser la classe de base pour créer une nouvelle classe, qui peut ensuite être utilisée pour produire des instances dérivées.

Pourquoi JavaScript est-il allé dans cette direction (orientation objet basée sur un prototype)? quels sont les avantages (et inconvénients) de l'OO basé sur un prototype par rapport à l'OO traditionnel basé sur une classe?

Stefano Borini
la source
10
JavaScript a été influencé par Self qui était le premier langage à héritage prototypique. À cette époque, l'héritage classique était à la mode, introduit pour la première fois dans Simula. Mais l'héritage classique était trop compliqué. Ensuite, David Ungar et Randall Smith ont eu une révélation après avoir lu GEB - "L'événement le plus spécifique peut servir d'exemple général d'une classe d'événements." Ils ont réalisé que les classes ne sont pas nécessaires pour la programmation orientée objet. C'est pourquoi le Soi est né. Pour savoir comment l'héritage prototypique est meilleur que l'héritage classique, lisez ceci: stackoverflow.com/a/16872315/783743 =)
Aadit M Shah
@AaditMShah Qu'est-ce / qui est GEB?
Alex
3
@Alex GEB est un livre écrit par Douglas Hofstadter. C'est une abréviation de Gödel Escher Bach. Kurt Gödel était mathématicien. Escher était un artiste. Bach était pianiste.
Aadit M Shah

Réponses:

201

Il y a une centaine de problèmes de terminologie ici, principalement construits autour de quelqu'un (pas vous) essayant de faire sonner son idée comme The Best.

Tous les langages orientés objet doivent pouvoir traiter plusieurs concepts:

  1. encapsulation de données ainsi que les opérations associées sur les données, connues sous le nom de membres de données et de fonctions membres, ou de données et méthodes, entre autres.
  2. l'héritage, la possibilité de dire que ces objets sont comme cet autre ensemble d'objets SAUF pour ces changements
  3. polymorphisme ("plusieurs formes") dans lequel un objet décide lui-même des méthodes à exécuter, afin que vous puissiez dépendre du langage pour acheminer correctement vos requêtes.

Maintenant, en ce qui concerne la comparaison:

La première chose est toute la question "classe" vs "prototype". L'idée a commencé à l'origine dans Simula, où avec une méthode basée sur les classes, chaque classe représentait un ensemble d'objets qui partageaient le même espace d'état (lire "valeurs possibles") et les mêmes opérations, formant ainsi une classe d'équivalence. Si vous regardez en arrière Smalltalk, puisque vous pouvez ouvrir une classe et ajouter des méthodes, c'est en fait la même chose que ce que vous pouvez faire en Javascript.

Plus tard, les langages OO voulaient pouvoir utiliser la vérification de type statique, nous avons donc eu la notion d'un ensemble de classes fixe au moment de la compilation. Dans la version open-class, vous aviez plus de flexibilité; dans la version la plus récente, vous aviez la possibilité de vérifier certains types d'exactitude sur le compilateur qui auraient autrement nécessité des tests.

Dans un langage "basé sur les classes", cette copie a lieu au moment de la compilation. Dans un langage prototype, les opérations sont stockées dans la structure de données prototype, qui est copiée et modifiée au moment de l'exécution. De manière abstraite, cependant, une classe est toujours la classe d'équivalence de tous les objets qui partagent le même espace d'état et les mêmes méthodes. Lorsque vous ajoutez une méthode au prototype, vous créez effectivement un élément d'une nouvelle classe d'équivalence.

Maintenant, pourquoi faire ça? principalement parce qu'il permet un mécanisme simple, logique et élégant au moment de l'exécution. maintenant, pour créer un nouvel objet, ou pour créer une nouvelle classe, il vous suffit d'effectuer une copie complète, en copiant toutes les données et la structure de données du prototype. Vous obtenez alors l'héritage et le polymorphisme plus ou moins gratuitement: la recherche de méthode consiste toujours à demander à un dictionnaire une implémentation de méthode par nom.

La raison qui a abouti au script Javascript / ECMA est essentiellement que lorsque nous avons commencé avec cela il y a 10 ans, nous avions affaire à des ordinateurs beaucoup moins puissants et à des navigateurs beaucoup moins sophistiqués. Le choix de la méthode basée sur le prototype signifiait que l'interpréteur pouvait être très simple tout en préservant les propriétés souhaitables de l'orientation des objets.

Charlie Martin
la source
1
D'accord, ce paragaphe se lit-il comme si je voulais dire le contraire? Dahl et Nyqvist ont proposé «classe» comme un ensemble d'objets avec la même signature de méthode.
Charlie Martin
1
Ce changement le dit-il mieux?
Charlie Martin
2
Non, désolé, CLOS est de la fin des années 80 dreamsongs.com/CLOS.html Smalltalk de 1980 en.wikipedia.org/wiki/Smalltalk et Simula avec une orientation complète des objets de 1967-68 en.wikipedia.org/wiki/Simula
Charlie Martin
3
@Stephano, Ils ne sont pas si distincts que ça: Python, Ruby, Smalltalk utilisent des dictionnaires pour la recherche de méthode, et javascript et Self ont des classes. Dans une certaine mesure, vous pourriez faire valoir que la différence est simplement que les langages orientés prototype exposent leurs implémentations. Il est donc probablement bon de ne pas en faire une grosse affaire: c'est probablement plus comme l'argument entre EMACS et vi.
Charlie Martin
21
Réponse utile . +1 indésirable moins utile dans les commentaires. Je veux dire, cela fait-il une différence si CLOS ou Smalltalk était le premier? La plupart des gens ici ne sont de toute façon pas des historiens.
Adam Arold
40

Une comparaison, qui est légèrement biaisée vers l'approche basée sur les prototypes, peut être trouvée dans l'article Self: The Power of Simplicity . Le document présente les arguments suivants en faveur des prototypes:

Création par copie . La création de nouveaux objets à partir de prototypes est réalisée par une opération simple, la copie, avec une simple métaphore biologique, le clonage. La création de nouveaux objets à partir de classes se fait par instanciation, qui comprend l'interprétation des informations de format dans une classe. L'instanciation est similaire à la construction d'une maison à partir d'un plan. La copie nous interpelle comme une métaphore plus simple que l'instanciation.

Exemples de modules préexistants . Les prototypes sont plus concrets que les classes car ce sont des exemples d'objets plutôt que des descriptions de format et d'initialisation. Ces exemples peuvent aider les utilisateurs à réutiliser les modules en les rendant plus faciles à comprendre. Un système basé sur un prototype permet à l'utilisateur d'examiner un représentant typique plutôt que de lui demander de donner un sens à sa description.

Prise en charge des objets uniques . Self fournit un cadre qui peut facilement inclure des objets uniques avec leur propre comportement. Étant donné que chaque objet a des emplacements nommés et que les emplacements peuvent contenir un état ou un comportement, tout objet peut avoir des emplacements ou un comportement uniques. Les systèmes basés sur des classes sont conçus pour les situations où il existe de nombreux objets ayant le même comportement. Il n'y a pas de support linguistique pour qu'un objet possède son propre comportement unique, et il est gênant de créer une classe qui est garantie d'avoir une seule instance [ pensez modèle singleton ]. L'individu ne souffre d'aucun de ces inconvénients. Tout objet peut être personnalisé avec son propre comportement. Un objet unique peut contenir le comportement unique et une "instance" distincte n'est pas nécessaire.

Élimination de la méta-régression . Aucun objet dans un système basé sur une classe ne peut être autosuffisant; un autre objet (sa classe) est nécessaire pour exprimer sa structure et son comportement. Cela conduit à une méta-régression conceptuellement infinie: a pointest une instance de classe Point, qui est une instance de métaclasse Point, qui est une instance de métamétaclasse Point, à l'infini. D'un autre côté, dans les systèmes basés sur des prototypes, un objet peut inclure son propre comportement; aucun autre objet n'est nécessaire pour lui insuffler la vie. Les prototypes éliminent la méta-régression.

Self est probablement le premier langage à implémenter des prototypes (il a également lancé d'autres technologies intéressantes comme JIT, qui a ensuite fait son chemin dans la JVM), donc la lecture des autres papiers Self devrait également être instructive.

Vijay Mathew
la source
5
RE: Élimination de la méta-régression: dans le système d'objet Common Lisp, qui est basé sur une classe, a pointest une instance de classe Point, qui est une instance de métaclasse standard-class, qui est une instance d'elle-même, ad finitum.
Max Nanasy
Les liens vers un auto-papiers sont morts. Liens de travail: Soi: le pouvoir de la simplicité | A Self Bibliography
user1201917
24

Vous devriez consulter un excellent livre sur JavaScript par Douglas Crockford . Il fournit une très bonne explication de certaines des décisions de conception prises par les créateurs JavaScript.

L'un des aspects de conception importants de JavaScript est son système d'héritage prototypique. Les objets sont des citoyens de première classe en JavaScript, à tel point que les fonctions régulières sont également implémentées en tant qu'objets (objet 'Function' pour être précis). À mon avis, lorsqu'il a été initialement conçu pour fonctionner dans un navigateur, il devait être utilisé pour créer de nombreux objets singleton. Dans le navigateur DOM, vous trouvez cette fenêtre, documentez etc. tous les objets singleton. De plus, JavaScript est un langage dynamique typé de manière lâche (contrairement à Python qui est un langage dynamique fortement typé), par conséquent, un concept d'extension d'objet a été implémenté grâce à l'utilisation de la propriété `` prototype ''.

Je pense donc qu'il existe des avantages pour les OO basés sur des prototypes, implémentés en JavaScript:

  1. Convient dans des environnements peu typés, pas besoin de définir de types explicites.
  2. Rend incroyablement facile à implémenter le modèle singleton (comparez JavaScript et Java à cet égard, et vous saurez de quoi je parle).
  3. Fournit des moyens d'appliquer une méthode d'un objet dans le contexte d'un objet différent, en ajoutant et en remplaçant dynamiquement des méthodes à partir d'un objet, etc. (choses qui ne sont pas possibles dans des langages fortement typés).

Voici certains des inconvénients de l'OO prototypique:

  1. Pas de moyen facile d'implémenter des variables privées. Il est possible d'implémenter des variables privées à l'aide de la magie de Crockford à l'aide de fermetures , mais ce n'est certainement pas aussi trivial que d'utiliser des variables privées, par exemple Java ou C #.
  2. Je ne sais pas encore comment implémenter plusieurs héritages (pour ce que ça vaut) en JavaScript.
Amit
la source
2
Utilisez simplement une convention de dénomination pour les variables privées, comme le fait Python.
aehlke
1
dans js la façon de faire des vars privés est avec des fermetures, et cela est indépendant du type d'héritage que vous choisissez.
Benja
6
Crockford a beaucoup fait pour endommager JavaScript, en ce sens qu'un langage de script assez simple a été transformé en une fascination maîtresse pour ses composants internes. JS n'a pas de véritable portée de mot clé privé ni de véritable héritage multiple: n'essayez pas de les simuler.
Hal50000