Histoire oubliée de la POO

La plupart des paradigmes de programmation que nous utilisons aujourd'hui ont d'abord été étudiés mathématiquement dans les années 1930 en utilisant les idées du calcul lambda et de la machine de Turing, qui sont des variantes du modèle informatique universel (ce sont des systèmes formalisés qui peuvent effectuer des calculs à usage général). La thèse de Church-Turing a montré que le calcul lambda et les machines de Turing sont fonctionnellement équivalents. À savoir, nous parlons du fait que tout ce qui peut être calculé à l'aide d'une machine de Turing peut également être calculé à l'aide du calcul lambda, et vice versa.



Il existe une idée fausse commune selon laquelle les machines de Turing peuvent calculer tout ce qui peut être calculé. Il existe des classes de problèmes (par exemple, le problème de l’arrêt ) qui peuvent être calculés à l’aide des machines de Turing dans certains cas seulement. Lorsque le mot «calculable» est utilisé dans ce texte, il signifie «calculable par une machine de Turing».

Le calcul lambda illustre l'approche consistant à appliquer des fonctions aux calculs de manière descendante. Une machine à bande Turing est une approche impérative (étape par étape) de l'informatique, mise en œuvre de façon ascendante.

Les langages de programmation de bas niveau, tels que le code machine ou l'assembleur, sont apparus dans les années 40 et, à la fin des années 50, les premiers langages de haut niveau populaires sont apparus et ont mis en œuvre des approches fonctionnelles et impératives. Ainsi, les dialectes de la langue Lisp sont encore largement utilisés, parmi lesquels Clojure, Scheme, AutoLisp et ainsi de suite. Dans les années cinquante, des langues comme le FORTRAN et le COBOL sont apparues. Ce sont des exemples de langages de haut niveau impératifs qui sont toujours vivants. Bien qu'il convient de noter que les langues de la famille C, dans la plupart des domaines, ont remplacé COBOL et FORTRAN.

Les racines de la programmation impérative et fonctionnelle se trouvent dans les mathématiques formelles de l'informatique, elles sont apparues avant les ordinateurs numériques. La programmation orientée objet (POO) est venue plus tard; elle trouve son origine dans la révolution structurelle de la programmation qui a eu lieu dans les années 60 et 70 du siècle dernier.

Le premier objet que je connaissais a été utilisé par Ivan Sutherland dans sa fatidique application Sketchpad, créée entre 1961 et 1962, qu'il a décrite dans cet ouvrage en 1963. Les objets étaient des caractères graphiques affichés sur l'écran de l'oscilloscope (c'est peut-être la première fois dans l'histoire de l'utilisation d'un écran d'ordinateur graphique) qui prend en charge l'héritage par le biais de délégués dynamiques, qu'Ivan Sutherland a appelé «maîtres» dans son travail. Tout objet pouvait devenir un objet maître, des instances supplémentaires de l'objet étaient appelées "occurrences". Cela a fait du système Sketchpad le propriétaire du premier des fameux langages de programmation qui implémentait l'héritage des prototypes.

Le premier langage de programmation, communément appelé "orienté objet", était le langage Simula, dont les spécifications ont été développées en 1965. Comme Sketchpad, Silmula prévoyait de travailler avec des objets, mais comprenait également des classes, un héritage basé sur une classe, des sous-classes et des méthodes virtuelles.

Une méthode virtuelle est une méthode définie dans une classe conçue pour être redéfinie par des sous-classes. Les méthodes virtuelles permettent aux programmes d'appeler des méthodes qui n'existent peut-être pas au moment de la compilation du code, en utilisant la répartition dynamique pour déterminer quelle méthode particulière doit être appelée pendant l'exécution du programme. JavaScript possède des types dynamiques et utilise une chaîne de délégation pour déterminer la méthode à invoquer. Par conséquent, ce langage n'a pas besoin d'introduire le concept de méthodes virtuelles aux programmeurs. En d'autres termes, toutes les méthodes de JavaScript utilisent la répartition au moment de l'exécution; par conséquent, les méthodes de JavaScript n'ont pas besoin d'être déclarées «virtuelles» pour prendre en charge cette fonctionnalité.

