Les modules de classe

Les modules de classe sont une extension extrêmement puissante des simples modules. En effet, nous approchons ainsi quelque peu de la POO (Programmation orientée Objet), telle que celle pratiquée avec C++, C#, Java, et autre Eiffel. Visual Basic est la la fois plus simple et moins puissant que ces langages, mais attention : En effet, même si vous avez déjà quelque peu bidouillé avec VBA, la création, l'utilisation et la rationnalisation des modules de classe demande bien plus de pratique, et si vous n'êtes pas un programmeur véritablement aguerri, et en tout cas avec une excellente connaissance des concepts tels que Procédures paramétrées, variables typées, variables globales, visibilités des fonctions et des procédures, il y a bien des chances pour que vous considériez cet article comme du chinois, ou tout du moins ne compreniez pas vraiment l'utilité de ce concept de classe. Accrochez-vous : C'est parti !

Avant de commencer l'étude des classes à proprement parler, nous allons d'abord créer une variable d'un type particulier, qui contient des sous-variables.

Allez dans un module normal (pas un module de classe), et écrivez directement :

Type Coordonnee
  Nom As String
  Prenom As String
  Age As Integer
End Type

On ne peut pas mettre ce code dans une procédure. Il faut que ce soit en en-tête de Module. Il s'agit d'une variable Coordonnee compoosée de 3 sous variables.

Créez maintenant juste plus bas un module, comme ceci :

Sub Remplissage()
  Dim MonCopain As Coordonnee

  MonCopain.Nom = "Dupont"
  MonCopain.Prenom = "Jean"
  MonCopain.Age = 40

  MsgBox MonCopain.Prenom
End Sub

Voilà comment on peut ainsi créer des variables composées : C'est déjà une sorte d'objet. Passons maintenant aux choses sérieuses !

Nous allons d'abord créer une Classe, grace à l'insertion d'un nouveau module de classe :

Imaginons le concept d'un simple bistrot

