Point de vue d'EcmaScript sur la théorie générale de la POO

Bonjour, Habr!

À ce jour, je n'ai traduit que des articles intéressants, à mon avis, d'auteurs anglophones. Et maintenant, il est temps d'écrire quelque chose vous-même. Pour le premier article, j'ai choisi un sujet qui, j'en suis sûr, sera utile aux développeurs débutants qui souhaitent évoluer vers le "milieu", car il analysera la similitude / différence entre JavaScript et les langages de programmation classiques (C ++, C #, Java) en termes de POO. Commençons donc!

Dispositions générales sur le paradigme


Si nous regardons la définition de JavaScript sur Wikipédia , nous verrons le concept suivant:
JavaScript (/ ˈdʒɑːvɑːˌskrɪpt /; abbr. JS /ˈdʒeɪ.ɛs./) est un langage de programmation multi-paradigmes. Prend en charge les styles orientés objet, impératifs et fonctionnels. Il s'agit d'une implémentation du langage ECMAScript (norme ECMA-262).

Comme il résulte de cette définition, JavaScript n'existe pas par lui-même, mais est une implémentation de certaines spécifications EcmaScript. En plus de cela, d'autres langages implémentent cette spécification.

Les paradigmes suivants sont présents dans EcmaScript (ci-après ES):

  • structurel
  • OOP
  • fonctionnel
  • impératif
  • orienté vers l'aspect (dans de rares cas)

La POO dans ES est implémentée sur une organisation prototype . Des développeurs novices en réponse à la question: "En quoi la POO dans JS est-elle différente de la POO dans les langages classiques." En règle générale, ils deviennent très vagues: «Les cours sont en langues classiques et les prototypes en JS».

En réalité, la situation est un peu plus compliquée. En termes de comportement, la différence entre l' organisation de classe dynamique et l' organisation de prototype est faible (elle existe certainement, mais pas si globalement).

Jetez un œil à Python ou Ruby. Dans ces langues, la POO est basée sur une organisation de classe dynamique. Dans ces deux langages, nous pouvons changer dynamiquement la classe d'un objet à mesure que le programme progresse et les changements au sein de la classe affectent également dynamiquement les entités qu'il génère. Tout comme dans JS, mais dans JS, OOP est basé sur des prototypes.

Une différence significative entre les langues avec une organisation de classe statique et une organisation de prototype. La différence en soi est «qu'il y a des classes. ici les prototypes »ne sont pas si importants.

Sur quoi repose l'organisation de la classe statique?


La base de ce type de POO sont les concepts de «Classe» et «Essence». Une classe est un certain ensemble généralisé et formalisé de caractéristiques d'entités qu'elle peut générer. C'est-à-dire c'est un certain plan général de tous les objets qu'il génère.

Les caractéristiques sont de deux types. Propriétés (description d'une entité) et méthodes (activité d'une entité, leur comportement).

Les entités générées par une classe sont des copies de cette classe, mais avec des propriétés initialisées. Comme nous le voyons, la classe réglemente strictement la description d'une entité (fournissant un ensemble de propriétés strictement défini) et son comportement (fournissant une liste de méthodes strictement définies).

Voici un petit exemple sur JAVA:

class Person{ String name; //  int age; //  void displayInfo(){ System.out.printf("Name: %s \tAge: %d\n", name, age); } } 

Créez maintenant une instance de la classe:

 public class Program{ public static void main(String[] args) { Person tom; } } 

Notre entité tom possède toutes les caractéristiques de la classe Person , elle possède également toutes les méthodes de sa classe.

Le paradigme de POO offre une très large palette de possibilités de réutilisation de code, l'une de ces fonctionnalités est l' héritage .

Une classe peut étendre une autre classe, créant ainsi une relation de généralisation-spécialisation. Dans ce cas, les propriétés de la classe générale (superclasse) sont copiées dans l'essence de la classe descendante lors de leur création, et les méthodes sont disponibles par référence (selon la chaîne hiérarchique d'héritage). Dans le cas du typage de classe statique, cette chaîne est statique et dans le cas du typage dynamique, elle peut changer pendant l'exécution du programme. C'est la différence la plus importante. Je vous conseille maintenant de vous souvenir de ce moment. De plus, lorsque nous arriverons à l'organisation Prototype, l'essence du problème de la réponse «il y a des classes, puis des prototypes» deviendra évidente.

Quels sont les inconvénients de cette approche?

Je pense qu'il est évident que:

  • En substance, il peut y avoir des caractéristiques qui ne seront jamais utiles.
  • Une classe ne peut pas modifier, ajouter, supprimer dynamiquement les propriétés et méthodes qu'elle fournit aux entités générées, c'est-à-dire ne peut pas changer sa signature.
  • En substance, les propriétés ou méthodes qui ne sont pas présentes dans la classe du parent (ou la chaîne hiérarchique des parents) ne peuvent pas être présentes
  • La consommation de mémoire est proportionnelle au nombre de liens dans la hiérarchie d'héritage (en raison des propriétés de copie)

Sur quoi repose l'organisation du prototype?


Le concept clé de l'organisation prototype est un objet mutable dynamique (dmo). DMO n'a pas besoin d'une classe. Lui-même peut stocker toutes ses propriétés et méthodes.

Lors de la définition d'un DMO d'une propriété, il vérifie la présence de cette propriété. S'il existe une propriété, elle est simplement attribuée; sinon, la propriété est ajoutée et initialisée avec la valeur transmise. Les OGD peuvent changer leur signature pendant le programme autant de fois qu'ils le souhaitent.

