RECHERCHE :
Bienvenue sur le site de Michel VOLLE
Powered by picosearch  


Vous êtes libre de copier, distribuer et/ou modifier les documents de ce site, à la seule condition de citer la source.
 GNU Free Documentation License.

De la programmation impérative à la technologie objet

28 décembre 2002

Tout programme consiste en la définition (1) des structures de données qui seront manipulées (saisies, consultées, soumises à des traitements) ; (2) des traitements qui seront appliqués aux données ; (3) enfin, d'une succession (comportant éventuellement des boucles, fourches et chemins parallèles) d'appels de données et de traitements. 

Les langages de programmation se distinguent par la façon dont ils réalisent et articulent ces trois éléments, et notamment par les règles de nommage et d'adressage qui permettent de localiser et d'appeler données et traitements.  

Selon la thèse de Church-Turing, tout langage de programmation non trivial équivaut à une machine de Turing ; il en résulte que tout programme que l'on peut écrire dans un langage pourrait être également écrit dans n'importe quel autre langage. La différence entre les langages ne réside donc pas dans ce qu'ils permettent de programmer, mais dans ce qu'il est facile, commode de programmer avec chaque langage : cette différence est d'ordre non pas logique, mais pratique.

Elle n'en est pas moins importante, et il faut relativiser la thèse de Church-Turing. Utilisons une analogie : il est certes également possible de traverser la Seine en marchant sur un câble d'acier ou sur le pont de la Concorde ; mais pour marcher sur le premier il faut être un funambule bien entraîné, alors que le second est à la portée d'un promeneur. Il en est de même pour les langages. Tout ce que l'on fait avec un langage de programmation objet (ou "orienté objet", comme on dit souvent) pourrait être fait en programmation impérative  ; mais la sécurité n'est pas la même dans les deux cas, ni l'évolutivité (possibilité de faire évoluer le programme pour un coût raisonnable). 

Programmation impérative

Si l'on peut tout faire avec un langage de programmation - et notamment faire en sorte qu'une application respecte toutes les contraintes de qualité évoquées dans "Découpage du système informatique en applications" - ce sera plus ou moins difficile selon la démarche de modélisation et le langage de programmation que l'on utilise (la "modélisation", c'est la définition des structures de données et des traitements ; elle est logiquement et chronologiquement antérieure à la programmation proprement dite). Il est trop facile de réaliser des "plats de spaghetti" en Fortran, en Cobol ou en C ; Pascal incite à la modularité, ce qui encourage à rédiger des programmes clairs et bien documentés : mais il est peu utilisé par les programmeurs professionnels. 

Ces quatre langages sont destinés à la programmation impérative, celle qui correspond à l'intuition la plus immédiate pour un informaticien : il s'agit de programmer d'une part les structures de données, d'autre part les traitements qui transformeront les données d'entrées en résultats. La programmation impérative se prête à la mise en commun des traitements (le traitement "mise à jour" peut être utilisé par les  fonctions "créer" et "supprimer") comme des données, éventuellement partagées avec d'autres applications (elles sont alors classées dans une "base de données"). 

Quoique données et traitements soient reliés par nature, puisque le logiciel fait traiter les données par l’ordinateur, l’approche la plus courante a été de concevoir les bases de données séparément des programmes de traitement en utilisant des outils de modélisation et de documentation différents. Certes il est avantageux de traiter ainsi séparément des problèmes de nature différente, mais cela comporte un inconvénient : si l'application est compliquée et si les besoins évoluent (création de nouveaux produits, introduction de nouveaux segments de clientèle, de nouveaux types de partenaires etc.), les mises à jour obligeront à réécrire une grande partie du code au risque d'introduire des erreurs qu'il faudra alors corriger. 

Rien n'empêche bien sûr d'organiser la programmation de sorte que les évolutions soient aussi simples que possible : cela suppose, en pratique, de rapprocher données et traitements. Prenons un exemple : si une entreprise a défini la structure de données et les traitements pour traiter des abonnés à un service, et si elle veut par la suite que l'application puisse traiter aussi des acheteurs de matériel, il lui faudra introduire une nouvelle structure de données et de nouveaux traitements et cela obligera à réécrire une partie de l'application. Mais si l'on a pris la précaution de définir une structure de données "client" capable de comporter comme cas particuliers des abonnés, des acheteurs de matériels ou encore d'autres types de clients, il sera possible d'introduire un nouveau type de client sans écrire autre chose que le code strictement nécessaire. Cependant, comme certains traitements s'appliquent à l'abonné, d'autres à l'acheteur, il faudra que les traitements associés à la structure de données "client" soient paramétrés par le type de client. Il sera donc utile, pour la clarté du code, d'écrire près l'un de l'autre les données et traitements relatifs à la structure "client". Nous sommes alors proches de la définition de que les informaticiens appellent un objet

