web-dev-qa-db-fra.com

Constante avec opérateur de points (VBA)

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.

  1. 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"
    
  2. 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?

14
R. Binter

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
12
Comintern

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.

5
Mathieu Guindon

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

Ajout de comportement

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)

Rendre les propriétés en lecture seule

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.

2
trincot

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
2
ArcherBird

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 Classes à Types 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
0
AJD