Les structures et les classes en VBA

Ou comment définir nos propres types de données

La programmation orientée objet est un paradigme de programmation, c’est-à-dire une façon de concevoir les programmes, dans lequel des objets sont définis et interagissent entre eux.

Les objets ont un type et peuvent avoir des propriétés et des méthodes. Vous en avez déjà sans doute utilisé en VBA sans vous en rendre compte : les feuilles, les plages ou encore les tableaux (ListObject) en sont ! En effet, une feuille possède notamment une propriété Name pour son nom et une méthode Activate pour la rendre active par exemple.

Au cours de ce billet, nous allons voir comment définir nos propres types de données en VBA.

C’est parti !

Pour ce billet, nous nous placerons dans l’environnement Microsoft Office.

Les structures

Les structures, aussi appelés types personnalisés, sont une façon simple d’ajouter de nouveaux types de données se composant de la même façon dans nos programmes.

Par exemple, nous pourrions avoir besoin d’un type Client qui aurait un nom, un prénom, une date de naissance et un nombre de commandes passées.

Définition

Pour définir un type personnalisé, nous utilisons l’instruction Type, en indiquant un niveau de visibilité, un nom et des variables internes.

Public Type TypeClient
    sNom As String
    sPrenom As String
    dDateNaissance As Date
    iNombreCommandes As Integer
End Type

En fonction de la visibilité (Private/Public), les structures ne peuvent pas être définies dans tous les types de fichier (UseForm, module de classe, feuille, …)

Utilisation

Notre type étant défini, nous sommes maintenant en mesure de l’utiliser.

Tout d’abord, nous devons déclarer une variable du type personnalisé voulu de la sorte :

Dim tClient As TypeClient ' Déclaration

Ensuite, nous pouvons accéder à une valeur pour la lire ou la modifier, avec la notation ., comme ceci :

tClient.sNom = "Dupont"
Debug.Print (tClient.sNom) ' Dupont

Avec un peu d’imagination, nous pouvons construire des procédures ou des fonctions utilisant nos structures :

Public Sub InitialiseClient(ByRef tClient As TypeClient, ByVal sNom As String, ByVal sPrenom As String, ByVal dDateNaissance As Date, ByVal iNombreCommandes As Integer)
    With tClient
        .sNom = sNom
        .sPrenom = sPrenom
        .dDateNaissance = dDateNaissance
        .iNombreCommandes = iNombreCommandes
    End With
End Sub

Private Function ClientToString(ByRef tClient As TypeClient) As String
    Dim sResultat As String
    sResultat = "Client : " & tClient.sNom & " " & tClient.sPrenom & " " & tClient.dDateNaissance
    ClientToString = sResultat
End Function

Dim tClient2 As TypeClient
InitialiseClient tClient2, "Dupont", "Albert", "15/05/1965", 8
Debug.Print (ClientToString(tClient2)) ' Client : Dupont Albert 15/05/1965

Il est tout à fait possible d’imbriquer des types. Par exemple, un type Client pourrait faire appel à une structure Adresse pour son adresse de livraison.

Au cours de cette section, nous avons comment nous servir des types personnalisés en VBA.

Les classes

Les classes sont un moyen plus complet de définir des types de données.

Il est alors possible d’encapsuler (c’est-à-dire donner une visibilité) différemment les éléments de la classe ou encore d’appliquer directement des méthodes à un objet instancié.

Création

Pour créer une classe, nous devons ajouter un module de classe au projet. Cela se fait en allant dans le menu "Insertion" et en cliquant sur "Module de classe" ou depuis la barre des raccourcis comme illustré ci-dessous :

Insertion module de classe
Insertion module de classe
Raccourci insertion module de classe
Raccourci insertion module de classe

Nous pouvons changer le nom de la classe et son accessibilité depuis les propriétés (raccourci F4 pour afficher la fenêtre "Propriétés").

Propriétés module de classe
Propriétés module de classe

Pour les exemples, nous utiliserons cette classe dans un module standard quelconque.

Instanciation

On dit que l’on instancie une classe lorsque l’on crée un objet à partir de celle-ci. Pour cela, nous faisons appel à l’opérateur New.

Dim oClient1 As clsClient ' Déclaration
Set oClient1 = New clsClient ' Instanciation

À noter que nous pouvons raccourcir la déclaration et l’instanciation à une ligne de cette manière :

Dim oClient2 As New clsClient ' Déclaration avec instanciation

Propriétés

Notre classe est pour le moment assez vide. Nous allons commencer par définir des propriétés qui seront l’équivalent des variables de notre structure Client de la section précédente.

Il y a deux façons de définir des propriétés en VBA.

Propriétés en tant que variables membres

La première consiste à définir des propriétés en tant que variables membres.

Public sNom As String
Public sPrenom As String
Public dDateNaissance As Date
Private iNombreCommandes As Integer ' Inaccessible depuis l'extérieur

Pour accéder aux valeurs de l’objet, nous utilisons la même notation que pour les structures :