Opinion du père OOP sur OOP


"J'ai inventé le terme" orienté objet "et je peux dire que je ne voulais pas dire C ++." Alan Kay, Conférence OOPSLA, 1997.

Alan Kay a inventé le terme «programmation orientée objet», se référant au langage de programmation Smalltalk (1972). Ce langage a été développé par Alan Kay, Dan Ingles et d'autres employés du Xerox PARC Research Center dans le cadre du projet Dynabook. Smalltalk était plus orienté objet que Simula. Dans Smalltalk, tout est un objet, y compris les classes, les entiers et les blocs (fermetures). L'implémentation initiale du langage, Smalltalk-72, n'avait pas la capacité de sous-classe. Cette fonctionnalité est apparue dans Smalltalk-76.

Bien que Smalltalk ait pris en charge les classes et, par conséquent, le sous-classement, Smalltalk n'a pas mis ces idées au premier plan. C'était un langage fonctionnel que Lisp a influencé autant que Simula. Selon Alan Kay, traiter les classes comme un mécanisme de réutilisation de code est une erreur. L'industrie de la programmation accorde une grande attention à la création de sous-classes, distrayant des avantages réels de la programmation orientée objet.

JavaScript et Smalltalk ont ​​beaucoup en commun. Je dirais que JavaScript est la vengeance de Smalltalk sur le monde pour avoir mal compris les concepts de la POO. Ces deux langues prennent en charge les fonctionnalités suivantes:

  • Objets
  • Fonctions et fermetures de première classe.
  • Types dynamiques.
  • Liaison tardive (les fonctions et méthodes peuvent être remplacées lors de l'exécution du programme).
  • POO sans système d'héritage basé sur les classes.

«Je regrette d'avoir trouvé le terme« objets »pour ce phénomène il y a longtemps, car son utilisation conduit au fait que beaucoup de gens accordent une importance primordiale à une idée qui n'est pas aussi importante que la principale. L'idée principale est la messagerie. " Alan Kay

Dans une correspondance par courrier électronique de 2003, Alan Kay a précisé ce qu'il avait en tête lorsqu'il a appelé Smalltalk "un langage orienté objet".

«Pour moi, la POO signifie uniquement la messagerie, le stockage local et la protection, l'état de masquage et la liaison très tardive.» Alan Kay

En d'autres termes, conformément aux idées d'Alan Kay, les ingrédients les plus importants de la POO sont les suivants:

  • Messagerie
  • Encapsulation.
  • Liaison dynamique.

Il est important de noter qu'Alan Kay, l'homme qui a inventé le terme «OOP» et l'a apporté aux masses, ne considérait pas l'hérédité et le polymorphisme comme les composants les plus importants de la POO.

L'essence de la POO


La combinaison de la messagerie et de l'encapsulation sert plusieurs objectifs importants:

  • Éviter l'état mutable partagé d'un objet en encapsulant l'état et en isolant d'autres objets des changements locaux de son état. La seule façon d'influencer l'état d'un autre objet est de lui demander (plutôt que de lui donner une commande) de changer en lui envoyant un message. Les changements d'état sont surveillés au niveau cellulaire local, l'état n'est pas mis à la disposition d'autres objets.
  • Séparation des objets les uns des autres. L'expéditeur du message est faiblement couplé au récepteur via l'API de messagerie.
  • Adaptabilité et résistance aux changements pendant l'exécution du programme grâce à une liaison tardive. L'adaptation aux changements pendant l'exécution du programme offre de nombreux avantages importants, qu'Alan Kay considérait très importants pour la POO.

Alan Kay, qui a exprimé ces idées, s'est inspiré de ses connaissances en biologie et de ses connaissances sur ARPANET (il s'agit d'une première version d'Internet). À savoir, nous parlons de cellules biologiques et d'ordinateurs individuels connectés au réseau. Même alors, Alan Kay a imaginé comment les programmes s'exécutent sur d'énormes ordinateurs distribués (Internet), tandis que les ordinateurs individuels agissent comme des cellules biologiques, travaillant indépendamment avec leur propre état isolé et échangeant des données avec d'autres ordinateurs en envoyant des messages.