Ce petit exemple montre comment les informaticiens ont très naturellement été conduits, pour améliorer l'évolutivité des applications, à concevoir des langages dans lesquels les données qui décrivent un dossier sont insérées dans le même module que les traitements qui leur sont appliqués ; pour utiliser les termes consacrés, on dira que données et traitements sont "encapsulés dans le même objet"

Ainsi les langages de programmation objet (ou "orientée objet") sont la conséquence ultime de la modularisation du logiciel, démarche qui vise à maîtriser sa production et son évolution. Mais malgré cette continuité logique ces langages ont apporté en pratique un profond changement dans l'art de la programmation : même si la programmation objet n'est que de la programmation impérative bien organisée, elle implique en effet un changement de l'attitude mentale du programmeur. 

En programmation impérative, la qualité des méthodes devait être apportée par le programmeur lui-même, un programmeur peu rigoureux ayant toute liberté de programmer un "plat de spaghetti" peu évolutif. En programmation objet, une part de la rigueur est incorporée dans le langage, tout comme un pont est muni de garde-fous. Cela n'exclut pas le risque d'erreur : même si un pont est muni de garde-fous, quelqu'un qui veut se jeter dans la rivière, tourner indéfiniment en rond sur le pont ou le franchir en marchant sur les mains pourra le faire ; mais l'utilisateur de bon sens, qui veut tout simplement traverser le pont, le traversera en marchant tout simplement.

Programmation objet

Le mot "objet" est un faux ami, car il oriente dans une mauvaise direction l'intuition de celui qui l'entend pour la première fois et il faut lutter contre cette intuition pour comprendre de quoi il s'agit (voir "A propos de l'utilisation du terme "objet" en informatique"). Pour faciliter la compréhension, nous allons recourir à des analogies.  

Toute entreprise doit gérer des "individus" qui composent des "populations", en utilisant ces termes selon le sens qu’ils ont en statistique : les "individus" peuvent être des personnes physiques ou morales, pièces de rechange, machines, établissements, commandes, factures, etc. Les "populations" sont donc des ensembles de personnes, de pièces de rechange, etc. : une population est un ensemble fini d'individus qui possèdent des caractéristiques analogues. 

Dès que les entreprises se sont organisées (voir "Évolution du SI : du concept au processus"), elles ont construit des "dossiers" pour décrire les individus ; à chaque population correspondait un type de dossier (ou de formulaire, ou de questionnaire) définissant une liste de données, à chaque individu correspondait un dossier rempli, chaque case contenant la valeur d'une donnée observée sur l'individu. A chaque dossier étaient associés également quelques calculs : vérifications et recoupements pour s'assurer de l'exactitude des données ; évaluation des taxes, droits, montants de facture, etc. 

La modélisation objet, même si elle semble nouvelle, revêt cette démarche traditionnelle d'un vocabulaire nouveau. La "classe", description de l'individu type d'une population, n'est rien d'autre qu'un dessin de dossier dans lequel on aurait fait figurer, outre la définition des données (que l'on appelle "attributs"), celle des traitements qui leur sont associés (que l'on appelle "opérations" ou "méthodes"). Le dossier relatif à un individu, dans lequel sont inscrites ("instanciées") les valeurs des données observées sur cet individu, sera nommé "objet". 

Ce vocabulaire provoque des confusions. La "classe" du langage objet n'est pas la "classe" d'une classification (qui est, elle, l'un des éléments d'une partition opérée sur une population) ; l'"objet" du langage objet, qui représente un individu, n'est pas l'"objet" de la philosophie, qui est l'individu lui-même ; les "méthodes" du langage objet ne sont pas des méthodes au sens où l'on dit "méthode de travail", mais des fonctions au sens mathématique du terme, comme lorsque l'on écrit y = f(x). 

Lorsqu'un informaticien dit "un objet est une abstraction", cette phrase est exacte car il parle d'un objet au sens de la modélisation objet : en effet la liste des données que contient cet objet, étant sélective, est "abstraite" de l'ensemble indéfini des données que l'on pourrait observer sur un même individu. Mais comme le philosophe entend par "objet" l'individu lui-même, cette phrase est pour lui un non-sens. Toutefois le philosophe aurait tort d'en rester à cette impression superficielle : s'il accepte de comprendre le vocabulaire de la modélisation objet, il verra que celui-ci ne fait qu'organiser de façon ingénieuse la démarche d'abstraction et de conceptualisation qui, depuis Aristote, est le fondement même de la philosophie. 

*
*  *
Le modélisateur choisit la liste des attributs à retenir pour caractériser une classe. Ce choix est guidé par les exigences de pertinence et de sobriété du système d'information (Voir "Découpage du système informatique en applications") : il faut connaître les données utiles à l'action que l'on entend assister, et elles seules. 

Supposons que pour une application particulière on ait défini la classe "automobile" et retenu la liste suivante des attributs et des méthodes : 

Il est d'usage de terminer le nom d'une méthode deux parenthèses successives. Ici, nous supposerons qu'age() calcule l'âge d'une automobile par différence entre la date d'achat et la date du jour et que  kilometrage() assure la mise à jour du kilométrage cumulé par saisie d'un kilométrage hebdomadaire. 

A chaque automobile sera associé un "objet" où figurent les instances des divers attributs. L'objet comporte (1) un identifiant qui permet de désigner sans ambiguïté l'individu dont il s'agit (ici : numéro du châssis) ; (2) les valeurs des attributs observées sur cet individu ; (3) les méthodes définies pour les individus de la classe. Voici l'objet qui correspond à une automobile concrète :

Les attributs qui caractérisent un objet sont protégés contre des modifications intempestives : ils ne peuvent être modifiés que si l'utilisateur emprunte la procédure prévue, que l'on appelle "interface". La protection ainsi accordée aux données est nommée "encapsulation". C'est l'un des garde-fous que procure la programmation objet : en programmation impérative, si l'on n'y prend garde, les données pourraient être modifiées sans précaution particulière. 

Héritage, polymorphisme, agrégation

La modélisation objet utilise des procédés qui permettent d'économiser l'écriture du code et contribuent à son évolutivité :

Héritage

Certaines populations peuvent faire l'objet d'une segmentation en sous-populations : c'est le cas des classes "employés", "client", "produit", "fournisseur", etc. Chaque sous-population peut être caractérisée par les mêmes données que la population entière, auxquelles on ajoute des données propres à la sous-population. Par exemple, si l'on considère la population des "employés", on peut définir les sous-populations "managers", "ouvriers", "secrétaires" etc. caractérisées chacune par des données spécifiques, venant s'ajouter aux données qui caractérisent les "employés" en général.

Si l'on utilise le langage de la modélisation objet, on dira que les classes "manager", "ouvrier", "secrétaire" héritent de la classe "employé" car elles reprennent ses attributs et méthodes en leur ajoutant des attributs et méthodes spécifiques. 

L'héritage peut être partiel : dans ce cas, seule une partie des attributs ou méthode de la classe-mère est repris par la classe-fille. Il peut être simple ou multiple : une même classe peut être sous-classe de plusieurs classes. Toute modification de la classe-mère entraîne une modification des classes-filles, sans qu'il soit nécessaire de les reprogrammer. En sens inverse, il arrive que plusieurs classes puissent être considérées comme filles d'une même classe-mère, ce qui permet de regrouper leurs caractéristiques communes en créant cette classe-mère. 

Le programmeur peut construire rapidement les classes dont il a besoin en puisant dans le stock des classes existantes, les "bibliothèques". Les bibliothèques fournies avec un langage objet sont riches ; le premier travail du programmeur sera de sélectionner et charger les classes utiles, après quoi la programmation ressemblera un peu au montage d'un produit vendu en "kit". Le programmeur peut bien sûr constituer une bibliothèque avec les classes qu'il a définies lui-même, ce qui lui permettra de réutiliser le code (Nota Bene : cet argument est souvent évoqué en faveur des langages objet ; toutefois la réutilisation suppose chez le programmeur une rigueur peu fréquente). 

Polymorphisme

Le "polymorphisme" est, pour une même opération, la faculté de s'appliquer à des objets appartenant à des classes différentes. 

Exemple : pour calculer la surface d’un carré, on utilise la formule S = a2 ; pour un triangle, S = Ah/2 ; pour un cercle, S = πR2 ; pour un trapèze, S = (A + B)h/2 etc. Ainsi la même opération « calculer la surface » s’appuie, selon la figure géométrique considérée, sur des méthodes différentes.

