Tutoriel Programmation objet immersive en Pharo proposé par l'inria disponible à l'adresse https://mooc.pharo.org et sur la plateforme FUN-MOOC.
Présentation par Stéphane Ducasse, direction de recherche, 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, pourtant c'est un peu différent. Pour l'assurance, la notion de risque, c'est son business intégral, alors que pour la banque, le risque est juste que vous ne remboursiez pas, donc ils prennent quelques précautions liées à ce risque spécifique. Comme on peut le percevoir ici, on va 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, d'autres é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'approche orientée objet, 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 l'exploiter selon ses objectifs. L'idée, c'est d'organiser cette modélisation du monde avec une représentation d'organisation 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.
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.
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.
Prenons un exemple. Si on modélise une image ou un diagramme qui serait composé de formes géométriques comprenant un rectangle, un carré et un cercle et admettons que je veuille calculer l'aire de ce diagramme.
Dans une vision procédurale du monde, je vais définir une procédure que je peux appeler '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 la donnée. Je vais dire :
Au final je retournerai le résultat: la somme de ces aires. 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 implications 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 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 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 ?
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 va favoriser 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: