L’histoire oubliée de la POO
Note : Ceci fait partie de la série « Composing Software » (maintenant un livre !) sur l’apprentissage de la programmation fonctionnelle et des techniques de composition logicielle en JavaScript ES6+ depuis la base. Restez à l’écoute. Il y a beaucoup plus de ceci à venir !
Acheter le livre | Index | < Précédent | Suivant >
Les paradigmes de programmation fonctionnelle et impérative que nous utilisons aujourd’hui ont été explorés mathématiquement pour la première fois dans les années 1930 avec le lambda calculus et la machine de Turing, qui sont des formulations alternatives du calcul universel (systèmes formalisés pouvant effectuer des calculs généraux). La thèse de Church Turing a montré que le lambda-calcul et les machines de Turing sont fonctionnellement équivalents – que tout ce qui peut être calculé à l’aide d’une machine de Turing peut être calculé à l’aide du lambda-calcul, et vice versa.
Note : Il existe une idée fausse courante selon laquelle les machines de Turing peuvent calculer tout ce qui est calculable. Il existe des classes de problèmes (par exemple, le problème des haltes) qui peuvent être calculables dans certains cas, mais qui ne sont généralement pas calculables dans tous les cas à l’aide de machines de Turing. Lorsque j’utilise le mot « calculable » dans ce texte, je veux dire « calculable par une machine de Turing ».
Leambda calculus représente une approche descendante, par application de fonctions, du calcul, tandis que la formulation ticker tape/register machine de la machine de Turing représente une approche ascendante, impérative (pas à pas) du calcul.
Les langages de bas niveau comme le code machine et l’assemblage sont apparus dans les années 1940, et à la fin des années 1950, les premiers langages de haut niveau populaires sont apparus. Des dialectes de Lisp sont encore couramment utilisés aujourd’hui, notamment Clojure, Scheme, AutoLISP, etc. FORTRAN et COBOL sont tous deux apparus dans les années 1950 et sont des exemples de langages de haut niveau impératifs encore utilisés aujourd’hui, bien que les langages de la famille C aient remplacé à la fois COBOL et FORTRAN pour la plupart des applications.
La programmation impérative et la programmation fonctionnelle trouvent toutes deux leurs racines dans les mathématiques de la théorie du calcul, avant les ordinateurs numériques. « La programmation orientée objet » (POO) a été inventée par Alan Kay vers 1966 ou 1967 alors qu’il était à l’école doctorale.
L’application séminale Sketchpad d’Ivan Sutherland a été une source d’inspiration précoce pour la POO. Elle a été créée entre 1961 et 1962 et publiée dans sa thèse sur le Sketchpad en 1963. Les objets étaient des structures de données représentant des images graphiques affichées sur un écran d’oscilloscope, et comportaient un héritage via des délégués dynamiques, qu’Ivan Sutherland appelait « maîtres » dans sa thèse. Tout objet pouvait devenir un « maître », et les instances supplémentaires des objets étaient appelées « occurrences ». Les maîtres de Sketchpad partagent beaucoup de points communs avec l’héritage prototypique de JavaScript.
Note : le TX-2 du laboratoire Lincoln du MIT a été l’une des premières utilisations d’un écran d’ordinateur graphique employant une interaction directe avec l’écran à l’aide d’un stylo léger. L’EDSAC, qui était opérationnel entre 1948-1958 pouvait afficher des graphiques sur un écran. Le Whirlwind du MIT disposait d’un écran d’oscilloscope fonctionnel en 1949. La motivation du projet était de créer un simulateur de vol général capable de simuler le retour des instruments pour plusieurs avions. Cela a conduit au développement du système informatique SAGE. Le TX-2 était un ordinateur de test pour SAGE.
Le premier langage de programmation largement reconnu comme « orienté objet » était Simula, spécifié en 1965. Comme Sketchpad, Simula présentait des objets, et a finalement introduit les classes, l’héritage de classe, les sous-classes et les méthodes virtuelles.
Note : Une méthode virtuelle est une méthode définie sur une classe qui est conçue pour être surchargée par les sous-classes. Les méthodes virtuelles permettent à un programme d’appeler des méthodes qui peuvent ne pas exister au moment où le code est compilé en employant la répartition dynamique pour déterminer quelle méthode concrète invoquer au moment de l’exécution. JavaScript propose des types dynamiques et utilise la chaîne de délégation pour déterminer les méthodes à invoquer. Il n’est donc pas nécessaire d’exposer le concept de méthodes virtuelles aux programmeurs. Dit autrement, toutes les méthodes en JavaScript utilisent la répartition des méthodes à l’exécution, donc les méthodes en JavaScript n’ont pas besoin d’être déclarées « virtuelles » pour supporter la fonctionnalité.
« J’ai inventé le terme « orienté objet », et je peux vous dire que je n’avais pas C++ en tête. » ~ Alan Kay, OOPSLA ’97
Alan Kay a inventé le terme « programmation orientée objet » à l’école supérieure en 1966 ou 1967. La grande idée était d’utiliser des mini-ordinateurs encapsulés dans des logiciels qui communiquaient par passage de messages plutôt que par partage direct de données – pour cesser de décomposer les programmes en « structures de données » et « procédures » distinctes.
« Le principe de base de la conception récursive est de faire en sorte que les parties aient la même puissance que le tout. » ~ Bob Barton, le principal concepteur du B5000, un mainframe optimisé pour exécuter Algol-60.
Smalltalk a été développé par Alan Kay, Dan Ingalls, Adele Goldberg et d’autres au Xerox PARC. Smalltalk était plus orienté objet que Simula – tout dans Smalltalk est un objet, y compris les classes, les entiers et les blocs (closures). La version originale de Smalltalk-72 ne comportait pas de sous-classements. Cela a été introduit dans Smalltalk-76 par Dan Ingalls.
Bien que Smalltalk ait supporté les classes et éventuellement le sous-classement, Smalltalk ne concernait pas les classes ou le sous-classement des choses. C’était un langage fonctionnel inspiré de Lisp ainsi que de Simula. Alan Kay considère que l’accent mis par l’industrie sur la sous-classification est une distraction des véritables avantages de la programmation orientée objet.
« Je suis désolé d’avoir inventé il y a longtemps le terme « objets » pour ce sujet car il amène beaucoup de gens à se concentrer sur la moindre idée. La grande idée, c’est la messagerie. »
~ Alan Kay
Dans un échange de courriels de 2003, Alan Kay a précisé ce qu’il entendait lorsqu’il qualifiait Smalltalk d' »orienté objet » :
« La POO pour moi ne signifie que la messagerie, la rétention locale et la protection et la dissimulation des processus d’état, et la liaison tardive extrême de toutes les choses. »
~ Alan Kay
En d’autres termes, selon Alan Kay, les ingrédients essentiels de la POO sont :
- Le passage de messages
- L’encapsulation
- La liaison dynamique
Notamment, l’héritage et le polymorphisme de sous-classe n’étaient PAS considérés comme des ingrédients essentiels de la POO par Alan Kay, l’homme qui a inventé le terme et fait connaître la POO aux masses.
L’essence de la POO
La combinaison du passage de messages et de l’encapsulation sert certains objectifs importants :
- Éviter l’état mutable partagé en encapsulant l’état et en isolant les autres objets des changements d’état locaux. La seule façon d’affecter l’état d’un autre objet est de demander (et non de commander) à cet objet de le modifier en envoyant un message. Les changements d’état sont contrôlés à un niveau local et cellulaire plutôt qu’exposés à un accès partagé.
- Découplage des objets les uns des autres – l’émetteur du message n’est que faiblement couplé au récepteur du message, par le biais de l’API de messagerie.
- Adaptabilité et résilience aux changements au moment de l’exécution via la liaison tardive. L’adaptabilité au moment de l’exécution fournit de nombreux grands avantages qu’Alan Kay considérait comme essentiels à la POO.
Ces idées ont été inspirées par les cellules biologiques et/ou les ordinateurs individuels sur un réseau via les antécédents d’Alan Kay en biologie et l’influence de la conception d’Arpanet (une première version d’Internet). Même aussi tôt, Alan Kay a imaginé des logiciels fonctionnant sur un ordinateur géant et distribué (l’internet), où les ordinateurs individuels agissaient comme des cellules biologiques, fonctionnant indépendamment sur leur propre état isolé, et communiquant via le passage de messages.
« J’ai réalisé que la métaphore de la cellule/de l’ordinateur complet permettrait de se débarrasser des données »
~ Alan Kay
Par « se débarrasser des données », Alan Kay était sûrement conscient des problèmes d’état mutable partagé et du couplage étroit causé par les données partagées – des thèmes courants aujourd’hui.
Mais à la fin des années 1960, les programmeurs de l’ARPA étaient frustrés par la nécessité de choisir une représentation du modèle de données pour leurs programmes avant de construire le logiciel. Les procédures trop étroitement couplées à des structures de données particulières n’étaient pas résilientes au changement. Ils voulaient un traitement plus homogène des données.
» tout l’intérêt de la POO est de ne pas avoir à se soucier de ce qui se trouve à l’intérieur d’un objet. Les objets fabriqués sur différentes machines et avec différents langages devraient pouvoir se parler » ~ Alan Kay
Les objets peuvent faire abstraction et cacher les implémentations des structures de données. L’implémentation interne d’un objet pourrait changer sans casser d’autres parties du système logiciel. En fait, avec une liaison tardive extrême, un système informatique entièrement différent pourrait prendre en charge les responsabilités d’un objet, et le logiciel pourrait continuer à fonctionner. Les objets, quant à eux, peuvent exposer une interface standard qui fonctionne avec n’importe quelle structure de données que l’objet utilise en interne. La même interface pouvait fonctionner avec une liste chaînée, un arbre, un flux, et ainsi de suite.
Alan Kay voyait également les objets comme des structures algébriques, qui apportent certaines garanties mathématiquement prouvables sur leurs comportements :
« Ma formation en mathématiques m’a fait réaliser que chaque objet pouvait avoir plusieurs algèbres associées, et qu’il pouvait y avoir des familles de celles-ci, et que celles-ci seraient très très utiles. »
~ Alan Kay
Cela s’est avéré vrai, et constitue la base d’objets tels que les promesses et les lentilles, tous deux inspirés de la théorie des catégories.
La nature algébrique de la vision d’Alan Kay pour les objets permettrait à ces derniers de se permettre des vérifications formelles, un comportement déterministe et une meilleure testabilité, car les algèbres sont essentiellement des opérations qui obéissent à quelques règles sous forme d’équations.
Dans le jargon des programmeurs, les algèbres sont comme des abstractions composées de fonctions (opérations) accompagnées de lois spécifiques appliquées par des tests unitaires que ces fonctions doivent passer (axiomes/équations).
Ces idées ont été oubliées pendant des décennies dans la plupart des langages OO de la famille C, notamment C++, Java, C#, etc, mais elles commencent à retrouver leur place dans les versions récentes des langages OO les plus utilisés.
On pourrait dire que le monde de la programmation redécouvre les avantages de la programmation fonctionnelle et de la pensée raisonnée dans le contexte des langages OO.
Comme JavaScript et Smalltalk avant lui, la plupart des langages OO modernes deviennent de plus en plus des » langages multi-paradigmes « . Il n’y a aucune raison de choisir entre la programmation fonctionnelle et la POO. Quand on regarde l’essence historique de chacun, ce ne sont pas seulement des idées compatibles, mais complémentaires.
Parce qu’ils ont tant de caractéristiques en commun, j’aime dire que JavaScript est la revanche de Smalltalk sur la mauvaise compréhension de la POO par le monde. Both Smalltalk and JavaScript support:
- Objects
- First-class functions and closures
- Dynamic types
- Late binding (functions/methods changeable at runtime)
- OOP without class inheritance
What is essential to OOP (according to Alan Kay)?
- Encapsulation
- Message passing
- Dynamic binding (the ability for the program to evolve/adapt at runtime)
What is non-essential?
- Classes
- Class inheritance
- Special treatment for objects/functions/data
- The
new
keyword - Polymorphism
- Static types
- Recognizing a class as a « type »
If your background is Java or C#, you may be thinking static types and Polymorphism are essential ingredients, but Alan Kay preferred dealing with generic behaviors in algebraic form. For example, from Haskell:
fmap :: (a -> b) -> f a -> f b
This is the functor map signature, which acts generically over unspecified types a
and b
, applying a function from a
to b
in the context of a functor of a
to produce a functor of b
. Functor is math jargon that essentially means « supporting the map operation ». If you’re familiar with .map()
in JavaScript, you already know what that means.
Here are two examples in JavaScript:
// isEven = Number => Boolean
const isEven = n => n % 2 === 0;const nums = ;// map takes a function `a => b` and an array of `a`s (via `this`)
// and returns an array of `b`s.
// in this case, `a` is `Number` and `b` is `Boolean`
const results = nums.map(isEven);console.log(results);
//
The .map()
method is generic in the sense that a
and b
can be any type, and .map()
handles it just fine because arrays are data structures that implement the algebraic functor
laws. Les types n’ont pas d’importance pour .map()
parce qu’il n’essaie pas de les manipuler directement, appliquant au lieu de cela une fonction qui attend et renvoie les types corrects pour l’application.
// matches = a => Boolean
// here, `a` can be any comparable type
const matches = control => input => input === control;const strings = ;const results = strings.map(matches('bar'));console.log(results);
//
Cette relation de type générique est difficile à exprimer correctement et de manière approfondie dans un langage comme TypeScript, mais était assez facile à exprimer dans les types Hindley Milner de Haskell avec un support pour les types de type supérieur (types de types).
La plupart des systèmes de types ont été trop restrictifs pour permettre la libre expression des idées dynamiques et fonctionnelles, telles que la composition de fonctions, la composition d’objets libres, l’extension d’objets d’exécution, les combinateurs, les objectifs, etc. En d’autres termes, les types statiques rendent fréquemment plus difficile l’écriture de logiciels composables.
Si votre système de types est trop restrictif (par exemple, TypeScript, Java), vous êtes obligé d’écrire un code plus alambiqué pour atteindre les mêmes objectifs. Cela ne signifie pas que les types statiques sont une mauvaise idée, ou que toutes les implémentations de types statiques sont également restrictives. J’ai rencontré beaucoup moins de problèmes avec le système de types de Haskell.
Si vous êtes un fan des types statiques et que les restrictions ne vous dérangent pas, plus de pouvoir pour vous, mais si vous trouvez certains des conseils de ce texte difficiles parce qu’il est difficile de typer les fonctions composées et les structures algébriques composites, blâmez le système de types, pas les idées. Les gens aiment le confort de leurs SUV, mais personne ne se plaint qu’ils ne vous permettent pas de voler. Pour cela, vous avez besoin d’un véhicule avec plus de degrés de liberté.
Si les restrictions rendent votre code plus simple, tant mieux ! Mais si les restrictions vous obligent à écrire un code plus compliqué, peut-être que les restrictions sont mauvaises.
Qu’est-ce qu’un objet ?
Les objets ont clairement pris beaucoup de connotations au fil des ans. Ce que nous appelons » objets » en JavaScript sont simplement des types de données composites, sans aucune des implications de la programmation basée sur les classes ou du passage de messages d’Alan Kay.
En JavaScript, ces objets peuvent supporter et supportent fréquemment l’encapsulation, le passage de messages, le partage de comportement via des méthodes, et même le polymorphisme de sous-classe (bien qu’utilisant une chaîne de délégation plutôt qu’une répartition basée sur le type). Vous pouvez affecter n’importe quelle fonction à n’importe quelle propriété. Vous pouvez construire des comportements d’objets de manière dynamique et modifier la signification d’un objet au moment de l’exécution. JavaScript prend également en charge l’encapsulation en utilisant des fermetures pour la confidentialité de la mise en œuvre. Mais tout cela est un comportement opt-in.
Notre idée actuelle d’un objet est simplement une structure de données composite, et ne nécessite rien de plus pour être considéré comme un objet. Mais programmer en utilisant ces types d’objets ne rend pas votre code « orienté objet », pas plus que programmer avec des fonctions ne rend votre code « fonctionnel ».
La POO n’est plus la vraie POO
Parce que « objet » dans les langages de programmation modernes signifie beaucoup moins que pour Alan Kay, j’utilise « composant » au lieu d' »objet » pour décrire les règles de la vraie POO. De nombreux objets sont possédés et manipulés directement par d’autres codes en JavaScript, mais les composants doivent encapsuler et contrôler leur propre état.
La vraie POO signifie :
- Programmer avec des composants (l' »objet » d’Alan Kay)
- L’état des composants doit être encapsulé
- Utiliser le passage de messages pour la communication inter-objets
- Les composants peuvent être ajoutés/changés/remplacés au moment de l’exécution
La plupart des comportements des composants peuvent être spécifiés de manière générique en utilisant des structures de données algébriques. L’héritage n’est pas nécessaire ici. Les composants peuvent réutiliser les comportements des fonctions partagées et des importations modulaires sans partager leurs données.
Manipuler des objets ou utiliser l’héritage de classe en JavaScript ne signifie pas que vous « faites de la POO ». Utiliser des composants de cette manière le fait. Mais l’usage populaire est la façon dont les mots se définissent, alors peut-être devrions-nous abandonner la POO et appeler cela » programmation orientée message (MOP) » au lieu de » programmation orientée objet (POO) » ?
Est-ce une coïncidence que les serpillières soient utilisées pour nettoyer les dégâts ?
What Good MOP Looks Like
Dans la plupart des logiciels modernes, il y a une certaine interface utilisateur responsable de la gestion des interactions avec l’utilisateur, un certain code gérant l’état de l’application (données de l’utilisateur), et un code gérant le système ou les E/S du réseau.
Chacun de ces systèmes peut nécessiter des processus à longue durée de vie, tels que les écouteurs d’événements, l’état pour garder la trace de choses comme la connexion réseau, l’état des éléments de l’ui et l’état de l’application elle-même.
Une bonne MOP signifie qu’au lieu que tous ces systèmes atteignent et manipulent directement l’état des autres, le système communique avec les autres composants via la répartition des messages. Lorsque l’utilisateur clique sur un bouton d’enregistrement, un message "SAVE"
pourrait être distribué, qu’un composant d’état de l’application pourrait interpréter et relayer à un gestionnaire de mise à jour de l’état (comme une fonction de réducteur pur). Peut-être qu’après la mise à jour de l’état, le composant d’état pourrait envoyer un message "STATE_UPDATED"
à un composant d’interface utilisateur, qui à son tour interprétera l’état, rapprochera les parties de l’interface utilisateur qui doivent être mises à jour et relaiera l’état mis à jour aux sous-composants qui gèrent ces parties de l’interface utilisateur.
Pendant ce temps, le composant de connexion réseau pourrait surveiller la connexion de l’utilisateur à une autre machine sur le réseau, écouter les messages et envoyer des représentations d’état mises à jour pour sauvegarder les données sur une machine distante. Il garde en interne la trace d’un timer de battement de cœur du réseau, si la connexion est actuellement en ligne ou hors ligne, et ainsi de suite.
Ces systèmes n’ont pas besoin de connaître les détails des autres parties du système. Seulement sur leurs préoccupations individuelles et modulaires. Les composants du système sont décomposables et recomposables. Ils mettent en œuvre des interfaces standardisées afin de pouvoir interagir. Tant que l’interface est satisfaite, vous pouvez substituer des remplacements qui peuvent faire la même chose de différentes manières, ou des choses complètement différentes avec les mêmes messages. Vous pourriez même le faire au moment de l’exécution, et tout continuerait à fonctionner correctement.
Les composants d’un même système logiciel n’auraient peut-être même pas besoin d’être situés sur la même machine. Le système pourrait être décentralisé. Le stockage réseau pourrait partager les données sur un système de stockage décentralisé comme IPFS, de sorte que l’utilisateur ne dépende pas de la santé d’une machine particulière pour s’assurer que ses données sont sauvegardées en toute sécurité, et à l’abri des pirates qui voudraient les voler.
OOP a été partiellement inspiré par Arpanet, et l’un des objectifs d’Arpanet était de construire un réseau décentralisé qui pourrait être résilient à des attaques comme les bombes atomiques. Selon le directeur de la DARPA pendant le développement d’Arpanet, Stephen J. Lukasik (« Why the Arpanet Was Built »):
« L’objectif était d’exploiter les nouvelles technologies informatiques pour répondre aux besoins du commandement et du contrôle militaires contre les menaces nucléaires, d’obtenir un contrôle survivable des forces nucléaires américaines et d’améliorer la prise de décision tactique et de gestion militaire. »
Note : L’impulsion première d’Arpanet était la commodité plutôt que la menace nucléaire, et ses avantages évidents pour la défense sont apparus plus tard. L’ARPA utilisait trois terminaux informatiques distincts pour communiquer avec trois projets de recherche informatique distincts. Bob Taylor voulait un seul réseau informatique pour connecter chaque projet avec les autres.
Un bon système MOP pourrait partager la robustesse d’Internet en utilisant des composants remplaçables à chaud pendant que l’application fonctionne. Il pourrait continuer à fonctionner si l’utilisateur est sur un téléphone portable et qu’il se déconnecte parce qu’il est entré dans un tunnel. Elle pourrait continuer à fonctionner si un ouragan met hors tension l’un des centres de données où se trouvent les serveurs.
Il est temps pour le monde du logiciel de laisser tomber l’expérience ratée de l’héritage de classe, et d’embrasser les principes mathématiques et scientifiques qui ont défini à l’origine l’esprit de la POO.
Il est temps pour nous de commencer à construire des logiciels plus flexibles, plus résilients, mieux composés, avec la POO et la programmation fonctionnelle travaillant en harmonie.
Note : L’acronyme MOP est déjà utilisé pour décrire la » programmation orientée surveillance » et il est peu probable que la POO disparaisse tranquillement.
Ne vous énervez pas si MOP ne s’impose pas comme jargon de programmation.
Faites de la MOP vos OOP.
En savoir plus sur EricElliottJS.com
Les leçons vidéo sur la programmation fonctionnelle sont disponibles pour les membres d’EricElliottJS.com. Si vous n’êtes pas un membre, inscrivez-vous dès aujourd’hui.