Si l'opération calculer_la_surface() est douée de polymorphisme, elle provoquera l’application de la méthode adaptée quelle que soit la figure géométrique. Dès lors le programmeur n’a plus à se soucier du type de figure auquel cette opération s’appliquera.

Agrégation

L'agrégation indique qu'une classe est une composante d'une autre classe. Cela permet de définir des objets composés d'autres objets, que l'on nomme "packages" ou "composants". L'une des classes agrégées dans un  composant est la classe principale ("main") qui assure les fonctions d'interface avec le reste du monde et lance les traitements dans les autres classes. 

Exemple : le composant "client" peut contenir une classe "adresse" qui permet de noter la (ou les) adresses du client. La même classe "adresse" pourra être utilisée par le composant "fournisseur", le formalisme de l'adresse ne changeant pas selon que l'on considère l'un ou l'autre des composants. 

Écriture et exécution d'un programme objet

Écriture

Avant d'écrire la première ligne d'un programme objet, il importe d'avoir défini les classes, attributs, etc., c'est-à-dire d'avoir "modélisé" l'application. Ce travail sera fait soit par le programmeur, qui devra faire valider ses choix par le client, soit par la maîtrise d'ouvrage qui livrera au programmeur un "modèle métier" (ou "spécifications générales") proche des préoccupations du client mais déjà formulé selon les concepts de la méthode objet (classes, attributs, etc.). L'état de l'art propose d'utiliser, pour cette modélisation, le langage UML. Puis ce modèle sera progressivement précisé (voir "Modélisation du processus") jusqu'à ce que toutes les décisions nécessaires à la production du code aient été prises. 

Le programmeur lance alors, en s'appuyant sur le modèle, un générateur de code source qui produit automatiquement de l'ordre de 80 % des lignes ; il restera à les compléter pour finir l'écriture du programme. Un outil comme Rose de Rational (http://www.rational.com/products/rose/index.jsp)  permet à la fois d'écrire le modèle, dans ses diverses étapes, et de générer le code source : ainsi le travail peut progresser de façon cumulative, sans perte d'information et sans qu'il soit besoin de le réécrire pour l'adapter à un formalisme nouveau. 

La programmation objet requiert une réflexion préalable plus longue que celle, peut-être insuffisante, qu'il est habituel de consacrer à la programmation impérative. Par contre, une fois le modèle construit, l'écriture du code est plus rapide. 

Exécution

Lorsqu'un programme objet est lancé, l'exécution commence par une des classes (nommée "main", classe principale). Cette classe envoie des messages vers d'autres classes ou sollicite une action de l'opérateur humain (ou d'une autre application), ce qui déclenche une cascade de traitements, messages, affichages sur les équipements périphériques (écrans, imprimantes etc.) jusqu'à la fin de l'exécution du programme.  

Les objets distribués

Les objets communiquent par des messages qui transmettent des données ou des appels de méthodes, les adresses étant définies dans l'espace de nommage du programme. L'adressage et le libellé des messages posent problème lorsque l'on travaille sur des objets résidant sur des ordinateurs différents ("objets distribués"), appartenant à des programmes écrits dans des langages différents ou exploités sur des plates-formes différentes. Il faut alors utiliser un "Object Request Broker" (ORB) : il permet de concevoir des applications où le traitement réalisé sur un ordinateur nécessite l'exécution d'un autre traitement sur un autre ordinateur. Les objets distribués sur le Web sont nommés "Web services". 

La définition des ORB a nécessité unee normalisation qui a été assurée par l'OMG (Object Management Group, www.omg.org, organisation internationale qui fédère les entreprises concernées par la technologie objet). L'OMG a défini l'architecture MDA ("Model Driven Architecture"), dont les fleurons sont la norme CORBA pour les ORB ("Common Object Request Broker Architecture") et le langage de modélisation UML ("Unified Modeling Language", voir "Le langage UML"). Les outils CORBA relèvent, comme les EAI, du "middleware" : ils assurent des fonctions de communication entre programmes et remplissent donc mutatis mutandis une fonction analogue à celle des commutateurs d'un réseau téléphonique ou des "hubs" d'un transporteur aérien. . 

Articuler la technologie objet et la programmation impérative

Pour longtemps encore le système d'information d'une entreprise devra faire cohabiter les applications nouvelles, écrites en langage objet, et les applications anciennes qui gèrent séparément les bases de données et les traitements. Il est possible d'introduire dans un programme objet des êtres qui se comportent comme des objets quand on les voit du côté du langage objet et qui lancent des requêtes et appels de traitements vers l'application traditionnelle. 

Sun commercialise la plate-forme J2EE ("Java 2 Platform, Enterprise Edition") qui assure la communication entre les applications objet, les bases de données et les applications traditionnelles.

Petit historique de la programmation objet

Les premiers langages de programmation qui aient utilisé des objets sont Simula I (1961-64) et Simula 67 (1967), conçus par les informaticiens norvégiens Ole-Johan Dahl et Kristan Nygaard pour simuler le comportement de systèmes complexes. 

Simula permet de traiter numériquement des problèmes qu'il serait impossible de résoudre de façon analytique, par des équations explicites. Il a été par exemple utilisé pour calculer la distribution statistique des durées de traitement des passagers dans un aéroport, problème qui articule plusieurs phénomènes aléatoires (rythme d'arrivée des passagers, durée de l'enregistrement, durée des déplacements dans l'aéroport etc.) ; Simula représente chaque passager par un objet (nombre de bagages, destination etc.), et après un nombre suffisant de simulations il fournit les histogrammes des distributions statistiques recherchées. Simula 67 contenait déjà les objets, les classes, l'héritage, l'encapsulation etc. 

