Rappel : Ce cours est enseigné dans la
spécialité PISE du Master MECI (Université Paris 7)
par Christophe Darmangeat

Partie 12
Initiation à la P.O.O.
Et si je vous disais que toute ce qu'on a fait jusque là n'était qu'une longue introduction ? Que les véritables délices de la programmation objet vous restent encore inconnus ? Que seules les lignes qui suivent pourront vous emporter vers les cimes de la béatitude informatique ? Et que j'ai dû forcer sur la bouteille hier soir pour écrire des choses pareilles ce matin ? Le croiriez-vous ?
En effet, jusqu'à présent, nous n'avons fait qu'apprendre à utiliser des éléments préfabriqués - par d'autres - pour construire nos applications : ces éléments sont les fameux contrôles, que je ne vous présente plus. Cela nous a permis, du point de vue de l'interface, de franchir un pas considérable par rapport à la programmation classique. Ainsi, nous avons découvert la programmation événementielle. C'est déjà bien, me direz-vous.
Certes, vous répondrai-je, car aujourd'hui, j'ai décidé de ne pas être contrariant. Mais réfléchissons un peu plus loin : lorsque nous avons eu des informations à mémoriser ou à traiter, nous avons utilisé les bons vieux outils de la programmation classique : des variables, voire des tableaux ou des variables structurées. Et au pire du pire, des tableaux de variables structurées.
Or, rappelez-vous ce que nous avions vu au tout début de ce cours, lorsque nous avions défini ce que sont les objets : les objets sont, avant toute autre chose, une manière nouvelle de structurer les informations (j'avais employé le terme de "super-variables"). De tout cela, qu'avons nous fait ? En réalité, rien : nous l'avons mis de côté, pour ne nous servir que d'une catégorie particulière d'objets, les contrôles. A peine avons-nous effleuré, pour un besoin précis ou pour un autre, des objets comme App. Autrement dit, même si c'est dur à entendre, il est de mon devoir de vous parler franchement : jusqu'à présent, nous n'avons pas réellement abordé la programmation objet.
Alors, avouez-le, ce serait vraiment trop dommage de se quitter sans remédier à cette grave lacune. Car si les fainéants nous diront que l'humanité a bien vécu quelques millions d'années sans connaître la programmation objet et qu'elle ne s'en est pas portée plus mal, nous leur répondrons sans hésiter qu'avec des raisonnements pareils, on en serait encore à tailler des silex.
Résumons-nous, en regroupant dans un tableau les concepts et le vocabulaire lié à la manipulation des objets et des variables. Rassurez-vous, on reviendra sur tout cela dans pas longtemps :
 
 
Variable
Objet
contient
des informations
des informations (les propriétés) et du code (les méthodes)
est désigné par un...
nom de variable
nom d'objet
le "moule" s'appelle
un type ou
une structure
une classe
On peut fabriquer ce "moule" dans
un module
un module de classe
1. Mener une analyse objet
Si l'on veut développer une application en raisonnant réellement en termes d'objets, c'est dès le départ, bien avant d'écrire le code, qu'il faut orienter l'analyse dans cette direction.
Mener correctement l'analyse d'un projet complexe en termes objets suppose un savoir-faire extrêmement long à acquérir. Les détracteurs de la méthode objet parlent d'ailleurs volontiers à ce propos d'usine à gaz. Et si l'on jette un oeil sur les ouvrages traitant de la question, on est généralement tenté de leur donner raison, tant le vocabulaire est imperméable à tout être humain normal, et tant les explications fournies fourmillent d'abstractions qui les rendent parfaitement obscures.
Cependant, pour se faire une idée de la chose, on peut aborder la question d'une manière simple, en menant une rapide analyse d'un problème pas trop compliqué. Encore une fois, j'insiste, les lignes qui suivent se veulent être une très brève illustration, et nullement un exposé couvrant tous les recoins de la chose.
Imaginons donc  que nous voulions programmer un jeu de Scrabble pour deux joueurs.
1.1 Analyse classique
Dans une analyse classique, on ferait l'inventaire des données qu'il faudrait coder, et de la manière dont ces codages seraient effectués au mieux.
  • Plateau de jeu : pour chaque emplacement du plateau de jeu, on doit savoir s'il s'agit d'une case  simple, ou d'une case avec bonus (mot compte double, lettre compte triple, etc.). On doit également savoir si une lettre y a été posée, et quelle est cette lettre.
    Une solution simple consiste à créer un tableau de caractères à deux dimensions. On représenterait les cases vides par un chiffre, correspondant à leur valeur : 0 pour une case simple, 1 pour une lettre compte double, 2 pour une lettre compte triple, etc. Lorsqu'une lettre serait posée, on remplacerait dans la case du tableau ce code par la lettre en question.
  • Valeur des lettres : un autre aspect à gérer est de mémoriser les valeurs des différentes lettres : au Scrabble, le A vaut 1 point, le B vaut 3 points, etc. Il y a plusieurs moyens de répondre à ce problème. Un d'entre eux consiste à remplir un tableau de 26 nombres, en y mettant, dans l'ordre, les valeurs des lettres. Il suffira ensuite de retrouver que le M est la 13e lettre de l'alphabet, pour aller chercher la treizième valeur de ce tableau.
  • Nombre de lettres : toutes les lettres ne sont pas disponibles en quantités égales dans le jeu. Fort heureusement, il y a davantage de E que de W, par exemple. Là aussi, il faudra stocker et mettre à jour cette information, par exemple en tenant à jour un tableau de 26 numériques indiquant les disponibilités restantes pour chaque lettre.
Ces quelques lignes ne représentent bien entendu pas une véritable analyse : elles ne font qu'effleurer l'affaire, mais je les ai écrites pour montrer (en fait, normalement, ce n'est qu'un rappel) quelles questions l'on se pose quand on programme de manière traditionnelle.
1.2 Analyse Objet
Passons maintenant à un bref aperçu de la manière dont un programmeur objet se poserait les problèmes.
Au scrabble, que fait-on ? On pioche des jetons, qu'on met sur les réglettes des joueurs, puis sur le plateau. Nous devons donc programmer les tribulations de différents objets de type jeton.
Le plateau est formé de cases, qui ont différentes caractéristiques (leur emplacement, leur valeur, le fait qu'elles soient vides ou occupées par des jetons...). Les cases du plateau seront elles aussi des objets maniés dans le jeu Scrabble.
Dès maintenant, remarquons que ce qui entre en compte n'est pas, pour l'essentiel, l'aspect graphique. Certes, on voit les jetons et les cases (encore que, pas toujours, il y a des jetons cachés). Mais ce n'est pas le problème, tout au moins, pas pour l'instant. Ce qui compte, pour parler comme les vrais informaticiens, c'est l'aspect fonctionnel des choses. Ce qui compte, ce sont les "choses" qu'on manipule (j'emploie un mot fourre-tout exprès, car ces "choses" peuvent être tout et n'importe quoi). Mais en l'occurrence, comme souvent, le mieux, c'est de faire simple et direct. Nos objets sont donc des jetons et des cases.
Continuons, en examinant ces objets de plus près.
  • Les cases : chaque case se caractérise par sa position sur le plateau (numéro de ligne, numéro de colonne), par son type de bonus, et par son contenu (le jeton qui vient éventuellement l'occuper). Accessoirement, la case se caractérise aussi par un graphisme différent (les "mots compte double" n'ont pas la même tête que les "lettre compte triple").
  • Les jetons : chaque jeton du jeu possède une lettre faciale, une valeur, et un emplacement (est-il dans le sac, sur la réglette de tel joueur, ou sur telle case du plateau).
Là aussi, ces quelques lignes sont bien sommaires, et ne font qu'indiquer la direction dans laquelle une analyse complète devrait être menée.
Ce qu'on peut établir dès à présent, c'est que notre jeu manipule deux types d'objets : : le type "cases", et le type "jetons". Toutefois, en bons programmeurs, nous ne parlerons pas de "types" d'objets (ce serait sans doute trop clair) mais de classes d'objets. Nous aurons donc deux classes : la classe cases, et la classe jeton. Exactement comme nous avons jusqu'à maintenant manié des objets de la classe Bouton de Commande, de la classe Case à Cocher, etc.
Chaque case du plateau, chaque jeton du jeu est donc un représentant de la classe Cases ou de la classe Jeton. Mais là encore, attention Léon : pas question de parler de "représentant", ce serait d'une vulgarité affligeante. L'usage impose le terme fleuri d'instance de classe. Nous dirons donc que les cases individuelles sont des instances de la classe Cases, et que chaque jeton est une instance de la classe Jeton. De la même façon, jusqu'à présent, chaque bouton de commande que nous avons créé était une instance de la classe Boutons de Commande. Ainsi, dans la vie courante, vous saurez dorénavant que chaque gâteau est une instance de la classe Moule à Gâteau. Pensez-y la prochaine fois que vous commanderez quelque chose dans une boulangerie. Effet 100% garanti.
Enfin, chaque caractéristique d'un objet individuel (pour les cases, le numéro de ligne, le numéro de colonne, le type du bonus, etc. ou pour les jetons, la lettre représentée, le nombre de points, l'emplacement dans le jeu) est une propriété de l'objet en question. Mais ce gros mot-là, vous le connaissiez déjà.
Il va de soi qu'en plus des propriétés, nous pourrions éventuellement concevoir des méthodes pour nos objets, c'est-à-dire des traitements en quelque sorte préfabriqués sur leurs propriétés. Quoique dans l'exemple du Scrabble, aucune ne s'impose immédiatement à l'esprit ; on pourrait toujours se forcer pour en inventer, mais franchement, on n'est pas là pour imaginer des difficultés là où il n'y en a pas.
Enfin, une analyse qui serait poussée plus loin devrait également recenser les événements que chaque objet de nos nouvelles classes pourrait recevoir, et les réactions de l'application à ces événements. Là encore, je ne fais que signaler l'existence éventuelle de ce problème, car dans notre exemple, les événements classiques (clic, drag and drop, etc.) devraient largement suffire.
Parvenus à ce stade, on voit qu'en fait, on a recensé peu ou prou les mêmes choses que dans l'analyse classique. Alors pourquoi tout ce barouf ? Parce que nous avons organisé nos idées différemment, et que c'est cette organisation différente qui va nous simplifier la vie au niveau de la programmation (du moins, si l'analyse a été bien menée, que nos classes et nos propriétés sont pertinentes).
La programmation objet ne dispense d'aucune abstraction mentale nécessaire à la programmation traditionnelle. En ce sens, elle ne constitue aucun progrès. Mais elle rend l'écriture de l'application beaucoup plus proche des événements  tels qu'ils se déroulent dans la réalité. Et en cela, elle permet de développer un code beaucoup plus lisible. Le travail en équipe des programmeurs sera donc plus facile à coordonner, et la structure du programme sera plus légère et plus claire. Enfin, si c'est réussi.
Un des paris de la programmation objet, c'est d'alourdir un peu la phase d'analyse, pour gagner beaucoup sur la phase de développement.
1.3 Objets et Visual Basic
Mais, si ce n'est que cela, direz-vous, ne serait-ce pas tout bêtement donner un nouveau nom à une vieille marmite ? Car après tout, construire des variables par agglomération de types simples, il y a très longtemps qu'on sait le faire ! Depuis les langages traditionnels et les bonnes vieilles données structurées ! Les objets ne seraient-ils donc pas tout bêtement des données structurées rebaptisées autrement pour être mieux vendues ?
Eh bien non, comme on l'a déjà dit, les objets ne sont pas que cela. Ils sont certes des données structurées. Mais ils sont des données structurées avec deux possibilités supplémentaires, ce qui change complètement la donne.
  1. les objets ne sont pas que des propriétés ; il sont aussi des méthodes. Cela veut dire que si dans un langage traditionnel, on sépare les données et les traitements sur ces données, en programmation objet, à l'inverse, on fabrique des bidules (les objets) qui regroupent les données (les propriétés) et les actions sur ces données (les méthodes). Visual Basic, dans la mesure où il fournit le moyen de construire de tels objets, est un langage objet.
  2. les objets possèdent une souplesse remarquable, qui se traduit notamment par la notion d'héritage. Je crée une classe "mère", puis des classes "filles" qui héritent de toutes les caractéristiques de la classe mère, plus d'autres, puis des classes petites-filles, qui héritent des caractéristiques de la classe fille, plus d'autres, etc.
    Ainsi, par exemple, je crée la classe "animal", puis les classes filles "reptile", mammifère", "poisson", etc. qui possèdent toutes les propriétés et méthodes des animaux, plus d'autres spécifiques. Et ensuite, je crée les classes "félin", "canin", "rongeur", etc, qui héritent les propriétés et méthodes des mammifères, en ajoutant des propriétés et méthodes propres et ainsi de suite. Ca ne paraît pas comme ça, mais c'est une manière de procéder très souple et très puissante pour modéliser des données complexes.
    Or, Visual Basic ne donnant pas le moyen de gérer l'héritage, ce n'est pas un vrai langage objet.
D'où le titre ambigu du titre du premier chapitre de ce cours : "VB, un langage (presque) objet". La boucle est ainsi bouclée, tout est dans tout et réciproquement.


2. Le code de la programmation objet
Comment, une fois l'analyse faite, traduire tout cela en Visual Basic ? C'est ce qu'on va voir maintenant.
2.1 Créer une nouvelle classe
Pour disposer dans notre application des objets de classe Jeton et Cases, il nous faut bien évidemment commencer par créer ces deux classes, qui n'existent pas a priori dans Visual Basic (à la différence des classes de contrôles, qui sont là toutes prêtes à être utilisées).
Une classe se définit par un certain nombre de lignes de codes écrites dans un emplacement spécifique appelé Module de Classe.
Donc, de même que nous connaissions les Form et les Modules, nous avons à  présent affaire à un troisième type de bidule dans lequel taper du code. On définit une classe et une seule par module, ce qui implique de manière imparable qu'on devra avoir autant de Modules de classes que de classes d'objet. En l'occurrence, pour notre mini-scrabble, il nous faudrait créer deux modules de classes : un pour les jetons, un pour les cases du plateau.
De plus, le nom de la classe sera celui du module. Pour changer le nom de cette classe (de ce module) il suffit d'aller voir les propriétés de notre module de classe, comme nous changions jusque là par exemple le nom d'un contrôle posé sur une feuille.
Dans notre exemple, nous devrons donc créer un module Jeton et un module Case.
2.2 Définir les propriétés de la classe
Le principe de base est le suivant :
  • à chaque propriété d'une classe doit correspondre une variable du module de classe. C'est cette variable qui stockera la valeur de la propriété pour chaque objet de la classe.
  • la lecture de la propriété (obligatoire) sera gérée par une procédure de type Property Get
  • l'écriture de la propriété (facultative) doit être gérée par une procédure de type Property Let
