La programmation procédurale / fonctionnelle n'est en aucun cas plus faible que la POO , même sans entrer dans les arguments de Turing (mon langage a le pouvoir de Turing et peut faire n'importe quoi d'autre), ce qui ne veut pas dire grand-chose. En fait, les techniques orientées objet ont d'abord été expérimentées dans des langages qui ne les intégraient pas. En ce sens, la programmation OO n'est qu'un style spécifique de programmation procédurale . Mais il aide à faire respecter des disciplines spécifiques, telles que la modularité, l'abstraction et la dissimulation d'informations qui sont essentielles pour la compréhension et la maintenance du programme.
Certains paradigmes de programmation découlent d'une vision théorique du calcul. Un langage comme Lisp a évolué à partir du lambda-calcul et de l'idée de méta-circularité des langages (similaire à la réflexivité dans le langage naturel). Les clauses Horn ont engendré Prolog et la programmation par contraintes. La famille Algol doit également au lambda-calcul, mais sans réflexivité intégrée.
Lisp est un exemple intéressant, car il a été le banc d'essai de nombreuses innovations de langage de programmation, qui sont traçables à son double héritage génétique.
Mais les langues évoluent alors, souvent sous de nouveaux noms. Un facteur d'évolution majeur est la pratique de la programmation. Les utilisateurs identifient les pratiques de programmation qui améliorent les propriétés des programmes telles que la lisibilité, la maintenabilité, la prouvabilité de l'exactitude. Ensuite, ils essaient d'ajouter aux langages des fonctionnalités ou des contraintes qui prendront en charge et parfois appliqueront ces pratiques afin d'améliorer la qualité des programmes.
Cela signifie que ces pratiques sont déjà possibles dans les anciens langages de programmation, mais il faut de la compréhension et de la discipline pour les utiliser. Leur intégration dans de nouveaux langages en tant que concepts principaux avec une syntaxe spécifique rend ces pratiques plus faciles à utiliser et à comprendre facilement, en particulier pour les utilisateurs les moins sophistiqués (c'est-à-dire la grande majorité). Cela facilite également la vie des utilisateurs avertis.
D'une certaine manière, c'est pour concevoir un langage ce qu'est un sous-programme / fonction / procédure pour un programme. Une fois le concept utile identifié, il reçoit un nom (éventuellement) et une syntaxe, de sorte qu'il peut être facilement utilisé dans tous les programmes développés avec ce langage. Et en cas de succès, il sera également intégré dans les futures langues.
Exemple: recréer l'orientation d'un objet
J'essaie maintenant d'illustrer cela sur un exemple (qui pourrait certainement être peaufiné davantage, avec le temps). Le but de l'exemple n'est pas de montrer qu'un programme orienté objet peut être écrit dans un style de programmation procédurale, éventuellement au détriment de la lisibilité et de la maintenabilité. Je vais plutôt essayer de montrer que certains langages sans installations OO peuvent en fait utiliser des fonctions d'ordre supérieur et une structure de données pour créer réellement les moyens de mimer efficacement l'orientation d'objet , afin de bénéficier de ses qualités en matière d'organisation de programme, notamment la modularité, l'abstraction et le masquage d'informations. .
Comme je l'ai dit, Lisp a été le banc d'essai de nombreuses évolutions linguistiques, y compris le paradigme OO (bien que ce qui pourrait être considéré comme la première langue OO soit Simula 67, dans la famille Algol). Lisp est très simple et le code de son interpréteur de base est inférieur à une page. Mais vous pouvez faire de la programmation OO en Lisp. Tout ce dont vous avez besoin, c'est de fonctions d'ordre supérieur.
Je n'utiliserai pas la syntaxe Lisp ésotérique, mais plutôt un pseudo-code, pour simplifier la présentation. Et je considérerai un problème essentiel simple: le masquage de l'information et la modularité . Définir une classe d'objets tout en empêchant l'utilisateur d'accéder (la plupart) à l'implémentation.
Supposons que je veuille créer une classe appelée vecteur, représentant des vecteurs bidimensionnels, avec des méthodes comprenant: l'addition de vecteur, la taille du vecteur et le parallélisme.
function vectorrec () {
function createrec(x,y) { return [x,y] }
function xcoordrec(v) { return v[0] }
function ycoordrec(v) { return v[1] }
function plusrec (u,v) { return [u[0]+v[0], u[1]+v[1]] }
function sizerec(v) { return sqrt(v[0]*v[0]+v[1]*v[1]) }
function parallelrec(u,v) { return u[0]*v[1]==u[1]*v[0]] }
return [createrec, xcoordrec, ycoordrec, plusrec, sizerec, parallelrec]
}
Ensuite, je peux attribuer le vecteur créé aux noms de fonction réels à utiliser.
[vecteur, xcoord, ycoord, vplus, vsize, vparallel] = vectorclass ()
Pourquoi être si compliqué? Parce que je peux définir dans la fonction vectorrec des constructions intermédiaires que je ne veux pas être visible pour le reste du programme, afin de conserver la modularité.
On peut faire une autre collection en coordonnées polaires
function vectorpol () {
...
function pluspol (u,v) { ... }
function sizepol (v) { return v[0] }
...
return [createpol, xcoordpol, ycoordpol, pluspol, sizepol, parallelpol]
}
Mais je peux vouloir utiliser indifféremment les deux implémentations. Une façon de le faire consiste à ajouter un composant de type à toutes les valeurs et à définir toutes les fonctions ci-dessus dans le même environnement: Ensuite, je peux définir chacune des fonctions renvoyées afin qu'il teste d'abord le type de coordonnées, puis applique la fonction spécifique pour ça.
function vector () {
...
function plusrec (u,v) { ... }
...
function pluspol (u,v) { ... }
...
function plus (u,v) { if u[2]='rec' and v[2]='rec'
then return plusrec (u,v) ... }
return [ ..., plus, ...]
}
Ce que j'ai gagné: les fonctions spécifiques restent invisibles (en raison de la portée des identifiants locaux), et le reste du programme ne peut utiliser que les plus abstraites retournées par l'appel à vectorclass.
Une objection est que je pourrais définir directement chacune des fonctions abstraites du programme et laisser à l'intérieur la définition des fonctions dépendantes du type de coordonnées. Il serait également caché. C'est vrai, mais alors le code pour chaque type de coordonnées serait coupé en petits morceaux répartis sur le programme, ce qui est moins réductible et maintenable.
En fait, je n'ai même pas besoin de leur donner un nom, et je pourrais simplement conserver les valeurs fonctionnelles anonymes dans une structure de données indexée par le type et une chaîne représentant le nom de la fonction. Cette structure étant locale au vecteur de fonction serait invisible du reste du programme.
Pour simplifier l'utilisation, au lieu de renvoyer une liste de fonctions, je peux renvoyer une seule fonction appelée apply en prenant comme argument une valeur de type explicite et une chaîne, et appliquer la fonction avec le type et le nom appropriés. Cela ressemble beaucoup à l'appel d'une méthode pour une classe OO.
Je m'arrêterai ici, dans cette reconstitution d'une installation orientée objet.
Ce que j'ai essayé de faire, c'est de montrer qu'il n'est pas trop difficile de créer une orientation d'objet utilisable dans un langage suffisamment puissant, y compris l'héritage et d'autres fonctionnalités de ce type. La métacircularité de l'interpréteur peut aider, mais surtout au niveau syntaxique, ce qui est encore loin d'être négligeable.
Les premiers utilisateurs de l'orientation objet ont expérimenté les concepts de cette façon. Et cela est généralement vrai pour de nombreuses améliorations apportées aux langages de programmation. Bien entendu, l'analyse théorique a également un rôle à jouer et a permis de comprendre ou d'affiner ces concepts.
Mais l'idée que les langues qui n'ont pas de fonctionnalités OO sont vouées à l'échec dans certains projets est tout simplement injustifiée. Au besoin, ils peuvent imiter la mise en œuvre de ces fonctionnalités de manière assez efficace. De nombreux langages ont le pouvoir syntaxique et sémantique de faire une orientation d'objet assez efficacement, même lorsqu'il n'est pas intégré. Et c'est plus qu'un argument de Turing.
La POO ne traite pas des limitations des autres langages, mais elle prend en charge ou applique les méthodologies de programmation qui aident à écrire de meilleurs programmes, aidant ainsi les utilisateurs moins expérimentés à suivre les bonnes pratiques que les programmeurs plus avancés utilisent et développent sans ce support.
Je crois qu'un bon livre pour comprendre tout cela pourrait être Abelson & Sussman: structure et interprétation des programmes informatiques .