oClient1.sNom = "Dupont"
Debug.Print (oClient1.sNom) ' Dupont
'oClient1.iNombreCommandes = 1 ' Erreur de compilation : Membre de méthode ou de données introuvable

Comme vous pouvez le voir, la propriété iNombreCommandes n’est pas atteignable depuis l’extérieur, c’est normal vu que nous lui avons donné une visibilité privée !

Propriétés en tant que procédures Property

La seconde approche consiste à définir des propriétés en tant que procédures Property.

Cette seconde approche offre davantage de flexibilité pour choisir l’interface de notre classe. Contrairement aux propriétés en tant que variables membres pour lesquelles c’est tout ou rien (accessible ou non), nous pouvons choisir ce qui sera accessible en lecture ou en écriture depuis l’extérieur.

De plus cette approche permet aussi de créer des propriétés dérivées d’autres plutôt que de passer par des fonctions pour faire cela.

Cette relation entre propriété membre et propriété en tant que procédure Property peut être représentée de la sorte :

Relation propriété membre et propriété procédure Property
Relation propriété membre et propriété procédure Property

Si la propriété membre est définie avec une visibilité publique, il n’est pas logique de définir des propriétés Property pour celle-ci, car nous ne pourrons pas restreindre son interface. En effet, nous pourrons toujours choisir de passer par cette propriété membre plutôt que par les propriétés de lecture ou d’écriture.

En revanche si la propriété membre est privée, nous pouvons choisir d’ouvrir son interface en lecture ou en écriture.

Pour l’exemple qui va suivre, ajoutons une classe clsAdresse.

Cela fait, modifions également nos propriétés dans clsClient :

Private mNom As String
Private mPrenom As String
Public dDateNaissance As Date
Private mAdresseLivraison As clsAdresse
Private mNombreCommandes As Integer
Let/Set

La Property Let permet d’assigner une valeur simple tandis que la Property Set permet d’assigner une valeur de type objet. Vous avez sans doute déjà l’habitude d’utiliser Set tandis que Let n’est pas explicitement requis.

' Exemples ouverture interface en écriture

Public Property Let sNom(ByVal sNom As String)
    mNom = sNom
End Property

Public Property Let sPrenom(ByVal sPrenom As String)
    mPrenom = sPrenom
End Property

Public Property Set oAdresseLivraison(ByRef oAdresseLivraison As clsAdresse)
    Set mAdresseLivraison = oAdresseLivraison
End Property
Get

La Property Get permet d’obtenir une valeur quelque soit sa nature.

' Exemple ouverture interface en lecture

Public Property Get iNombreCommandes() As Integer
    iNombreCommandes = mNombreCommandes
End Property

' Exemples propriétés dérivées d'autres

Public Property Get sNomComplet() As String
    sNomComplet = mPrenom & " " & UCase(mNom)
End Property

Public Property Get sTypeClient() As String
    Dim sResult As String
    
    If iNombreCommandes = 0 Then
        sResult = "Client timide"
    ElseIf iNombreCommandes < 10 Then
        sResult = "Client néophyte"
    ElseIf iNombreCommandes < 20 Then
        sResult = "Client régulier"
    Else
        sResult = "Super client"
    End If
    
    sTypeClient = sResult
End Property

Nous pouvons alors utiliser notre classe ainsi :

Dim oClient1 As clsClient ' Déclaration
Set oClient1 = New clsClient ' Instanciation
    
oClient1.sNom = "Dupont"
oClient1.sPrenom = "Albert"
oClient1.dDateNaissance = "15/05/1965"
Set oClient1.oAdresseLivraison = New clsAdresse
Debug.Print (oClient1.sNomComplet) ' Albert DUPONT
Debug.Print (oClient1.dDateNaissance) ' 15/05/1965
Debug.Print (oClient1.iNombreCommandes) ' 0 
Debug.Print (oClient1.sTypeClient) ' Client timide

Événements

En VBA, les événements nous permettent de réaliser des actions en réponse à quelque chose qui se produit (l’ouverture du classeur, l’activation d’une feuille, etc…).

Exemples événements feuille
Exemples événements feuille

Constructeur

Lorsqu’un objet est créé, il est d’abord construit puis initialisé.

L’événement Class_Initialize nous permet de définir un constructeur par défaut afin d’initialiser l’objet après sa création.

Private mCommandes As Collection ' Propriété ajoutée

Private Sub Class_Initialize()
    Debug.Print ("____________________________")
    Debug.Print ("Objet créé")
    Set mCommandes = New Collection
End Sub

Destructeur

Avant qu’un objet ne soit détruit, il peut être important de réaliser certaines opérations (sauvegardes, nettoyage de la mémoire, …).

L’événement Class_Terminate nous permet de capturer l’objet avant sa destruction dans ce but.

Private Sub Class_Terminate()
    Set mCommandes = Nothing ' Traitements...
    Debug.Print ("Objet " & sNomComplet & " détruit")
    Debug.Print ("____________________________")
End Sub

Événement personnalisé