En théorie, nous devrons nous demander pour chaque propriété si elle devra fonctionner en lecture - écriture, ou en lecture seulement (quitte à asséner une banalité, je rappelle qu'une propriété ne peut pas exister en écriture sans exister en lecture). Autrement dit, est-ce que je donne à mon propre programme le droit de modifier telle ou telle propriété ? Cette question n'est pas d'un grand intérêt pour une petite application autonome, mais elle peut devenir plus épineuse dans le cas d'une application plus grosse, où il serait dommage qu'un bout de code malheureux casse tout sur son passage. Cela dit, 99 fois sur 100, on rend systématiquement les propriétés accessibles tant en écriture qu'en lecture. Donc, sauf raison impérieuse, chaque propriété sera l'occasion d'écrire deux procédures Property.
Donc, je me répète, mais pour chaque propriété, la procédure Property Get, correspondant à la lecture, est obligatoire, la procédure Property Let, correspondant à l'écriture, est facultative (mais très souvent présente quand même !).
Définissons par exemple la propriété "valeur" d'un jeton, en admettant que nous souhaitions que cette propriété soit accessible tant en lecture qu'en écriture. Nous écrirons dans notre module de classe Jeton :
Private Tutu as Integer

Public Property Let Valeur (ByVal Nb As Integer)
  Tutu = Nb
End Property

Public Property Get Valeur() As Integer
Valeur = Tutu
End Property
Fouillouillouille... Essayons de comprendre tout ce charabia.
Pour cela, le mieux est d'imaginer ce qui va se passer lorsqu'on va utiliser un des objets Jeton. Quelque part dans le code principal, on va créer les jetons les uns après les autres (on verra comment faire cela dans un moment). Et puis, pour chaque jeton créé, on va affecter ses propriétés. Supposons que nous en soyons à créer un des jetons "B" du jeu, qui au Scrabble, valent trois points. Si le nom de ce jeton est Caramel(i), car nous définirons vraisemblablement tous les jetons du jeu comme un groupe, nous aurons une ligne qui ressemblera à :
Caramel(i).Valeur = 3
Regardons ce qui va se passer lorsque le programme exécutera cette ligne.
La propriété Valeur étant utilisée en écriture, la procédure Property Let Valeur sera immédiatement exécutée. Celle-ci devant transmettre une information à l'objet (ici, le nombre de points), elle comporte obligatoirement un paramètre en entrée (que j'ai appelé Nb). Ce paramètre Nb, dans mon exemple, vaut 3. La ligne de code suivante place la valeur de Nb dans la variable Tutu, qui vaut donc à présent elle aussi 3. Et le tour est joué : Tutu, variable privée de la classe Jetons, a pour rôle de stocker la propriété Valeur de mes objets de type Jeton.
Dans l'autre sens, ça marche tout aussi bien : si j'utilise au cours de mon programme ma propriété Valeur en lecture, comme par exemple en faisant :
Points = Points + Caramel(i).Valeur
Ce code, qui appelle la propriété Valeur, déclenche illico la procédure Property Get Valeur. Celle-ci va en réalité marcher comme une fonction : elle va consulter combien vaut la variable Tutu, et renvoyer dans Valeur le contenu de cette variable.
Soit dit en passant, vous devez comprendre que ce mode de fonctionnement nous permet d'effectuer, si nous le souhaitons, un contrôle élaboré sur les propriétés de nos objets. Par exemple, les valeurs des lettres, au Scrabble, sont exclusivement 1, 3, 8 et 10. Afin d'être sûr et certain que la propriété Valeur d'un jeton ne pourra jamais être autre chose que cela, je pourrais transformer ma procédure Let en :
Public Property Let Valeur (ByVal Nb As Integer)
If Nb = 1 or Nb = 3 or Nb = 8 or Nb = 10 Then
   Tutu = Nb