Alan Kay, du PARC de Xerox, avait utilisé Simula dans les années 60. Il réalisa en 1976 Smalltalk qui reste aux yeux de certains programmeurs le meilleur langage de programmation objet. Pour Alan Kay, chaque objet est comme un petit ordinateur qui interagirait avec d'autres ordinateurs : « Bob Barton (...) had said (…) : "The basic principle of recursive design is to make the parts have the same power as the whole". For the first time I thought of the whole as the entire computer and wondered why anyone would want to divide it up into weaker things called data structures and procedures. Why not divide it up into little computers, as time-sharing was starting to ? But not in dozens. Why not thousands of them, each simulating a useful structure ? » (Alan Kay, « The Early History of Smalltalk », History of Programming Language II, Addison Wesley, 1996, p. 516).

Bjarne Stroustrup a mis au point C++ aux Bell Labs d'AT&T en 1980. C++, c'est "C with classes", le langage C doté des outils qui permettent la programmation objet. C++ deviendra le langage le plus utilisé par les programmeurs professionnels ; il requiert beaucoup de savoir faire, car comme C il permet d'agir sur les couches basses (programmer un pilote de disque ou une pile TCP/IP), possibilité utile pour un industriel mais dangereuse entre les mains d'un débutant (voir "Entretien avec Laurent Bloch") et qui n'est d'ailleurs généralement pas nécessaire pour programmer une application destinée à une entreprise. 

Java est lancé par Sun en 1995. Comme il présente plus de sécurité que C++ il deviendra le langage favori de certains développeurs professionnels. Un programme en Java peut être soit directement compilé en langage machine (tout comme un programme en C++), soit d'abord compilé dans un code intermédiaire, le "bytecode", puis interprété et exécuté sur chaque machine par une "Java Virtual Machine". Ce dispositif en deux étapes permet d'exécuter un même programme Java sur des plates-formes diverses, pourvu qu'elles soient munies chacune de sa JVM. Cela permet à Java de réaliser l'un des objectifs les plus ambitieux en informatique : "write once - run anywhere", un programme une fois écrit peut être exploité sur toutes les plates-formes. En outre Java fournit au programmeur des outils commodes comme celui qui libère automatiquement la mémoire non utilisée ("garbage collector"). Cette fonction faisait défaut en C et C++, dans lesquels la gestion de la mémoire est un casse-tête pour les programmeurs. 

On doit ajouter à la liste de nombreux autres langages : Eiffel, Objective C, Loops etc. Presque tous les langages impératifs se sont dotés d'outils de programmation objet. Les programmeurs compétents en programmation objet sont les plus demandés sur le marché. Les langages de programmation objet sont entourés d'outils de productivité pour le programmeur professionnel : générateurs automatiques de code, bibliothèques de classes, environnement de développement intégré, outils de test etc. 

Faut-il désormais tout programmer en langage objet ? pas nécessairement. Pour des applications simples, ou entre les mains d'un programmeur rigoureux, la programmation impérative est efficace - et on peut faire beaucoup de choses en utilisant simplement Excel, Word, PowerPoint et FrontPage ! Par ailleurs, l'essentiel des applications exploitées dans les entreprises a été programmé en Cobol ; elles fonctionnent correctement et il faudra pendant des années encore assurer leur maintenance, ce qui assure une longue vie à la programmation en Cobol.