Passer des arguments au constructeur dans VBA

Comment Pouvez-vous construire des objets en passant des arguments directement à vos propres classes?

Quelque Chose comme ceci:

Dim this_employee as Employee
Set this_employee = new Employee(name:="Johnny", age:=69)

Ne pas être capable de le faire est très ennuyeux, et vous vous retrouvez avec des solutions sales pour contourner ce problème.

59
demandé sur bgusach 2013-03-05 16:43:23

5 réponses

Voici un petit truc que j'utilise dernièrement et qui apporte de bons résultats. Je voudrais partager avec ceux qui doivent se battre souvent avec VBA.

1.- implémentez un sous-programme d'initiation public dans chacune de vos classes personnalisées. Je l'appelle InitiateProperties dans toutes mes classes. Cette méthode doit accepter les arguments que vous souhaitez envoyer au constructeur.

2.- Créez un module appelé factory, et créez une fonction publique avec le mot "créer" plus le même nom comme la classe, et les mêmes arguments entrants que le constructeur a besoin. Cette fonction doit instancier votre classe, et appeler le sous-programme d'initiation expliqué au point (1), en passant les arguments reçus. Enfin retourné la méthode instanciée et initiée.

Exemple:

Disons que nous avons L'employé de classe personnalisée. Comme l'exemple précédent, is doit être instancié avec name et age.

C'est la méthode InitiateProperties. m_name et m_age sont nos privée propriétés à définir.

Public Sub InitiateProperties(name as String, age as Integer)

    m_name = name
    m_age = age

End Sub

Et maintenant dans le module d'usine:

Public Function CreateEmployee(name as String, age as Integer) as Employee

    Dim employee_obj As Employee
    Set employee_obj = new Employee

    employee_obj.InitiateProperties name:=name, age:=age
    set CreateEmployee = employee_obj

End Function

Et enfin quand vous voulez instancier un employé

Dim this_employee as Employee
Set this_employee = factory.CreateEmployee(name:="Johnny", age:=89)

Particulièrement utile lorsque vous avez plusieurs classes. Il suffit de placer une fonction pour chacun dans la fabrique du module et d'instancier simplement en appelant factory.CreateClassA (arguments), usine.CreateClassB (other_arguments) , etc.

Modifier

Comme l'a souligné stenci, vous pouvez faire la même chose avec une syntaxe terser en éviter de créer une variable locale dans les fonctions du constructeur. Par exemple, la fonction CreateEmployee peut être écrite comme ceci:

Public Function CreateEmployee(name as String, age as Integer) as Employee

    Set CreateEmployee = new Employee
    CreateEmployee.InitiateProperties name:=name, age:=age

End Function

Ce qui est plus agréable.

95
répondu bgusach 2014-03-18 08:17:20

, j'utilise un Factory module qui contient une (ou plusieurs) constructeur par classe qui appelle la Init membre de chaque classe.

Par exemple une classe Point:

Class Point
Private X, Y
Sub Init(X, Y)
  Me.X = X
  Me.Y = Y
End Sub

Un Line classe

Class Line
Private P1, P2
Sub Init(Optional P1, Optional P2, Optional X1, Optional X2, Optional Y1, Optional Y2)
  If P1 Is Nothing Then
    Set Me.P1 = NewPoint(X1, Y1)
    Set Me.P2 = NewPoint(X2, Y2)
  Else
    Set Me.P1 = P1
    Set Me.P2 = P2
  End If
End Sub

Et un module Factory:

Module Factory
Function NewPoint(X, Y)
  Set NewPoint = New Point
  NewPoint.Init X, Y
End Function

Function NewLine(Optional P1, Optional P2, Optional X1, Optional X2, Optional Y1, Optional Y2)
  Set NewLine = New Line
  NewLine.Init P1, P2, X1, Y1, X2, Y2
End Function

Function NewLinePt(P1, P2)
  Set NewLinePt = New Line
  NewLinePt.Init P1:=P1, P2:=P2
End Function

Function NewLineXY(X1, Y1, X2, Y2)
  Set NewLineXY = New Line
  NewLineXY.Init X1:=X1, Y1:=Y1, X2:=X2, Y2:=Y2
End Function

Un aspect intéressant de cette approche est qu'il est facile d'utiliser les fonctions d'usine dans les expressions. Par exemple, il est possible de faire quelque chose comme:

D = Distance(NewPoint(10, 10), NewPoint(20, 20)

Ou:

D = NewPoint(10, 10).Distance(NewPoint(20, 20))

C'est propre: l'usine fait très peu et il le fait de manière cohérente sur tous les objets, juste la création et un Init appel à chaque créateur .

Et c'est assez orienté objet: les fonctions Init sont définies à l'intérieur des objets.

Modifier

J'ai oublié d'ajouter que cela me permet de créer des méthodes statiques. Par exemple, je peux faire quelque chose comme (après avoir rendu les paramètres facultatifs):

NewLine.DeleteAllLinesShorterThan 10

Malheureusement, une nouvelle instance de l'objet est créée à chaque fois, donc tout la variable statique sera perdue après l'exécution. La collection de lignes et toute autre variable statique utilisée dans cette méthode pseudo-statique doit être définie dans un module.

29
répondu stenci 2016-07-20 16:40:24

Lorsque vous exportez un module de classe et ouvrez le fichier dans le bloc-notes, vous remarquerez, près du haut, un tas d'attributs cachés (le VBE ne les affiche pas, et n'expose pas la fonctionnalité pour modifier la plupart d'entre eux non plus). L'un d'eux est VB_PredeclaredId:

Attribute VB_PredeclaredId = False

Définissez-le sur True, Enregistrez et réimportez le module dans votre projet VBA.

Les Classes avec un PredeclaredId ont une" instance globale " que vous obtenez gratuitement-exactement comme les modules UserForm (exportez un formulaire utilisateur, vous verrez son predeclaredId l'attribut est défini sur true).

Beaucoup de gens utilisent simplement l'instance prédéclarée pour stocker l'état. C'est faux - c'est comme stocker l'état de l'instance dans une classe statique!

Au lieu de cela, vous utilisez cette instance par défaut pour implémenter votre méthode d'usine:

[Employee classe]

'@PredeclaredId
Option Explicit

Private Type TEmployee
    Name As String
    Age As Integer
End Type

Private this As TEmployee

Public Function Create(ByVal emplName As String, ByVal emplAge As Integer) As Employee
    With New Employee
        .Name = emplName
        .Age = emplAge
        Set Create = .Self 'returns the newly created instance
    End With
End Function

Public Property Get Self() As Employee
    Set Self = Me
End Property

Public Property Get Name() As String
    Name = this.Name
End Property

Public Property Let Name(ByVal value As String)
    this.Name = value
End Property

Public Property Get Age() As String
    Age = this.Age
End Property

Public Property Let Age(ByVal value As String)
    this.Age = value
End Property

, Avec cela, vous pouvez faire ceci:

Dim empl As Employee
Set empl = Employee.Create("Johnny", 69)

Employee.Create Fonctionne à partir de l'instance par défaut , c'est-à-dire qu'elle est considérée comme un membre du type , et invoquée depuis l'instance par défaut uniquement.

Le problème est, c'est aussi parfaitement légal:

Dim emplFactory As New Employee
Dim empl As Employee
Set empl = emplFactory.Create("Johnny", 69)

Et ça craint, parce que maintenant vous avez une API confuse. Vous pouvez utiliser '@Description annotations / VB_Description attributs pour l'utilisation du document, mais sans Rubberduck, il n'y a rien dans l'éditeur qui vous montre cette information sur les sites d'appel.

En outre, les membres Property Let sont accessibles, donc votre instance Employee est mutable :

empl.Name = "Booba" ' Johnny no more!

L'astuce est de faire en sorte que votre classe implémente une interface qui expose uniquement ce qui doit être exposé:

[IEmployee classe]

Option Explicit

Public Property Get Name() As String : End Property
Public Property Get Age() As Integer : End Property

Et maintenant vous pouvez faire des Employee mettre en œuvre IEmployee - la classe finale pourrait ressembler à ceci:

[Employee classe]

'@PredeclaredId
Option Explicit
Implements IEmployee

Private Type TEmployee
    Name As String
    Age As Integer
End Type

Private this As TEmployee

Public Function Create(ByVal emplName As String, ByVal emplAge As Integer) As IEmployee
    With New Employee
        .Name = emplName
        .Age = emplAge
        Set Create = .Self 'returns the newly created instance
    End With
End Function

Public Property Get Self() As IEmployee
    Set Self = Me
End Property

Public Property Get Name() As String
    Name = this.Name
End Property

Public Property Let Name(ByVal value As String)
    this.Name = value
End Property

Public Property Get Age() As String
    Age = this.Age
End Property

Public Property Let Age(ByVal value As String)
    this.Age = value
End Property

Private Property Get IEmployee_Name() As String
    IEmployee_Name = Name
End Property

Private Property Get IEmployee_Age() As Integer
    IEmployee_Age = Age
End Property

Avis de la Create méthode retourne maintenant l'interface, et l'interface n'est pas exposer le Property Let membres? Maintenant, le code d'appel peut ressembler à ceci:

Dim empl As IEmployee
Set empl = Employee.Create("Immutable", 42)

Et puisque le code client est écrit contre le interface, les seuls membres empl exposés sont les membres définis par l'interface IEmployee, ce qui signifie qu'il ne voit pas la méthode Create, ni le getter Self, ni aucun des mutateurs Property Let: donc au lieu de travailler avec la classe" concrète "Employee, le reste du code peut fonctionner avec l'interface "abstraite" IEmployee, et profiter d'un objet

14
répondu Mathieu Guindon 2018-03-27 11:25:38

Utiliser l'astuce

Attribute VB_PredeclaredId = True

J'ai trouvé un autre moyen plus compact:

Option Explicit
Option Base 0
Option Compare Binary

Private v_cBox As ComboBox

'
' Class creaor
Public Function New_(ByRef cBox As ComboBox) As ComboBoxExt_c
  If Me Is ComboBoxExt_c Then
    Set New_ = New ComboBoxExt_c
    Call New_.New_(cBox)
  Else
    Set v_cBox = cBox
  End If
End Function

Comme vous pouvez le voir, le constructeur New_ est appelé à la fois pour créer et définir les membres privés de la classe (comme init). mais cela peut être évité en définissant un drapeau.

0
répondu Tomasz 2018-09-05 10:44:17

Une Autre approche

Disons que vous créez une classe clsBitcoinPublicKey

Dans le module de classe, créez un sous-programme supplémentaire, qui agit comme vous voulez que le constructeur réel se comporte. Ci-dessous, je l'ai nommé Constructoradjoint.

Public Sub ConstructorAdjunct(ByVal ...)

 ...

End Sub

From the calling module, you use an additional statement

Dim loPublicKey AS clsBitcoinPublicKey

Set loPublicKey = New clsBitcoinPublicKey

Call loPublicKey.ConstructorAdjunct(...)

La seule pénalité est l'appel supplémentaire, mais l'avantage est que vous pouvez tout garder dans le module de classe, et le débogage devient plus facile.

-1
répondu Larry Kavounas 2014-03-29 18:51:48