EndIf
Et toc ! Toute tentative d'affecter à la Valeur d'un Jeton un autre nombre que 1, 3, 8, ou 10 se solderait par un échec cuisant. On peut ainsi blinder ses objets pour pas cher, et s'assurer qu'ils se comporteront toujours très exactement comme on le voulait.
Pour finir, une excellente nouvelle : c'est qu'il existe dans VB un petit outil qui vous évitera de frapper vous-mêmes ce code fastidieux. Il suffit pour cela d'aller dans le menu Outils - Gestionnaire de compléments, et de cocher la case "VB Class Builder Utility". Cela rendra ensuite disponible, dans ce même menu Outils, la commande Générateur de Classes.
Grâce à celle-ci, vous pouvez définir en deux clics le nom et le type de chacune des propriétés souhaitées pour une classe de votre cru, et VB se chargera lui-même d'écrire toutes les procédures correspondantes. Merci, monsieur VB.
2.3 Définir les méthodes de la classe
Définir les propriétés, c'est bien. Mais définir les méthodes, c'est encore mieux, et, coup de chance, ce n'est pas difficile à faire. En fait, à chaque méthode doit correspondre une procédure qui modifie certaines des propriétés de l'objet
.
Nous voulons par exemple créer la méthode Tirer, qui consiste à sortir un jeton du sac pour l'attribuer à tel ou tel joueur. On suppose qu'on a créé la propriété Emplacement des jetons, propriété numérique qui désigne le numéro du joueur possédant actuellement le jeton (comme au Scrabble il n'y a que quatre joueurs au maximum, on pourrait alors supposer que la valeur 5 désigne un jeton encore dans le sac et la valeur 6 un jeton posé sur le plateau de jeu).
Pour fonctionner, la méthode Tirer aura besoin d'un argument, qui sera le numéro du joueur qui vient de tirer ledit jeton.
Au total, le code donnera :
Public Sub Tirer (Toto As Object, Nb As Byte)
Toto.Emplacement = Nb
End Property
On remarque qu'un argument obligatoire d'une procédure de méthode est l'objet auquel s'applique la méthode. Et si, comme c'est le cas ici, la méthode exige de surcroît des arguments, ces arguments se traduisent par des paramètres supplémentaires de la procédure définissant la méthode.
Toujours est-il qu'une fois ceci fait, dans le corps même de l'application, le tirage du caramel numéro i par le joueur numéro j pourra du coup s'écrire :
Caramel(i).Tirer (j)
Et voilà.
2.4 Créer des instances de classe
Il ne nous reste plus qu'à voir comment le programme peut lui même générer de nouveaux objets (ou en supprimer) en cours de route. En fait, ce n'est là que l'extension aux objets "faits main". d'un procédé déjà largement abordé à propos de objets préfabriqués que sont les contrôles (voir partie 10.
De même que la création d'une variable s'effectue grâce aux mots-clés Dim et As, la création d'un nouvel objet va elle aussi emprunter les mêmes chemins :
Dim Toto As New Jeton
Ici, Toto est le nom (propriété Name) de l'objet, et Jeton correspond obligatoirement au nom d'un module de classe (ou alors, il s'agit d'une classe de contrôles, comme Form, CommandButton, etc.)
Si l'on voulait créer dynamiquement les 95 jetons du jeu, le plus simple serait sans doute de dire :
For i = 0 to 94
   Dim Caramel(i) As New Jeton
Next i
Et le tour serait joué.
Quant à la destruction d'un objet, elle est d'une simplicité biblique ::
Set Toto = Nothing
Et l'objet disparaît pour toujours dans le néant intersidéral. Comme disent les comiques, "poussière, tu retourneras poussière..."
Avant d'en terminer, j'ajouterais juste qu'il est également possible de stipuler les événements auxquels devront réagit les objets de telle ou telle classe. Mais là, on commence à aller un peu loin pour les modestes objectifs de ce cours. On s'en tiendra donc sagement là... pour le moment en tout cas.
Ce cours de Visual Basic est maintenant terminé. Mais si vous avez aimé cela et que vous comptez vous servir de ce que vous avez appris, vous aurez compris depuis longtemps que vos ennuis, eux, ne font que commencer...