|
Rappel : Ce cours est enseigné dans la |
||||||||||||||
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 :
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.
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.
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.
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.
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 :
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...
|