Voici un exemple:

 //        const Person = { name: null, age: null, sayHi() { return `Hi! My name is ${this.name}. I'm ${this.age} years old.` } } const Tom = { //-       } Tom.__proto__ = Person; 

Je pense que tout le monde dans le sujet sait que la syntaxe des classes est apparue dans ES6, mais ce n'est rien de plus que du sucre syntaxique, c'est-à-dire prototypes sous le capot. Le code ci-dessus ne doit pas être considéré comme une bonne pratique de codage. Ce n'est rien de plus qu'une illustration, il est présenté sous cette forme (maintenant tous les gens normaux utilisent des classes ES6) afin de ne pas confondre le lecteur et souligner la différence dans les concepts théoriques.

Si nous sortons l'objet Tom sur la console, nous verrons que l'objet lui-même n'a que le lien _proto_, qui y est toujours présent par défaut. La référence pointe vers l'objet Person, qui est un prototype de l'objet Tom.

Un prototype est un objet qui sert de prototype à d'autres objets ou à un objet dans lequel un autre objet peut dessiner des propriétés et des méthodes si elles sont nécessaires.

Le prototype d'un objet peut être n'importe quel objet, de plus, un objet peut réaffecter son prototype pendant le programme.

Revenons à notre Tom:

 Tom.name = 'Tom'; //    Tom.surname = 'Williams'; //    Tom.age = 28;//    Tom.sayHi();//  sayHi,      ,    ,       const tomSon = { name: 'John', age: 5, sayHi() { return this.__proto__.sayHi.call(this) + `My father is ${this.__proto__.name} ${this.surname}`; } } //,     tomSon.__proto__ = Tom; tomSon.sayHi();//  "Hi! My name is John. I'm 5 years old.My father is Tom Williams" 

Notez que le nom, les propriétés d'âge et la méthode sayHi sont les propriétés de l'objet tomSon. Dans le même temps, dans tomSon, sayHi appelle explicitement la méthode prototype sayHi comme si elle se trouvait dans l'objet Tom, mais en fait elle n'est pas là, et elle revient implicitement du prototype Person. Nous opérons également explicitement sur le nom de propriété du prototype et obtenons implicitement la propriété du nom de famille, que nous appelons notre propre propriété de l'objet tomSon, mais en réalité elle n'est pas là. La propriété du nom de famille est implicitement tirée via le lien __proto__ du prototype.

Nous continuons le développement de l'histoire de notre Tom et de son fils John.

 // ,    (  ) //  ,   ,    , //      const Ben = { name: 'Ben', surname: 'Silver', age: 42, sayHi() { return `Hello! I'm ${this.name} ${this.surname}. `; } } tomSon.nativeFather = Tom; tomSon.__proto__= Ben; tomSon.sayHi(); //    (),     () //   'Hello! I'm John Silver. My father is Ben Silver' 

Veuillez noter que pendant le programme, nous avons changé le prototype de l'objet déjà créé. Il s'agit de la similitude de l' organisation prototype et de l' organisation de classe dynamique . C'est pourquoi la réponse "il y a des classes, il y a des prototypes" à la question "quelle est la différence entre les langages classiques et JavaScript?" pas tout à fait correct et indique une mauvaise compréhension de la théorie de la POO et de sa mise en œuvre sur les classes et / ou les prototypes.

Avec l'organisation Prototype, contrairement à la classe statique, nous avons la possibilité d'apporter des modifications au prototype après avoir créé une entité qui hérite des propriétés de ce prototype, et ces modifications affecteront l'entité déjà créée.

 Ben.hobbies = ['chess', 'badminton']; // tomSon   ,             ,      tomSon.sayAboutFathersHobies = function () { const reducer = (accumulator, current) => {`${accumulator} and ${current}`} return `My Father play ${this.hobbies.reduce(reducer)}` } tomSon.sayAboutFathersHobies(); //  'My Father play chess and badminton' 

C'est ce qu'on appelle l'organisation prototype déléguant le modèle ou l' héritage du prototype .

Comment la capacité d'une entité à mettre en œuvre un certain comportement est-elle déterminée?


Dans une organisation de classe statique, cette opération implique de vérifier l'entité pour l'appartenance à une classe particulière qui implémente le comportement requis. Dans l'organisation du prototype, il y a le concept de typage du canard . Dans le cas du typage canard, vérifier l'entité pour la capacité d'implémenter un comportement spécifique signifie tester directement l'entité pour la capacité d'implémenter ce comportement à un moment donné, c'est-à-dire dans différentes parties du programme, le résultat de la vérification peut être diamétralement opposé.

Quels sont les avantages d'une approche prototype?

  • Plus de flexibilité
  • Les entités n'ont pas de propriétés dont elles n'ont pas besoin.

Quels sont les inconvénients?

  • Moins clair
  • Il n'est pas toujours facile de suivre ce qui a servi de point de départ au comportement indésirable de l'entité, c'est-à-dire le prototype est moins prévisible que l'organisation de classe statique
  • La communauté du développement logiciel ne la connaît pas assez bien, malgré la popularité et la prévalence de JavaScript

Conclusion


Sur cela, nous terminerons aujourd'hui. J'espère avoir pu transmettre l'idée que la différence entre les langages classiques et JavaScript n'est pas liée à la présence / absence de classes et à la présence / absence de prototypes, mais plutôt à la nature statique / dynamique de l'organisation.

Bien sûr, beaucoup n'a pas été considéré. Je ne voudrais pas écrire des articles trop longs, donc les caractéristiques du modèle Cascade dans l'organisation du prototype et les outils OOP (Polymorphisme, Encapsulation, Abstraction, etc.) seront discutées dans les articles suivants.

Source: https://habr.com/ru/post/fr468157/


All Articles