"Une nouvelle façon de modéliser le monde"
Stéphane Ducasse, research direction, Inria.
-Dans ce cours, je vous montrerai
que la programmation objet et la conception objet
représentent le monde d'une manière décentralisée.
D'abord, il faut voir qu'il n'y a pas un modèle central et unique du monde.
Si on compare la notion de risque dans une banque et dans une assurance,
on pourrait croire que c'est la même chose, mais non.
L'assurance, la notion de risque, c'est son business intégral.
Alors que dans la banque, le risque est juste que vous ne remboursiez pas,
donc ils prennent des précautions.
Donc, le monde...
Vous allez avoir différents modèles pour modéliser une même chose.
Maintenant, il y a différentes façons d'aborder la modélisation
et de créer ces modèles.
Dans le passé, des approches étaient dirigées par les données,
étaient plus favorables aux bases de données relationnelles.
Il y a eu de la programmation objet, ce qu'on étudie dans ce cours,
de la programmation procédurale et d'autres.
Là, on va comparer ces deux approches.
L'approche orientée, qu'est-ce que c'est ?
C'est un paradigme. Ce n'est pas une technologie.
C'est une façon différente de simuler et de représenter le monde
pour en tirer les objectifs qu'on a.
L'idée, c'est d'organiser cette modélisation du monde
avec une représentation d'organisations décentralisée.
L'idée, à ce moment-là,
c'est d'essayer de contrôler la complexité de ces systèmes
et de permettre leur meilleure réutilisation.
C'est ce qu'on va voir.
D'abord, on va faire une comparaison
entre une modélisation procédurale et une modélisation objet.
Si on regarde la modélisation procédurale,
elle va faire un focus sur les données et les procédures.
Les données sont partagées entre toutes les procédures.
Les données sont accessibles par tous les clients
et les procédures connaissent toutes les structures des données.
Il n'y a pas cette distance qu'il y a entre un message et une méthode
comme dans la modélisation objet.
Il n'y a pas de notion de liaison tardive.
Donc, ça veut dire qu'on a besoin d'un grand nombre de procédures.
Il n'y a pas la possibilité d'avoir deux procédures qui ont le même nom
et qui auraient des comportements différents,
comme on l'a vu dans la programmation objet lors des cours précédents.
Donc, prenons un exemple.
Si on modélise une image ou un diagramme
qui serait composé de formes géométriques...
Là, j'ai un diagramme avec un rectangle, un carré et un cercle
et je voudrais calculer l'aire de ce diagramme.
Dans une vision procédurale du monde,
je vais définir une procédure qui s'appelle pictureArea
et dans cette procédure, que vais-je faire ?
Je vais parcourir la liste
des formes géométriques qui composent le diagramme
et à chaque fois, je vais regarder.
Je vais dire : "Si c'est un carré, je sais le calculer.
Je vais faire côté multiplié par côté.
Si c'est un rectangle, je vais faire longueur multipliée par largeur.
Si c'est un cercle, je vais calculer pi r²."
Et je vais rendre la chose. Là, je l'ai écrit en Java.
On peut très bien développer de façon procédurale dans un langage objet.
Rien ne nous en empêche. Là, on a la même définition en Pharo.
On va faire une boucle et regarder pour chacune des formes géométriques
et on va calculer ça.
Quel est le désavantage de cette approche ?
Il y en a plusieurs. Le premier, c'est que la définition est monolithique.
Elle n'est pas embêtante en elle-même, mais ses applications le sont.
Ça signifie que je ne peux pas la réutiliser.
Je ne peux pas réutiliser la fonction pictureArea
si j'ajoute une nouvelle forme.
Si demain, je mets une étoile dedans,
je vais devoir modifier le code de pictureArea.
En plus, je ne peux pas réutiliser
la définition de l'aire dans un carré ou un rectangle,
puisque tout est défini au même endroit.
Le modèle objet, d'un autre côté, est complètement différent.
Il va déléguer le calcul de l'aire à d'autres entités.
Donc, la classe Diagramme, Picture, que va-t-elle faire ?
Elle va juste faire une boucle sur chacune des formes géométriques
et demander à chaque forme géométrique de calculer une aire, son aire.
Chaque forme géométrique va définir une méthode aire
qui va lui retourner la bonne définition.
Quel est l'avantage ?
Déjà, je peux ajouter une forme géométrique sans aucun problème.
Il suffit juste que j'ajoute une classe avec le message aire
et que je crée les objets de cette classe.
Je peux réutiliser, là, la méthode aire de la classe Picture.
Donc, là, j'ai vraiment de la réutilisation.
On voit qu'on a un calcul décentralisé.
Ça signifie que la classe Diagramme va demander aux autres classes
de faire un calcul pour elle.
Si on regarde, il y a vraiment un point central.
C'est que si même dans la vision objet,
j'avais défini des méthodes avec des noms différents,
je n'aurais pas pu avoir le bénéfice entier de l'approche objet.
Ce qui est important, c'est que chacune des formes géométriques
se met d'accord et offre la même interface au client.
Ça signifie que chacune des formes va offrir la méthode aire,
ce qui va permettre au client de ne pas avoir à faire de distinction
et du coup, qu'on puisse le réutiliser.
Ça s'appelle le polymorphisme.
Différents objets vont pouvoir répondre
à un même message avec différentes exécutions.
Ça, c'est tout d'un coup très puissant.
Donc, qu'est-ce qu'une application dans un système objet ?
C'est un ensemble d'entités qui vont collaborer et interagir.
Les entités sont des objets.
On a vu que les objets ont un comportement et un état.
Que cet état est privé et n'est accessible que des méthodes.
Que ces objets interagissent avec des messages
et que ces messages sont résolus dynamiquement
et que la méthode correspondante va être exécutée lors de l'exécution.
Et le dernier point que j'ai mentionné, c'est que ces messages
forment souvent des familles polymorphiques
qui me permettront de réutiliser le code.
Donc, les messages, je le disais, ont une entité unique.
Le point central, c'est qu'ils doivent exposer des interfaces polymorphiques
qui seront substituées par d'autres objets.
Maintenant, j'aimerais aborder
trois points essentiels de la programmation objet
qui sont les pierres angulaires de la programmation objet.
La première, c'est l'encapsulation. On l'a vue dans un autre cours.
C'est l'idée qu'en tant que client, je vais envoyer un message à un objet
et l'objet contrôle la manière dont il encode l'information
et la manière dont il va faire du calcul.
Cette encapsulation est vraiment importante,
car je pourrai substituer un objet par un autre
sans que le client soit impacté
et c'est très important, car ça favorisera la réutilisation.
Cette encapsulation va permettre, par exemple, une composition d'objets.
En tant que client, qu'un objet fasse tout le calcul
ou qu'il distribue son calcul à d'autres objets,
ça ne m'importune pas à partir du moment où je ne sais pas que ça se passe
et que je ne suis pas impacté par les décisions de cet objet.
Donc, l'encapsulation et la composition d'objets vont de pair.
La composition d'objets va permettre
l'explicitation d'un calcul souvent de manière plus simple.
L'autre pierre angulaire, c'est cette notion de distribution
ou de décentralisation.
L'idée, c'est que quand je vais calculer quelque chose,
ça va être le résultat de plusieurs objets qui exécutent des sous-tâches.
C'est favorisé par la liaison tardive
qui va dire, en fait : "Je saurai quelle est la méthode à exécuter
lors de l'exécution et ce sera basé sur la classe du receveur."
C'est renforcé par cette notion de polymorphisme
qui va faire que plusieurs objets vont pouvoir être substituables
et proposer la même interface.
Donc, cet ensemble de points va favoriser cette idée
qu'on aura un système qui sera plus décentralisé
et qui va pouvoir maximiser de la réutilisation.
Dans l'exemple qu'on avait pris avec le diagramme,
le comportement du calcul de l'aire du diagramme
est indépendant du calcul de l'aire de chacun de ces éléments.
Donc, on le réutilise.
Le dernier point-clé de la programmation objet
que je ne montre pas dans ce cours mais que je veux évoquer,
c'est l'idée que je pourrai définir des abstractions
par extension d'autres abstractions.
Imaginons qu'on ait une classe qui représente des collections.
Je pourrai définir une autre classe, qui est une OrderedCollection,
par extension de la collection.
Je pourrai définir la classe Tableau par extension de la classe Collection.
Par extension, je vais réutiliser
tous les comportements que j'ai définis dans la classe Collection
et je pourrai spécialiser ces comportements dans chaque classe.
Donc, l'héritage est ce mécanisme qui permet une définition incrémentale
d'une abstraction par rapport à une autre.
Et c'est très important dans la programmation objet.
Dans le cadre de ce cours, je le traite après,
mais je voulais dire que c'est vraiment un point essentiel.
Finalement, qu'a-t-on vu ?
On a vu que dans une conception objet, un programme,
c'est des entités qui collaborent et envoient des messages.
Que les objets, ces entités encapsulent les données
et contrôlent l'interface qu'elles exposent à leurs clients.
La liaison tardive permet de sélectionner la bonne méthode à exécuter
et va éviter à un client de devoir faire des vérifications
avant d'exécuter un message.
Et ce mécanisme-là est renforcé si les objets qui offrent un même service
le décrivent par des méthodes qui ont le même nom,
ce qui va faire que le client va pouvoir simplement donner les ordres
sans avoir à devoir choisir quel ordre il doit donner
pour chaque objet avec lequel il interagit.