Insertion/Module de classe (et pas nouveau module, sinon, ce n'est pas un module de classe)

Le sauvegarder sous Bistrot

Dans ce module, installer le code (Minable, j'en conviens) suivant :

Public NombreClient

Sauvegardez.

Voici notre première classe réduite à sa plus simple expression : En effet : Elle ne comporte ni procédure ni fonction, ni rien d'autre qu'une simple variable : NombreLitre

Nous allons la tester. La manière la plus simple consiste à créer un module (normal cette fois), et à y insérer le code suivant : Il s'agit de créer une variable de type Bistrot (As New Bistrot). C'est comme une variable normale, mais c'est cette fois une variable dite OBJET, c'est à dire qu'elle sera d'une structure beaucoup plus complexe qu'un type de données String, ou Single qui sont considérées comme des variables de type SCALAIRES (Simples)

Sub TesterLaClasse()
  Dim CafeDeLaGare As New Bistrot
End Sub

Exécutez cette procédure TesterLaClasse : Il ne se passe rien, mais il n'y a pas d'erreur.

Nous allons maintenant placer la valeur 5 dans cette variable NombreLitre, et l'afficher à l'aide d'un MsgBox. Nous pouvons nous permettre cela uniquement grâce au fait d'avoir déclaré NombreLitre en Public, sinon, une erreur de compilation aurait eu lieu :

Sub TesterLaClasse()
  Dim CafeDeLaGare As New Bistrot
  CafeDeLaGare.NombreClient = 5
  MsgBox CafeDeLaGare.NombreClient
End Sub

Plusieurs instances d'une classe

Ouvrons un bistrot : Le Café de la Gare

De cette même façon, il est donc possible de déclarer 2 INSTANCES de Bistrot, chacune étant indépendante de l'autre, comme ceci :

Sub TesterLaClasse()
  Dim CafeDeLaGare As New Bistrot
  Dim ChezGaston As New Bistrot
  CafeDeLaGare.NombreClient = 5
  ChezGaston.NombreClient = 10
  MsgBox CafeDeLaGare.NombreClient' 5
  MsgBox ChezGaston.NombreClient' 10
End Sub

Méthodes simples

Cuisinons

Maintenant, il est possible de créer des Subs directement dans la Classe. Modifiez votre module de classe Bistrot comme ceci :

Public Sub Cuisiner()
  MsgBox "Je cuisine"
End Sub

Et dans votre autre module de test TesterLaClasse, faites un appel :

Sub TesterLaClasse()
  Dim CafeDeLaGare As New Bistrot
  CafeDeLaGare.Cuisiner ' Affichera "Je cuisine"
End Sub

Nous avons pu appeler ce sub grâce au fait qu'il est déclaré comme Public, sinon, une erreur de compilation serait survenue. Un Sub dans un module de classe est appelé alors une METHODE.

Méthodes paramétrées

Cuisinons un bon repas

Il est possible de paramétrer la méthode comme ceci :

Modifications à faire dans le module de classe Bistrot (Appelons désormais ce module de classe simplement "La classe bistrot") :

Public Sub Cuisiner(QuelRepas)
    MsgBox "Je cuisine " & QuelRepas
End Sub

Modifications à faire dans TesterLaClasse :

Sub TesterLaClasse()
    Dim CafeDeLaGare As New Bistrot
    CafeDeLaGare.Cuisiner "Un steack"
    CafeDeLaGare.Cuisiner "Une omelette"
End Sub

Constatez l'absence de parenthèses. Vous allez donc maintenant pouvoir appeler la méthode Cuisiner de la classe Bistrot avec un paramètre.

Fonctions (Sortes de méthodes avec renvoi de valeurs)

Mettons les tables en place

On peut également placer des fonctions dans les classes. Ajoutez la fonction PreparerLesTables() dans la classe Bistrot :

Public Function PreparerLesTables()
  PreparerLesTables = "Les tables sont préparées"
End Function

Cette fonction s'appelle comme une fonction normale. A nouveau, pour qu'elle puisse s'appeler depuis ailleurs que dans un MEMBRE de la classe elle-même, il est nécessaire de la précéder de Public. Un membre est une fonction, une procédure, une variable ou une propriété de la classe (Nous n'avons pas encore abordé les propriétés)

Modifiez TesterLaClasse pour appeler cette nouvelle fonction :

Sub TesterLaClasse()
  Dim CafeDeLaGare As New Bistrot
  MsgBox CafeDeLaGare.PreparerLesTables()
End Sub

Constatez l'apparition des parrenthèses vides indispensables en cas d'appel à une fonction, par opposition aux procédures Sub. Constatez également l'obligation du MsgBox. En effet, on DOIT récupérer la valeur "Les tables sont préparées" :

CafeDeLaGare.PreparerLesTables()

donnerait une erreur. On aurait pu également stocker cette valeur "Les tables sont préparées" dans une variable, comme ceci :

Dim EtatDesTables
EtatDesTables =
CafeDeLaGare.PreparerLesTables()

Fonctions paramétrées

Mettons les tables en place plus consciencieusement !

Imaginons la même fonction mais avec 3 paramètres : Le genre de la nappe, sa couleur, et s'il faut mettre des bougies. Modifiez Bistrot.PreparerLesTables comme suit :

Public Function PreparerLesTables(GenreDeNappe,   CouleurNappe, MettreBougie)
  Dim JePrepare
  JePrepare = "Je met des nappes en " & GenreDeNappe
  JePrepare = JePrepare & " " & CouleurNappe & "s "
  If MettreBougie = True Then
    JePrepare = JePrepare & "avec une bougie"
  End If

  PreparerLesTables = JePrepare
End Function

Et appelez maintenant cette nouvelle fonction comme ceci :

Sub TesterLaClasse()
  Dim CafeDeLaGare As New Bistrot
  MsgBox CafeDeLaGare.PreparerLesTables("papier", "rouge",   True)
End Sub

Il va afficher :

Je met des nappes en papier rouges avec une bougie

Propriétés

Property Let

Mettons de la musique dans le café de la gare

Les propriétés permettent une certaine syntaxe d'appel aux membres de Bistrot.

Corrigez la classe Bistrot comme suit :

Property Let Musique(QuelGenre)
End Property

Les Property sont par défaut Publiques : On pourra donc les appeler sans problème depuis l'extérieur. QuelGenre est un paramètre OBLIGATOIRE. On ne peut PAS créer une property sans paramètre. Nous verrons plus tard comment mettre plusieurs paramètres tels que Property Let Musique(QuelGenre, QuelVolume) par exemple.

Le Let veut dire qu'il s'agit d'une propriété qui va ACCUEILLIR un paramètre lors de l'appel. Ce qui veut dire que cette propriété sera dite EN ECRITURE, puisqu'on va pouvoir ECRIRE dedans, comme ceci :

Sub TesterLaClasse()
  Dim CafeDeLaGare As New Bistrot
  CafeDeLaGare.Musique = "Jazz"
End Sub

Voilà. En exécutant ce code, il ne va pas se passer grand chose, c'est le moins qu'on puisse dire : En effet, Il se contente d'envoyer "Jazz" dans le paramètre QuelGenre.

Le but étant bien évidemment de traiter ce paramètre, en le gardant en mémoire, par exemple en mémorisant le résultat dans une variable locale comme ceci :

Dim GenreMusique
Property Let Musique(QuelGenre)
  GenreMusique = QuelGenre
End Property

Peut-on utiliser une Property Let avec plus d'un paramètre ?

On peut effectivement se poser la question puisque l'appel à une Property Let à comme forme NomDeLaClasse.NomDeLaPropertyLet = QuelqueChose

Et bien oui, on peut, et l'appel se fera alors comme ceci :

Prenons l'exemple d'une Property Let qui allume le bistrot (Un certain nombre de lustres, de chandelles, le tout à une certaine puissance) :

Property Let Allumer(Lustre, chandelle, Puissance)
   MsgBox "Lustre : " & Lustre
   MsgBox "Chandelle : " & chandelle
   MsgBox "Puissance : " & Puissance
End Property

Qui s'appelle de la manière suivante :

Sub MettreLaLumiere()
   Dim CafeDesAmis As New Bistrot
   CafeDesAmis.Allumer(2, 10) = "60 Watts"
End Sub

Différence entre variable et Property

Mais alors, on est en droit de se demander pourquoi créer une propriété, et pas simplement déclarer GenreMusique comme étant une variable Public, et directement injecter le genre de musique dedans, comme ceci :

CafeDeLaGare.GenreMusique = "Jazz"

Ce qui reviendrait effectivement au même. Mais en fait, la propriété étant carrément tout un bout de code visual Basic, on peut se permettre de faire beaucoup plus que d'attribuer une simple valeur à une variable, comme par exemple, définir automatiquement le volume sonore suivant le genre de musique choisie, ou simplement s'il y a de la musique. Changez la classe Bistrot comme ceci :

Dim AvecMusique
Dim GenreMusical
Dim VolumeMusical

Property Let Musique(QuelGenre)
  AvecMusique = True
  Select Case QuelGenre
    Case "Jazz":
      GenreMusical = "Détendu"
      VolumeMusical = 4
    Case "Hard-Rock":
      GenreMusical = "Violent"
      VolumeMusical = 10
    Case "Classique"
      GenreMusical = "Calme"
      VolumeMusical = 1
    Case "Silence"
      GenreMusique = ""
      AvecMusique = False
      VolumeMusical = 0
End Property

Voyez comme le simple envoi d'une valeur à une propriété peut engendrer des grands changements.

Si vous réfléchissez un peu, vous constaterez qu'on aurait pu faire la même chose avec une procédure Musique (Sub), comme ceci :

Dim AvecMusique
Dim GenreMusique
Dim VolumeMusical

Public Sub Musique(QuelGenre)
  AvecMusique = True
  Select Case QuelGenre
    Case "Jazz":
      GenreMusical = "Détendu"
      VolumeMusical = 4
    ' Etc, la même chose qu'avec le property
End Sub

L'appel à cette procédure depuis TesterLaClasse aurait alors été :

CafeDeLaGare.Musique "Jazz"

Au lieu de

CafeDeLaGare.GenreMusique = "Jazz"

On aurait pu même le transformer en Function, mais dans ce cas, il aurait fallu renvoyer le résultat dans une variable du style :

Resultat = CafeDeLaGare.Musique ("Jazz")

Enfin bref, voyez comme les Property, Sub et Function peuvent s'interchanger. Donc, finalement, pourquoi utiliser les Property ?

Simplement parce qu'elles permettent de LIRE et d'ECRIRE avec le même nom ! Je m'explique : Dans la Classe bistrot, créez une Property Let toute simple :

Dim GenreMusique
Property Let Musique(QuelGenre)
  GenreMusique = QuelGenre
End Property

C'est la possibilité pour un appel externe d'IMPOSER le style musical :

Dim GenreMusique
Sub TesterLaClasse()
  Dim CafeDeLaGare As New Bistrot
  CafeDeLaGare.Musique = "Jazz"
End Sub

Maintenant, dans la classe Bistrot, ajoutez la MEME propriété Musique, mais avec GET, pour que finalement votre Classe ressemble à ceci :

Dim GenreMusique

Property Let Musique(QuelGenre)
  GenreMusique = QuelGenre
End Property

Property Get Musique()
  Musique = GenreMusique
End Property

A nouveau, on pourrait se demander pourquoi créer une Property Let et Get, puisqu'à priori, il suffit de lire ou d'écrire dans la variable GenreMusique qui serait simplement déclarée Public pour que tout le monde puisse y accéder sans restriction, comme ceci :

Contenu de la classe Bistrot :

Public GenreMusique

Appel depuis l'extérieur :

Sub TesterLaClasse()
  Dim CafeDeLaGare As New Bistrot
  CafeDeLaGare.GenreMusique = "Jazz"
  MsgBox CafeDeLaGare.GenreMusique
End Sub

C'est effectivement strictement la même chose. Mais je fais la même remarque qu'avant : En effet, lorsqu'on met de la musique, on voudrait définir :

  1. Que la musique est en marche (True)
  2. Le volume de la musique (entre 0 et 10)
  3. Le style d'ambiance (Calme, reposante, dynamique,...)

Et lorsqu'on lit la musique, on ne voudrait par exemple pas seulement le style de la musique, mais un message personnalsié, comme par exemple :

"Jazz à volume moyen"
"Hard-Rock Plein Pot"
"Musique classique douce"

Maintenant, la simple utilisation d'une variable ne suffit plus.

Voici le code complet de la classe Bistrot, avec les 2 Property Musique :

Dim AvecMusique
Dim GenreMusical
Dim VolumeMusical

Property Let Musique(QuelGenre)
  AvecMusique = True
  Select Case QuelGenre
    Case "Jazz":
      GenreMusical = "Jazzy"
      VolumeMusical = 4
    Case "Hard-Rock":
      GenreMusical = "Hardeux"
      VolumeMusical = 10
    Case "Classique":
      GenreMusical = "Bien habillé"
      VolumeMusical = 1
    Case "Silence":
      GenreMusique = ""
      AvecMusique = False
      VolumeMusical = 0
    End Select
End Property


Property Get Musique()
  If AvecMusique = False Then
     Musique = "Pas de musique"
     Exit Property
  End If

  Musique = GenreMusical & ", "
  Select Case VolumeMusical
    Case 1 To 3: Musique = Musique & "Calme"
    Case 3 To 5: Musique = Musique & "Détendu"
    Case 6 To 8: Musique = Musique & "Dynamique"
    Case Is > 8: Musique = Musique & "Très forte"
  End Select
End Property

Et le code de l'appel à la classe :

Sub TesterLaClasse()
  Dim CafeDeLaGare As New Bistrot
  CafeDeLaGare.Musique = "Jazz"
  MsgBox CafeDeLaGare.Musique
End Sub

Voici un exemple beaucoup plus basique pour juste bien comprendre la différence entre Get et Let :

Contenu de la classe "ClasseDebile":

Property Let Machin(Truc)
  MsgBox "Je suis dans Let" ' (Truc vaut 1)
End Property

Property Get Machin()
  MsgBox "Je suis dans Get"
End Property

Appel à la classe :

Dim Troulala As New ClasseDebile
Sub TestBidon()

  Troulala.Machin = "N'importe quoi" ' Appelle Let Machin(Parametre)
  VariableQuelconque = Troulala.Machin ' Appelle Get Machin()
End Sub

Une classe peut contenir elle-même une classe

Un exemple parlant va être plus compréhensible qu'un long disours :

  1. Créez une classe que vous appellerez Salle. Placez y le code suivant :
    Public NombreTable
  2. Créez une autre classe que vous appellerez Bistrot. Placez-y le code suivant :
    Public NombreClient
    Public Salon As New Salle
  3. Créez encore ailleurs une procédure que vous appellerez Sub Tralala. Placez y le code suivant :
    Sub Tralala()
      Dim CafeDeLaGare As New Bistrot
      ' Appel à une variable dans Bistrot :
      CafeDeLaGare.NombreClient = 12
      ' Appel à une variable dans Salle, elle-même définie comme Salon dans la classe Bistrot :  
      CafeDeLaGare.Salon.NombreTable = 4
    End Sub

Le constructeur et le destructeur

Ce que l'on appelle le constructeur en POO (Programmation Orientée Objet) désigne simplement une procédure exécutée automatiquement immédiatement lors du premier appel à quoi que ce soit dans la classe en question en utilisant le nom de procédure-clé Class_initialize. Exemple :

  1. Créez une autre classe que vous appellerez Bistrot. Placez-y le code suivant :
    Public NombreClient
    Private Sub Class_initialize()
      MsgBox "Début"
    End Sub
  2. Créez ailleurs une procédure sub Tralala :
    Sub Tralala()
      Dim Impact As New Bistrot
      ' Dès l'exécution de la ligne suivante, juste avant, sera exécutée Class_initialize (ok ok)
      Impact.NombreClient = 2
    End Sub

Même commentaire avec le destructeur qui s'exécutera à la fin du code VBA

Private Sub Class_Terminate()
MsgBox "Fin"
End Sub

Tutoriel très instructif ici : http://cafeine.developpez.com/access/tutoriel/classe/

Plus d'infos (en anglais) sur cette page de Microsoft :

http://www.microsoft.com/AccessDev/Articles/GetzCh3.HTM

---