Access : Modules de classesDate de publication : Work in Progress... , Date de mise à jour : 14 février 2006
Par
Michel Blavin (autres articles) niveau : solid snake Ce cours se présente comme un tutoriel sur les modules de classes et la Programmation Orientée Objets (POO) avec Visual Basic pour Application (VBA). Après un bref rappel sur les objets, nous mettrons en application les principes et fonctionnalités des Modules de classes. Programmation modulaire VBA, un Langage orienté objet Pourquoi utiliser les modules de classes ? A qui s'adresse ce document ? Où trouver la dernière version de ce cours ? Conventions Remerciements 1. Définitions et rappels. 1.1. Les bienfaits l'abstration. 1.2. Les objets, les classes et leurs différences 1.3. Qu'esce qu'une interface ? 1.4. Rappels sur les objets, les pointeurs et leurs durée de vie. 2. Première classe 2.1. Interface 2.2. Propriétés 2.2.1. Implémentation des propriétés utilisant des variables publiques 2.2.2. Implémentation des propriétés utilisant les Procédures Property 2.3. Méthodes 2.4. Les événements 2.5. La propriété Parent 3. Collections 3.1. L'objet Collection de VBA 3.2. Collections personnalisées 3.2.1. Généralités 3.2.2. Interface 3.2.3. Implémentation 3.2.3. Que manque-t-il aux collections personnalisées ? 4. Les modules des formulaires et des Etats Les liens qui ont la classe Programmation modulaire
De nos jours, l'un des plus grands défits pour un developpeur est de ne pas devoir réinventer la
roue à chaque nouvelle application. C'est pourquoi le developpeur moderne est un
developpeur de modules. On developpe des modules autonomes et plus ou moins génériques
que l'on assemble pour former une application. La modularité et la réutilisation du code
est un des principes de bases de la POO. Il est plus facile de débugger des modules de
petite tailles qu'une grosse application. En dehors de la programmation objet il est aussi très pratique d'utiliser des bibliothèques de code, qui, une fois que l'on a résolut le problème des liens cassé, devient indispensable. Cela permet d'obtenir du code de meilleurs qualité, plus facile à maintenir, plus robuste. VBA, un Langage orienté objet
Les dévelopeurs de langage orienté objet, égarés sur cette page,
viennent de s'étrangler en lisant le titre de ce paragraphe. Faut bien s'amuser ;o). Disons plutôt, par égard pour les puristes, que VBA n'est pas un langage orienté objet mais qu'il permet de faire de la Programmation Orientée Objet (POO) en VBA, malgré de nombreuses limitations (souvent frustrantes) : Certaines des caractéristiques de la POO, comme l'héritage ou le polymorphisme , ne sont pas présents. Il est impossible de créer des classes dérivées. La surcharge de méthode est impossible, puisqu'il est impossible en VB d'utiliser le meme nom pour plusieurs méthodes dans une même classe. Nous verrons le cas particulier des procédures Property D'autres part, les classes ne peuvent pas être directement instenciée si elle se trouve dans un complément (*.mda). C'est à mon avis la plus grosse lacune et surtout la plus absurde, étant donné que la réutilisation du code est un des chevaux de bataille de la POO. Il est cependant possible d'utiliser cette classe si le code qui instancie l'objet se trouve dans un modules de code simple dans le même mda que la classe. Pourquoi utiliser les modules de classes ?
La programmation procédurale a fait ses preuves et vous vous demandez peut-être pourquoi
changer ? Les raisons sont multiples : La création d'objets personnalisés permet de mieux organiser les développements en regroupant dans un même objet les données ainsi que les méthodes pour manipuler ses données. Les classes sont des sous ensembles cohérents de vos applications. On commence par developper les classes dont on aura besoin (et qui pour certaines seront réutilisables ultérieurement) et ensuite on implémente la logique liée à l'application. Cela vous oblige à réfléchir à l'application que vous aller développer avant de commencer à coder, ce qui est rarement une mauvaise idée. ;) Le découpage du code en classes permet d'accélérer le développement d'application complexe et facilite la maintenance, qui représente 80% de la vie d'un logiciel, en est grandement facilité. Le temps que vous passez au départ à réflechir c'est du temps gagné pour plus tard. Si vous avez eu à vous replonger dans un code écrit l'année dernière et que vous aviez du mal à comprendre, vous savez de quoi je parle. Et malgré tout cela, il est difficile de prendre conscience du gain réel l'utilisation des modules de classes (à part pour frimer à coté de la machine à café), mais je peux vous dire que maintenant que j'y ai gouté, je ne developperai plus aucune application sans les utiliser. A qui s'adresse ce document ?
Cet article s'adresse à tous les developpeurs Visual Basic pour Application, en
particulier le développeurs Access.
Cependant ce cours ne s'adresse pas à des débutants : vous devez connaitre les bases
de la programmation, des connaissances POO ne sont pas nécessaire mais seront un plus.
Où trouver la dernière version de ce cours ?
Ce document a été écrit pour être publié sur
developpez.com
la dernière version est disponnible à l'adresse suivante :
sinarf.developpez.com/access/vbaclass.
Une version pdf de l'
introduction de aux modules de classes est disponible.
Conventions
Je suis un adepte de la normalisation des noms, je trouve que cela simplifie
grandement la programmation surtout quand un projet commence à grossir. Le nom des
classes est un peu une exception : aucun préfixe. Les noms des objets de leurs
propriétés
et de leur méthodes utilisent des noms explicites. Par soucis de lisibilité, j'utilise une
majuscule pour séparer les differents mots par exemple une propriété contenant le nom d'un
fichier pourra être appeler NomFichier.
Remarque : Pour ne pas allourdir les blocs de code présent dans cette article nous n'avons pas implémenté de gestion d'erreur, mais gardez à l'esprit que les developpeurs sérieux implémentent systématiquement une gestion d'erreur. Alors tordons le coup aux rumeurs, comme quoi les developpeurs VB, qui plus est VBA, ne sont pas des développeurs sérieux. ;) Remerciements
Merci à toute l'équipe de developpez.com qui m'a tant apporté. Je tiens à remercier
Maxence Hubiche, pour m'avoir
tenu la main, encouragé, harcellement, flatté, flagorné, suggéré et pour son infinie
patience, car je dois avouer que cet article est en gestation depuis des temps
immémoriaux. Je remercie tous les correcteurs :
Christophe WARIN,
Frank, Etienne Bar,
Jean-Marc RABILLOUD, et surement
plein d'autre que j'ai oublié, ingrat que je suis.
1. Définitions et rappels.
Je donne ici quelques rappels sur les notions de base de la Programmation Orienté Objet,
il ne s'agit pas de faire un cours complet sur le sujet,
si vous cherchez des précisions à ce sujet je vous donne quelques
liens en fins d'article mais je vous conseil en particulier
Pensez en java
de Bruce Eckel.
1.1. Les bienfaits l'abstration.
En encapsulant votre code dans des objets vous masquez l'implémentation. C'est à dire
que les utilisateurs de vos classes n'ont
pas besoin d'en connaitre l'implémentation. Ces objets sont des boites noires,
une fois developpés, il nous suffit de savoir ce qui entre ou sort de la boite.
Par exemple, vous utilisez souvent des objets Recordset, peut-être que par curiosité
vous aimeriez connaitre l'implémentation utilisée, mais reconnaissez
que cela n'a aucune utilité pour vos developpements. Tout ce dont vous avez besoin c'est de savoir comment utiliser cet objet Les classes peuvent aussi permettre de masquer des procéssus complexes derrière une interface simplifié. Une des utilisatation peut être d'encapsulé les API pour les rendre plus convivial. 1.2. Les objets, les classes et leurs différences
Les classes sont des types évolués. Contrairement
aux types simples (entiers, flotant, chaine de caractères ...) qui ne contiennent
que des données,
les objets contiennent à la fois les données et les méthodes qui permettent de
manipuler ces données. Il est facile de faire une confusion entre objets et classes, pour faire une analogie culinaire, nous dirons que si les objets sont des gateaux, les classes sont les recettes qui permettent de faire ces gâteaux. La recette n'est pas un gâteaux mais elle permet de faire autant de gâteaux que l'on désire. Dans cette image les ingrédients seraient les propriétés et les manipulations (mélanger, cuire) les méthodes. Quand on crée un objet à partir d'une classe, on dit que l'on instancie un objet. Les differents objets instanciés à partir d'une classe, sont appelés instances. Les objets ont des propriétés, qui contiennent les données, et des méthodes qui permettent de les manipuler. Dans certains langages, les propriétés sont appelées champs. Quant aux méthodes ce ne sont que des procédures ou des fonctions qui peuvent retourner une valeur et recevoir des paramètres. 1.3. Qu'esce qu'une interface ?
Nous ne parlons pas ici d'interface graphique. L'interface est la partie visible d'une
classe.
Le créateur de la classe n'est pas forcement l'utilisateur de celle-ci, il est
donc indispensable de définir une interface et de s'y tenir. Le créateur de la
classe définit l'interface et met en place l'implémentation. Peu importe que
l'implémentation change du moment que l'interface ne change pas. En gros, une
mise à jour de la classe doit rester compatible avec le code existant des
utilisateurs de cette classe. Il est donc très simple d'ajouter des propriétés
et des méthodes mais
pratiquement impossible d'en supprimer. Dans la réalité, cela se
produit, dans ce cas on ne supprime pas tout de suite la méthode, propriété ou même
la classe mais on dit que cet élément est "deprecated" pour indiquer qu'il ne faut plus
l'utiliser qu'il sera supprimés dans les versions futures. En java, le compilateur émet
un avertissement indiquant
qu'il utilise une méthode deprecated, il n'y a pas, à ma connaissance, de processus
similaire en VB. Dans certains des langages orientés objet, comme Java, il existe un mot clé interface, il n'existe pas en VBA. Par contre, il est possible créer une interface en créant une classe ne contenant que les prototypes des méthodes et propriétés. 1.4. Rappels sur les objets, les pointeurs et leurs durée de vie.
Nous allons faire ici un petit rappel sur les objets. Lorsque vous déclarez un
objet vous créez un pointeur vers cet objet. Plusieurs pointeurs peuvent pointer
sur le même objet. Lorsque vous utilisez le code suivant :
Vous ne faites pas une copie de l'objet comme c'est le cas avec les types simples
mais vous créez un deuxième pointeur vers votre objet. Lorsque vous passez
un objet en paramètre d'une fonction ou procédure vous ne passez en fait
qu'un pointeur vers l'objet. On dit
que les objets sont passés par référence (c'est aussi le cas des types simple dans
Visual Basic, bien que ces derniers puissent être passé
en tant que valeur).
La création des objets :
Maintenant que nous savons quand nos objets sont effectivement créés, voyons comment
se passe la destruction des objets :
Un objet est détruit quand il n'existe plus de pointeur lui faisant référence. Cette définition est simple mais l'on a souvent l'impression que lorsque le code suivant est executé :
l'objet est détruit, alors que ce n'est vrai que si objA est le dernier pointeur
vers l'objet. Il est très important de prendre la précautions de bien
"nettoyer" derrière soit et de ne pas laisser traîner de pointeurs. Comme nous le verrons plus loin nous pouvons executer du code lors de la destruction d'un objet encore faut-il être certains que ce code s'executera. 2. Première classe
Après ces brefs rappels théoriques, ce n'est pas sans une certaine émotion que
nous allons voir en pratique comment créer notre première classe.
2.1. Interface
Commençons par définir l'interface de la classe que nous allons créer.
Celle-ci sera simple afin de mettre en relief le fonctionnement des modules
de classes sans risquer de s'égarer avec une implémentation obscure. Notre classe aura 4 propriétés :
et une méthode :
2.2. Propriétés
Puisque notre classe n'existe pas encore, nous devons commencer par créer un module
de classe. Pour cela, dans l'éditeur VB, cliquer sur Insertion/Module de classe.
Enregistrons le module. Ce nom ne doit pas être choisi au hasard car il s'agit du
nom du type que vous allez créer. Notre classe s'appelle Person.
2.2.1. Implémentation des propriétés utilisant des variables publiques
La méthode la plus simple pour implémenter des propriétés est d'utiliser des
variables publiques. Pour implémenter une propriété il nous suffit de déclarer
des variables publiques dans notre module de classe.
Comme ceci :
Puisque l'on ne peut pas implémenter une propriété en lecture seule avec
cette méthode nous laisserons la propriété NomComplet pour le moment
Nous pouvons tester grâce au code suivant, dans un module de code classique.
Vous devriez obtenir le texte suivant sur la sortie de debug.
Cette façon de faire est vraiment très simple mais elle a plusieurs défauts :
En fait cette méthode viole ce que l'on appelle l'encapsulation des données qui veux que
les données ne devrait etre accessible qu'au travers de méthodes.
C'est une mauvaise implémentation, nous la réserverons à la phase de prototypage.
2.2.2. Implémentation des propriétés utilisant les Procédures Property
Les procédures Property permettent de donner un accès complet ou
limité aux propriétés d'un
objet. Nous accèderons à ces données à travers des méthodes.
Il existe 3 procédures Property :
En séparant l'écriture de la lecture, il devient très simple d'implémenter une
propriété en lecture seule. Pour cela, il suffira de créer
une procédure Get mais pas la procédure Set ou Let
correspondante. Et
inversement, pour une propriété en écriture seule (cas plus rare mais utile,
par exemple le cas de la propriété UID du type User du modèle de sécurité d'Access).
Ce que nous allons faire maintenant est très important car nous allons modifier l'implémentation de notre classe et ceci sans modifier son interface. Nous allons toujours utiliser des variables pour stocker les valeurs de nos propriétés mais nous allons utiliser des variables privées. Pour cela, nous allons transformer les déclarations des variables publiques en variables privées, ainsi seules les méthodes de la classe y auront accès.
Maintenant nous devons mettre en place les procédures Property. Pour
commencer nous n'allons mettre
aucun contrôle, aucune validation, nous allons juste transformer les variables
publiques en propriétés. Vous remarquerez que le type de retour de la procédure
Get est le type du paramètre de la procédure Let (ou Set) correspondante, c'est
une obligation. Vous remarquerez aussi que ces procédures portent exactement le
même nom comme nous l'avons vu plus haut, c'est un cas unique en VB.
Si vous utilisez la procédure de test précédente vous constaterez que le
fonctionnement des objets n'a pas changé alors que l'implémentation a
évolué. On voit ici comment on peut faire évoluer une partie de notre
code sans compromettre le fonctionnement de l'application.
Nous allons, maintenant, ajouter les contrôles suivants :
Ce qui nous donnera le code suivant :
Il ne nous reste plus qu'à implémenter le propriété NomComplet en lecture seule.
Lorsque l'on fait référence à une propriété (ou une méthode) publique de la
classe, nous utilisons le mot clé Me. Ce mot clé
fait référence à l'instance en cours, il ne peut donc être utilisé
que dans des modules de classe. En pratique, lors de l'instanciation d'un objet la
propriété privée Me est automatiquement créée, il s'agit d'un pointeur
vers l'objet courant. S'il est utilisable dans les modules de formulaires et de rapports, c'est que ceux-ci, comme nous le verrons plus loin, sont des modules de classes. 2.3. Méthodes
Comme dans un module simple les méthodes peuvent, soit ne pas renvoyer de valeur
(procédure Sub), soit comme dans le cas de la méthode Age que nous
allons implémenter, renvoyer une valeur (une fonction).
Nous avons maintenant une classe totalement fonctionnelle.
2.4. Les événements
En plus des propriétés et des méthodes, les modules de classes savent gérer des
événements.
Les modules de classes simples en implémentent 2, l'événement Initialize et
l'événement Terminate.
L'événement Initialize se produit lors de la création de l'objet et
l'événement Terminate se produit lors de sa destruction.
Il est indispensable de savoir quand un objet est créé et quand il est détruit car si,
à première vue, cela à l'air évident mais si les pointeurs ne sont pas bien libéré
il se peut qu'un objet ne soit pas détruit et donc que le code de
l'événement Terminate ne soit jamais exécuté.
Pour prendre conscience du moment exact ou ces événements se produisent, nous allons ajouter le code suivant à notre classe :
Ainsi, la fenêtre de debug nous informera à chaque fois que l'un de ces événements
se produira. Pendant la phase d'apprentissage, je vous conseille fortement
d'implémenter systématiquement ce genre de code, cela vous évitera probablement quelques
heures de debug.
Maintenant notre procédure de test donne :
Vous pouvez aussi ajouter à vos classes des événements personnalisés (à partir de
Office 2000 seulement).
Nous allons ajouter un événement que l'on appelera Completed qui se produira
lorsque les propriétés Nom, Prenom et DateNaissance
auront été renseignés.
Commençons par ajouter la déclaration de l'événement :
Pour qu'un événement se produise, il faut utiliser la méthode RaiseEvent.
Dans notre cas, nous allons créer une méthode privée IsCompleted dans notre module de classe, qui va vérifier si toutes les propriétés concernées sont renseignées.
C'est 3 variables seront initialisées à False dans l'événement
Initialize et nous allons transformer les procédures Let
pour qu'elles appellent la procédure IsCompleted qui provoquera l'événement
Completed si les trois propriétés on été renseignées.
Comme me l'a fait remarquer un de mes relecteurs cette procédure aurait pu être
publique ce qui aurait permis aux l'utilisateurs de la classe d'interroger cette
méthode plutot que de lever un évenement,
mais cela nous permet de implémenter une méthode privée.
Maintenant il ne nous reste plus qu'à tester. Créons un nouveau formulaire
et ajoutons les blocs de code suivant, pour la gestion de l'événement.
Remarquez l'usage d'un nouveau mot clé WithEvents, qui, comme son nom l'indique
signale lors de la déclaration de l'objet que nous allons utiliser tout ou partie des
événements de l'objet instancié. La méthode exécutée lorsque l'événement se produit a un nom
particulier composé du nom de l'objet concerné et du nom de l'événement, séparés par un
underscore ("_").
Comme vous le voyez, l'événement est déclenché par l'objet Person,
par contre, l'action à effectuer lorsque cet événement se produit n'est pas
contenu dans la classe Person mais dans le formulaire qui utilise la classe.
C'est l'utilisateur de l'objet qui décide ce qu'il veut faire lorsque cet événement
ce produit.
2.5. La propriété Parent
Lorsque vous créez une hiérarchie entre vos objets, il peut être interessant de pouvoir remonter la
hiérarchie, c'est le rôle de la propriété Parent. Vous pouvez d'ailleurs l'appeler
autrement mais ce serait juste totalement stupide car cela rendrait votre code un peu plus obscur.
Vous remarquerez que la propriété Parent ne peut être affectée qu'une seule fois (write once).
Pour renseigner cette propriété, il nous faudra, dans l'objet Parent, ajouter la ligne de code suivante :
3. Collections
Manipuler un objet c'est bien, mais en manipuler plusieurs, c'est encore mieux. Grâce aux
Collections nous allons pouvoir agir sur un groupe d'objets.
3.1. L'objet Collection de VBA
Dans certains cas, on peut utiliser tout simplement utiliser l'objet Collection,
mais il accepte tous les objets. Vous risquez d'utiliser dans votre code des
méthodes ou des propriétés absentes de certains objets, ce qui provoquera des
erreurs d'exécution pas belles (Ugly runtime error;)). Autre avantage de taille, nous pouvons choisir d'implémenter tout ou partie des méthode de l'objet Collection, voir même, comme nous allons le voir, d'en ajouter. 3.2. Collections personnalisées3.2.1. Généralités
Par convention, les collections portent le même nom que les objets qu'elles
contiennent suivit d'un "s".
Dans notre cas, Nous appellerons notre collection Persons. Une collection personnalisée implemente les méthodes de l'objet Collection, qui sont les suivantes :
Ainsi que la propriété :
3.2.2. Interface
Pour créer une collection personnalisée, nous allons utiliser un objet
Collection que nous allons encapsuler dans une classe.
Commençons par
créer un nouveau module de classe et enregistrons le sous le nom de
Persons. Nous allons déclarer un objet Collection
qui sera privé à notre classe.
Dans l'événement Initialize nous instancions notre objet collection
pour qu'il soit disponible dès que notre collection Persons sera
instanciée et nous libérons notre variable dans l'événement Terminate.
Ajoutons maintenant les 4 méthodes que nous avons citées au paragraphe précédent.
Pour démontrer la possibilité d'ajouter des méthodes nous allons créer une méthode CreatePerson qui, comme son nom l'indique, permet de créer un objet Person (et de l'ajouter à la collection). Elle reçoit en paramètre le nom, le prénom et la date de naissance. Elle renvoie bien évidement un objet Person (en fait, comme vous l'avez compris, une référence vers le nouvel objet créé). 3.2.3. Implémentation
Nous allons donc assez simplement implémenter nos 4 méthodes de base en encapsulant de
la manière la plus simple les méthodes de l'objet Collection. Commençons par la méthode Add
qui est la plus complexe.
Pas de problème particulier avec ces méthodes, on remarquera juste que
là où l'objet Collection utilise le type Object, nous utilisons le
type Person ce qui empêchera d'insérer tout autre type d'objet
dans la collection. Vous remarquerez aussi que dans la méthode Add on renseigne automatiquement la propriété Parent de l'objet fils. Le paramètre Index des méthodes Item et Remove peuvent être de deux types differents.
Nous allons maintenant ajouter une méthode CreatePerson à la collection
qui permet d'instancier un objet Person sans avoir à créer de pointeur
vers un objet personne. Cela nous permet comme vous allez le voir dans le code qui
suit d'instancier et d'insérer un objet dans la collection.
Maintenant, testons notre collection. Voici
un petit code qui va nous permettre de tester les méthodes de notre classe. (Observez
la fenêtre de debug pour voir à quel moment les objets sont créés et
supprimés.)
Ce qui nous donne :
3.2.3. Que manque-t-il aux collections personnalisées ?
Du fait que notre objet Collection est encapsulé, il perd certaines
fonctionnalités comme
la propriété par defaut qui n'existe pas.
Il existe un "workaround" pour simuler la propriété par défaut mais la solution
ne me plaisant pas, je ne vous la montrerai pas, na ! (Pour les tordus, vous
la trouverez dans les liens, en cherchant bien...)
L'autre manque est celui du for each qui ne fonctionne pas, la solution consiste à utiliser une boucle for classique. Attention, si vous supprimer des éléments dans votre boucle vous risquez d'avoir des surprises.
4. Les modules des formulaires et des Etats
Les modules de formulaires et d'états sont, eux aussi, des modules de classe.
Ils n'ont pas d'événement Initialize et Terminate mais ils en ont
bien d'autres que vous utilisez d'ailleurs tous les jours.
Les modules attachés aux formulaires étant des modules de classe, vous pouvez leur
ajouter les propriétés, méthodes et événements.
Rappelez-vous juste que, pour les utiliser, vous devez avoir un objet instancié, Le formulaire doit donc être ouvert même s'il est caché. Un petit cas pratique, rien de tel pour illustré notre propos : Il s'agit d'un formulaire pop-up contenant un controle calendrier il acceptera une propriété personnalisée contenant le nom du contrôle dans lequel on retournera la date qui sera sélectionner (par un double clic dans notre cas) dans le formulaire calendrier. Commençons par créer un formulaire contenant le contrôle calendrier d'un bouton Annuler et enregistrer le sous le nom "frmDateChooser" ce qui nous donnera ceci : ![]() Maintenant ajoutons le code gérant la propriété personnalisée.
Comme vous voyez cette propriété est en écriture seule car la lecture de la propriété est
inutile dans le contexte.
Maintenant, il faut que nous renvoyons la valeur que nous avons sélectionnée au contrôle de contenu dans la propriété.
Puis le code pour le bouton Annuler :
Pour que le formulaire s'ouvre à la date d'aujourd'hui, ajoutons le code nécessaire dans
l'événement Open du formulaire.
Pour tester créons un nouveau formulaire et mettons une Textbox dessus ajoutons
dans l'événement DblClick le code suivant.
Enregistrez votre formulaire et faites un test. Le DateChooser est disponible en téléchargement Les liens qui ont la classe
Ce document issu de
www.developpez.com.
et écrit par Michel Blavin
est soumis à la licence
GNU FDL
traduit en français
ici
Permission vous est donnée de distribuer, modifier des copies de cette page tant que cette note apparaît clairement. |