"J'ai réalisé qu'une métaphore pour une cellule ou un ordinateur aiderait à se débarrasser des données [...]." Alan Kay

Dire «aider à se débarrasser des données», Alan Kay, bien sûr, était conscient des problèmes causés par l'état mutable partagé et de la forte connectivité causée par le partage des données. Aujourd'hui, ces sujets sont largement entendus. Mais à la fin des années 1960, les programmeurs ARPANET n'étaient pas satisfaits de la nécessité de choisir une représentation de modèle de données pour leurs programmes avant de développer des programmes. Les développeurs ont voulu s'éloigner de cette pratique, car, avant de se plonger dans le cadre déterminé par la présentation des données, il est plus difficile de changer quelque chose à l'avenir.

Le problème était que différentes façons de présenter les données nécessitaient, pour y accéder, un code différent et une syntaxe différente dans les langages de programmation utilisés à un moment donné. Le Saint Graal serait ici un moyen universel d'accéder et de gérer les données. Si toutes les données se ressemblaient pour le programme, cela résoudrait de nombreux problèmes des développeurs concernant le développement et la maintenance des programmes.
Alan Kay a tenté de "se débarrasser" de l'idée, selon laquelle les données et les programmes étaient, en un sens, des entités indépendantes. Ils ne sont pas considérés comme tels dans List ou Smalltalk. Il n'y a pas de séparation entre ce qui peut être fait avec des données (avec des valeurs, des variables, des structures de données, etc.) et des constructions logicielles comme des fonctions. Les fonctions sont des «citoyens de première classe» et les programmes peuvent changer pendant leur exécution. En d'autres termes, Smalltalk n'a pas de relation privilégiée spéciale avec les données.

Alan Kay, en outre, considérait les objets comme des structures algébriques, qui donnaient des garanties précises et mathématiquement prouvables de leur comportement.

"Mes connaissances mathématiques m'ont permis de comprendre que chaque objet peut avoir plusieurs modèles algébriques associés, qu'il peut y avoir des groupes entiers de modèles similaires et qu'ils peuvent être très, très utiles." Alan Kay

Il a été prouvé qu'il en était ainsi, et cela a constitué la base pour des objets, tels que les promesses et les lentilles, de plus, la théorie des catégories a été influencée par les deux.
La nature algébrique de la façon dont Alan Kay a vu les objets permettrait aux objets de fournir une vérification formelle, un comportement déterministe et d'améliorer la testabilité, car les modèles algébriques sont, par essence, des opérations qui obéissent à plusieurs règles sous la forme d'équations.

Dans le jargon des programmeurs, les «modèles algébriques» sont des abstractions créées à partir de fonctions (opérations) qui sont accompagnées de certaines règles, imposées par des tests unitaires que ces fonctions doivent réussir (axiomes, équations).

Ces idées sont oubliées depuis des décennies dans la plupart des langages orientés objet de la famille C, notamment C ++, Java, C #, etc. Mais ces idées commencent la recherche du voyage de retour, dans les versions récentes des langages orientés objet les plus utilisés.

A cette occasion, quelqu'un peut dire que le monde de la programmation redécouvre les avantages de la programmation fonctionnelle et fournit des arguments rationnels dans le contexte des langages orientés objet.

Comme JavaScript et Smalltalk plus tôt, la plupart des langages orientés objet modernes deviennent de plus en plus "multi-paradigmes". Il n'y a aucune raison de choisir entre la programmation fonctionnelle et la POO. Lorsque nous examinons l'essence historique de chacune de ces approches, elles semblent non seulement compatibles, mais aussi complémentaires.

