Je souhaite disposer d'un catalogue de matériaux constants pour pouvoir utiliser un code ressemblant à ce qui suit:
Dim MyDensity, MySymbol
MyDensity = ALUMINUM.Density
MySymbol = ALUMINUM.Symbol
Évidemment, la densité et le symbole de l'aluminium ne devraient pas changer, je veux donc que ce soient des constantes, mais j'aime bien la notation par points pour plus de simplicité.
Je vois quelques options mais je ne les aime pas.
Faites des constantes pour chaque propriété de chaque matériau. Cela semble être trop de constantes puisque je pourrais avoir 20 matériaux chacun avec 5 propriétés.
Const ALUMINUM_DENSITY As Float = 169.34
Const ALUMINUM_SYMBOL As String = "AL"
Définissez une énumération avec tous les matériaux et créez des fonctions qui renvoient les propriétés. Il n'est pas aussi évident que la densité soit constante puisque sa valeur est renvoyée par une fonction.
Public Enum Material
MAT_ALUMINUM
MAT_COPPER
End Enum
Public Function GetDensity(Mat As Material)
Select Case Mat
Case MAT_ALUMINUM
GetDensity = 164.34
End Select
End Function
Il ne semble pas que Const Structs ou Const Objects vont résoudre ce problème, mais je me trompe peut-être (ils ne sont peut-être même pas autorisés). Y a-t-il un meilleur moyen?
Rendre l'équivalent de VBA à une "classe statique". Les modules standard peuvent avoir des propriétés, et rien ne dit qu'ils ne peuvent pas être en lecture seule. Je voudrais également envelopper la densité et le symbole dans un type:
'Materials.bas
Public Type Material
Density As Double
Symbol As String
End Type
Public Property Get Aluminum() As Material
Dim output As Material
output.Density = 169.34
output.Symbol = "AL"
Aluminum = output
End Property
Public Property Get Iron() As Material
'... etc
End Property
Cela se rapproche beaucoup de la sémantique d'utilisation souhaitée:
Private Sub Example()
Debug.Print Materials.Aluminum.Density
Debug.Print Materials.Aluminum.Symbol
End Sub
Si vous êtes dans le même projet, vous pouvez même supprimer le qualificatif explicite Materials
(bien que je vous recommande de le rendre explicite):
Private Sub Example()
Debug.Print Aluminum.Density
Debug.Print Aluminum.Symbol
End Sub
IMO @Comintern claquez la tête; cette réponse est juste une autre alternative possible.
Faites une interface pour cela. Ajoutez un module de classe, appelez-le IMaterial
; cette interface formalisera les propriétés get-only dont Material
a besoin:
Option Explicit
Public Property Get Symbol() As String
End Property
Public Property Get Density() As Single
End Property
Maintenant, ouvrez le Bloc-notes et collez cet en-tête de classe:
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "StaticClass1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Enregistrez-le sous le nom StaticClass1.cls
et conservez-le dans votre dossier "Fichiers de code VBA fréquemment utilisés" (make un si vous n'en avez pas!).
Ajoutez maintenant un prototype d'implémentation au fichier texte:
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "Material"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Implements IMaterial
Private Const mSymbol As String = ""
Private Const mDensity As Single = 0
Private Property Get IMaterial_Symbol() As String
IMaterial_Symbol = Symbol
End Property
Private Property Get IMaterial_Density() As Single
IMaterial_Density = Density
End Property
Public Property Get Symbol() As String
Symbol = mSymbol
End Property
Public Property Get Density() As Single
Density = mDensity
End Property
Enregistrez ce fichier texte en tant que Material.cls
.
Maintenant, importez cette classe Material
dans votre projet; renommez-le en AluminiumMaterial
et remplissez les espaces:
Private Const mSymbol As String = "AL"
Private Const mDensity As Single = 169.34
Importez à nouveau la classe Material
, renommez-la en AnotherMaterial
, remplissez les espaces:
Private Const mSymbol As String = "XYZ"
Private Const mDensity As Single = 123.45
Rinçage et répétition pour chaque matériau: il suffit de fournir chaque valeur une fois par matériau.
Si vous utilisez Rubberduck , ajoutez une annotation de dossier au fichier de modèle:
'@Folder("Materials")
Et ensuite, l'explorateur de code regroupera proprement toutes les classes IMaterial
dans un dossier Materials
.
Avoir "plusieurs modules" n'est qu'un problème dans VBA car le Project Explorer de VBE le rend plutôt gênant (en plaçant chaque classe dans un seul dossier "classes"). L'explorateur Code de Rubberduck ne permet pas à VBA d'avoir des espaces de noms, mais vous permet d'organiser votre projet VBA de manière structurée.
En termes d'utilisation, vous pouvez maintenant avoir du code polymorphe écrit sur l'interface IMaterial
:
Public Sub DoSomething(ByVal material As IMaterial)
Debug.Print material.Symbol, material.Density
End Sub
Ou vous pouvez accéder aux propriétés get-only à partir de l'instance exposée default (obtenue à partir de l'attribut VB_PredeclaredId = True
des modules):
Public Sub DoSomething()
Debug.Print AluminumMaterial.Symbol, AluminumMaterial.Density
End Sub
Et vous pouvez transmettre les instances par défaut à toute méthode devant fonctionner avec une variable IMaterial
:
Public Sub DoSomething()
PrintToDebugPane AluminumMaterial
End Sub
Private Sub PrintToDebugPane(ByVal material As IMaterial)
Debug.Print material.Symbol, material.Density
End Sub
Upsides, vous obtenez une validation au moment de la compilation pour tout; les types sont impossibles à abuser.
Inconvénients, vous avez besoin de nombreux modules (classes), et si l'interface doit être modifiée, de nombreuses classes doivent être mises à jour pour que le code reste compilable.
Vous pouvez créer un module de classe - appelons-le Matériau et définissons les propriétés d'un matériau en tant que membres publics (variables), telles que Densité, Symbole:
Public Density As Float
Public Symbol As String
Ensuite, dans un module standard, créez les matériaux:
Public Aluminium As New Material
Aluminium.Density = 169.34
Aluminium.Symbol = "AL"
Public Copper As New Material
' ... etc
La bonne chose à propos des classes est que vous pouvez y définir des fonctions (méthodes) que vous pouvez également appeler avec la notation par points sur n’importe quelle instance. Par exemple, if pourrait définir dans la classe:
Public Function AsString()
AsString = Symbol & "(" & Density & ")"
End Function
... puis avec votre instance Aluminium
(voir plus haut) vous pouvez faire:
MsgBox Aluminium.AsString() ' => "AL(169.34)"
Et chaque fois que vous avez une nouvelle fonctionnalité/comportement à implémenter qui doit être disponible pour tous les matériaux, il vous suffit de l'implémenter dans la classe.
Un autre exemple. Définir dans la classe:
Public Function CalculateWeight(Volume As Float) As Float
CalculateWeight = Volume * Density
End Function
... et vous pouvez maintenant faire:
Weight = Aluminium.CalculateWeight(50.6)
Si vous voulez vous assurer que votre code n'attribue pas de nouvelle valeur aux propriétés Density
et Symbol
, vous avez besoin d'un peu plus de code. Dans la classe, vous définiriez ces propriétés avec des getters et des setters (en utilisant les syntaxes Get
et Set
). Par exemple, Symbol
serait défini comme suit:
Private privSymbol as String
Property Get Symbol() As String
Symbol = privSymbol
End Property
Property Set Symbol(value As String)
If privSymbol = "" Then privSymbol = value
End Property
Le code ci-dessus ne permettra de définir la propriété Symbol que si elle est différente de la chaîne vide. Une fois réglé sur "AL", il ne peut plus être changé. Vous pourriez même vouloir générer une erreur si une telle tentative est faite.
Vous pouvez créer un module appelé "ALUMINIUM" et y placer les éléments suivants:
Public Const Density As Double = 169.34
Public Const Symbol As String = "AL"
Maintenant, dans un autre module, vous pouvez faire appel à ceux-ci comme ceci:
Sub test()
Debug.Print ALUMINUM.Density
Debug.Print ALUMINUM.Symbol
End Sub
J'aime une approche hybride. C'est du pseudo-code car je n'ai pas le temps de travailler pleinement sur l'exemple.
Créez une MaterialsDataClass
- voir les connaissances de Mathieu Guindon sur la manière de la définir en tant que classe statique
Private ArrayOfSymbols() as String
Private ArrayOfDensity() as Double
Private ArrayOfName() as String
' ....
ArrayOfSymbols = Split("H|He|AL|O|...","|")
ArrayOfDensity = '....
ArrayOfName = '....
Property Get GetMaterialBySymbol(value as Variant) as Material
Dim Index as Long
Dim NewMaterial as Material
'Find value in the Symbol array, get the Index
New Material = SetNewMaterial(ArrayOfSymbols(Index), ArrayofName(Index), ArrayofDensity(Index))
GetMaterialBySymbol = NewMaterial
End Property
Property Get GetMaterialByName(value as string) ' etc.
Material
est semblable aux autres réponses. J'ai utilisé un Type
ci-dessous, mais je préfère Class
es à Type
s car ils permettent davantage de fonctionnalités et peuvent également être utilisés dans des boucles 'For Each'.
Public Type Material
Density As Double
Symbol As String
Name as String
End Type
Dans votre utilisation:
Public MaterialsData as New MaterialsDataClass
Dim MyMaterial as Material
Set MyMaterial = MaterialsDataClass.GetMaterialByName("Aluminium")
Debug.print MyMaterial.Density