- Max Weber et la « violence légitime »
- Comment casser votre double authentification et la rendre tout à fait inutile
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 :
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").
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 :
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…).
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 :