D'après les pensées d'Alan Kay, quel est le plus important au sein de l'OLP?

  • Encapsulation.
  • Messagerie
  • Liaison dynamique (capacité des programmes à se développer et à s'adapter aux changements pendant leur exécution).

Qu'est-ce qui est négligeable dans la POO?

  • Cours.
  • Héritage basé sur les classes.
  • Relation particulière aux objets, fonctions ou données.
  • new mot-clé.
  • Polymorphisme.
  • Typage statique.
  • Attitude envers les classes en tant que «types».

Si vous connaissez Java ou C #, vous pourriez penser que le typage statique ou le polymorphisme sont les ingrédients les plus importants de la POO, mais Alan Kay préfère traiter les modèles de comportement universels sous forme algébrique. Voici un exemple écrit en Haskell:

 fmap :: (a -> b) -> fa -> fb 

Il s'agit de la signature du foncteur de map universel, qui fonctionne avec les types non définis a et b , en appliquant la fonction de a à b dans le contexte du foncteur a afin de créer le foncteur b . «Functor» est un mot du jargon mathématique dont la signification est réduite à «support de l'opération d'affichage». Si vous connaissez la méthode [].map() en JavaScript, vous savez déjà ce que cela signifie.

Voici quelques exemples JavaScript:

 // isEven = Number => Boolean const isEven = n => n % 2 === 0; const nums = [1, 2, 3, 4, 5, 6]; //  map   `a => b`    `a` ( `this`) //     `b` //    `a`   `Number`,   `b`  `Boolean` const results = nums.map(isEven); console.log(results); // [false, true, false, true, false, true] 

La méthode .map() est universelle, dans le sens où a et b peuvent être de tout type, et cette méthode résout sans problème une situation similaire, car les tableaux sont des structures de données qui mettent en œuvre les lois algébriques des foncteurs. Les types de .map() n'ont pas d'importance, car cette méthode n'essaie pas de travailler directement avec les valeurs correspondantes. Au lieu de cela, il utilise une fonction qui attend et renvoie des valeurs des types correspondants qui sont correctes du point de vue de l'application.

 // matches = a => Boolean //  `a`    ,   const matches = control => input => input === control; const strings = ['foo', 'bar', 'baz']; const results = strings.map(matches('bar')); console.log(results); // [false, true, false] 

La relation des types universels peut être difficile à exprimer correctement et complètement dans des langages comme TypeScript, mais elle est très simple à faire dans le système de type Hindley-Milner utilisé dans Haskell, qui prend en charge les types supérieurs (types de type).

La plupart des systèmes de types imposent des restrictions trop fortes pour permettre la libre expression d'idées dynamiques et fonctionnelles, telles que la composition des fonctions, la libre composition des objets, l'expansion des objets pendant l'exécution du programme, l'utilisation de combinateurs, de lentilles, etc. En d'autres termes? les types statiques rendent souvent difficile l'écriture de logiciels à l'aide de méthodes de construction.

Si votre système de types a trop de restrictions (comme dans TypeScript ou Java), pour atteindre les mêmes objectifs, vous devez écrire du code plus complexe que lorsque vous utilisez des langages avec une approche plus libre de la frappe. Cela ne signifie pas que l'utilisation de types statiques est une idée malheureuse, ou que toutes les implémentations de types statiques ont les mêmes limitations. Par exemple, j'ai rencontré beaucoup moins de problèmes avec le système de type Haskell.

Si vous êtes un fan des types statiques et que vous n'êtes pas contre les restrictions, je vous souhaite sept pieds sous la quille. Mais si vous trouvez que certaines des idées exprimées ici sont difficiles à mettre en œuvre en raison du fait qu'il n'est pas facile de taper des fonctions obtenues en composant d'autres fonctions et des structures algébriques composites, blâmez le système de types et non l'idée. Les conducteurs aiment les commodités que les VUS à châssis leur offrent, mais personne ne se plaint de ne pas voler. Pour voler, vous avez besoin d'un véhicule qui a plus de degrés de liberté.

Si les restrictions rendent votre code plus simple, tant mieux! Mais si les contraintes vous obligent à écrire du code plus complexe, alors peut-être que quelque chose ne va pas avec ces contraintes.

Qu'est-ce qu'un «objet»?


Le mot «objet», au fil du temps, a acquis de nombreuses connotations secondaires de sens. Ce que nous appelons des «objets» en JavaScript sont simplement des types de données composites, sans aucune allusion à la programmation basée sur les classes, ni aux idées de passage de messages d'Alan Kay.

En JavaScript, ces objets peuvent prendre en charge, et prennent souvent en charge, l'encapsulation, le passage de messages, la séparation des comportements par le biais de méthodes, voire le polymorphisme à l'aide de sous-classes (bien qu'en utilisant une chaîne de délégation plutôt qu'une répartition basée sur le type).

Alan Kay voulait se débarrasser de la différence entre le programme et ses données. JavaScript, dans une certaine mesure, atteint cet objectif en plaçant les méthodes d'objet au même endroit que les propriétés qui stockent les données. Par exemple, n'importe quelle propriété peut être affectée à n'importe quelle fonction. Vous pouvez construire le comportement d'un objet de manière dynamique et modifier le contenu sémantique de l'objet pendant l'exécution du programme.

Un objet est juste une structure de données composite, et il n'a besoin de rien de spécial pour être considéré comme un objet. Cependant, la programmation à l'aide d'objets ne conduit pas au fait qu'un tel code se révèle «orienté objet», tout comme l'utilisation de fonctions ne rend pas le code «fonctionnel».

La POO n'est plus une vraie POO


Étant donné que le concept d '«objet» dans les langages de programmation modernes signifie beaucoup moins que ce qu'Alan Kay voulait dire, j'utilise le mot «composant» au lieu du mot «objet» pour décrire les règles de ce POO. De nombreux objets sont directement détenus et contrôlés par du code JavaScript tiers, mais les composants doivent encapsuler leur propre état et le contrôler.

Voici ce qu'est la vraie POO:

  • Programmation à l'aide de composants (Alan Kay les appelle des «objets»).
  • L'état du composant doit être encapsulé.
  • Pour la communication entre les entités, la messagerie est utilisée.
  • Les composants peuvent être ajoutés, modifiés et remplacés lors de l'exécution.

La plupart des comportements d'objet peuvent être définis de manière universelle à l'aide de structures de données algébriques. Il n'y a pas besoin d'héritage. Les composants peuvent réutiliser les comportements des fonctions publiques et importer des modules, sans avoir à rendre leurs données publiques.

La manipulation d'objets en JavaScript ou l'utilisation de l'héritage basé sur une classe ne signifie pas que quelqu'un est impliqué dans la programmation POO. Mais l'utilisation de composants de cette manière - signifie. Mais il est très difficile de se débarrasser des idées établies sur les termes, alors peut-être devrions-nous laisser le terme «POO» et appeler ce que les «composants» ci-dessus sont utilisés comme «programmation orientée message (MOP)»? Nous utiliserons le terme «MOP» ci-dessous pour parler de programmation orientée message.

Par hasard, le mot anglais «mop» est traduit par «mop» et, comme vous le savez, ils sont utilisés pour rétablir l'ordre.

À quoi ressemble une bonne MOP?


La plupart des programmes modernes ont une interface utilisateur (User Interface, UI) qui est chargée d'interagir avec l'utilisateur, un code qui gère l'état de l'application (données utilisateur) et un code qui fonctionne avec le système ou qui est responsable de l'échange de données avec le réseau.

Pour prendre en charge le fonctionnement de chacun de ces systèmes, des processus à longue durée de vie, tels que des écouteurs d'événements, peuvent être nécessaires. Ici, vous aurez besoin de l'état de l'application - pour stocker quelque chose comme des informations sur les connexions réseau, sur l'état des choses avec les contrôles d'interface et sur l'application elle-même.

Un bon MOP signifie qu'au lieu que tous ces systèmes aient accès à l'état de l'autre et puissent les contrôler directement, ils interagissent les uns avec les autres par le biais de messages. Lorsque l'utilisateur clique sur le bouton "Enregistrer", le message "SAVE" peut être envoyé. Le composant d'application de gestion d'état peut interpréter ce message et le rediriger vers le gestionnaire responsable de la mise à jour à partir de l'état (tel qu'une fonction de réducteur pur). Peut-être qu'après la mise à jour de l'état, le composant responsable de la gestion de l'état envoie le message "STATE_UPDATED" composant d'interface utilisateur qui, à son tour, interprète l'état, décide quelles parties de l'interface doivent être mises à jour et transfère l'état mis à jour aux sous-composants chargés de travailler avec éléments d'interface spécifiques.

Pendant ce temps, le composant responsable des connexions réseau peut surveiller la connexion de l'utilisateur à un autre ordinateur du réseau, écouter les messages et envoyer une vue mise à jour de l'état pour l'enregistrer sur la machine distante. Un tel composant est chargé de travailler avec des mécanismes de réseau, sait si la connexion fonctionne ou non, etc.

Les systèmes d'application similaires ne devraient pas connaître les détails de ses autres parties. Ils ne devraient se soucier que de résoudre leurs propres problèmes. Les composants du système peuvent être démontés et assemblés en tant que constructeur. Ils implémentent des interfaces standardisées, ce qui signifie qu'ils peuvent interagir les uns avec les autres. Tant que les exigences bien connues pour l'interface des composants sont remplies, ces composants peuvent être remplacés par d'autres, avec les mêmes interfaces, mais faisant la même chose différemment, ou effectuant, recevant les mêmes messages, quelque chose de complètement différent. Vous pouvez changer un composant en un autre même pendant l'exécution du programme - cela ne cassera pas son travail.

Les composants d'un système logiciel n'ont même pas besoin d'être sur le même ordinateur. Le système peut être décentralisé. Le stockage en réseau peut placer des données dans un système de stockage décentralisé comme IPFS , par conséquent, l'utilisateur est indépendant de la santé d'une machine particulière, ce qui garantit la sécurité de ses données. Avec cette approche, les données sont stockées de manière fiable et protégées contre les intrus.

L'OLP, en partie, est tombée sous l'influence des idées d'ARPANET, et l'un des objectifs de ce projet était de créer un réseau décentralisé qui serait résistant aux attaques comme une frappe nucléaire.

Un bon système MOP peut être caractérisé par un niveau de stabilité similaire en utilisant des composants qui prennent en charge l'échange à chaud pendant que l'application est en cours d'exécution. Il pourra continuer à fonctionner si l'utilisateur travaille avec lui à partir d'un téléphone portable et est hors de la couverture réseau du fait qu'il est entré dans le tunnel. Si un ouragan interrompait l'alimentation électrique de l'un des centres de données dans lesquels ses serveurs sont situés, il continuera également de fonctionner.

Il est temps que le monde du logiciel se libère d'une expérience d'héritage basée sur une classe infructueuse et adopte les principes mathématiques et scientifiques qui étaient à la pointe de la POO.

Il est temps pour nous, développeurs, de créer des programmes plus flexibles, stables et beaux en utilisant une combinaison harmonieuse de MOP et de programmation fonctionnelle.
Soit dit en passant, l'acronyme "MOP" est déjà utilisé, décrivant "Monitoring Oriented Programming", mais ce concept, contrairement à la POO, disparaîtra tout simplement.

Par conséquent, ne vous découragez pas si le terme «MOP» ne ressemble pas à un mot du jargon des programmeurs. Ranger juste votre POO avec les principes MOP ci-dessus.

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


All Articles