En plus de ces deux événements de base, nous pouvons ajouter nos propres événements. Ce sujet ne sera pas abordé dans ce billet, sachez simplement que c’est possible.

Méthodes

Les méthodes sont des fonctions ou des procédures au sein de la classe. Comme pour le reste, nous pouvons choisir leur encapsulation.

Private Function NombreCommandes() As Integer
    ' Remplace les propriétés mNombreCommandes et iNombreCommandes définies auparavant
    NombreCommandes = mCommandes.Count
End Function

Public Sub AjouteCommande(ByVal sCommande As String)
    mCommandes.Add sCommande
End Sub

Dans l’exemple ci-dessus nous définissons une méthode NombreCommandes et une AjouteCommande que nous utilisons de la sorte :

Dim i As Integer
For i = 1 To 20
    oClient1.AjouteCommande Format(i, "00000000")
Next i

Code final

Voici le code final :

Classe clsAdresse

Option Explicit

' Vide

Classe clsClient

Option Explicit

Private mNom As String
Private mPrenom As String
Public dDateNaissance As Date
Private mAdresseLivraison As clsAdresse
Private mCommandes As Collection

Public Property Let sNom(ByVal sNom As String)
    mNom = sNom
End Property

Public Property Let sPrenom(ByVal sPrenom As String)
    mPrenom = sPrenom
End Property

Public Property Get sNomComplet() As String
    sNomComplet = mPrenom & " " & UCase(mNom)
End Property

Public Property Get sTypeClient() As String
    Dim sResult As String
    
    Dim iNombreCommandes As Integer
    iNombreCommandes = NombreCommandes()
    
    If iNombreCommandes = 0 Then
        sResult = "Client timide"
    ElseIf iNombreCommandes < 10 Then
        sResult = "Client néophyte"
    ElseIf iNombreCommandes < 20 Then
        sResult = "Client régulier"
    Else
        sResult = "Super client"
    End If
    
    sTypeClient = sResult
End Property

Public Property Set oAdresseLivraison(ByRef oAdresseLivraison As clsAdresse)
    Set mAdresseLivraison = oAdresseLivraison
End Property

Private Function NombreCommandes() As Integer
    NombreCommandes = mCommandes.Count
End Function

Public Sub AjouteCommande(ByVal sCommande As String)
    mCommandes.Add sCommande
End Sub

Private Sub Class_Initialize()
    Debug.Print ("____________________________")
    Debug.Print ("Objet créé")
    Set mCommandes = New Collection
End Sub

Private Sub Class_Terminate()
    Set mCommandes = Nothing ' Traitements...
    Debug.Print ("Objet " & sNomComplet & " détruit")
    Debug.Print ("____________________________")
End Sub

Module

Option Explicit

Private Sub CreerClient()
    Dim oClient1 As clsClient ' Déclaration
    Set oClient1 = New clsClient ' Instanciation
    
    oClient1.sNom = "Dupont"
    oClient1.sPrenom = "Albert"
    oClient1.dDateNaissance = "15/05/1965"
    Set oClient1.oAdresseLivraison = New clsAdresse
    
    Debug.Print (oClient1.sNomComplet) ' Albert DUPONT
    Debug.Print (oClient1.dDateNaissance) ' 15/05/1965
    Debug.Print (oClient1.sTypeClient) ' Client timide
    
    Dim i As Integer
    For i = 1 To 20
        oClient1.AjouteCommande Format(i, "00000000")
    Next i
    
    Debug.Print (oClient1.sTypeClient) ' Super client
End Sub

Fenêtre d’exécution (CTRL + G)

____________________________
Objet créé
Albert DUPONT
15/05/1965 
Client timide
Objet Albert DUPONT détruit
____________________________

Pendant cette section, nous avons vu comment utiliser les classes en VBA.


C’est déjà la fin de ce billet.

Au cours de celui-ci, nous avons vu comment définir nos propres types de données en VBA avec les structures d’abord et les classes ensuite. À noter que le système de POO en VBA est moins poussé que dans d’autres langages tels que Java.

Pour aller plus loin, il est souvent pratique de regrouper des objets de même type au sein de structures de données telles que les collections ou encore les dictionnaires.

À bientôt !

Quelques ressources :

3 commentaires

Bonjour, Merci pour ce billet 4435 sur la POO avec VBA.

Dans le module de classe ClsAdresse, je ne comprends pas comment la propriété mAdresseLivraison peut faire référence à sa propre classe ?

Je ne comprends pas pourquoi mAdresseLivraison est déclaré dans les 2 modules de classe et que oAdresseLivraison n’est déclaré dans aucun module …

Merci pour vos lumières Bonne soirée

Bonjour, merci pour votre message

En fait, la classe clsAdresse ne contient aucun code, d’ailleurs il n’y a rien la concernant cf "Voici le code final :" plus bas.

C’est vrai que le texte "Pour l’exemple qui va suivre, modifions nos propriétés et ajoutons une classe clsAdresse publique." suivi du code peut induire en erreur.

EDIT : j’ai scindé pour faciliter la lecture.

Bonne soirée,

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte