Passer des arguments au constructeur dans VBA

Comment pouvez-vous construire des objects en passant des arguments directement à vos propres classes?

Quelque chose comme ça:

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

Ne pas pouvoir faire cela est très ennuyeux, et vous vous retrouvez avec des solutions sales pour y remédier.

Voici un petit truc que j’utilise dernièrement et qui donne 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 “Create” plus le même nom que la classe et les mêmes arguments entrants que le constructeur. Cette fonction doit instancier votre classe et appeler le sous-programme d’initiation expliqué au point (1) en transmettant les arguments reçus. Finalement renvoyé la méthode instanciée et initiée.

Exemple:

Disons que nous avons la classe personnalisée Employé. Comme dans l’exemple précédent, il doit être instancié avec le nom et l’âge.

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

 Public Sub InitiateProperties(name as Ssortingng, age as Integer) m_name = name m_age = age End Sub 

Et maintenant dans le module d’usine:

 Public Function CreateEmployee(name as Ssortingng, 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 de modules et instancier simplement en appelant factory.CreateClassA (arguments) , factory.CreateClassB (other_arguments) , etc.

MODIFIER

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

 Public Function CreateEmployee(name as Ssortingng, age as Integer) as Employee Set CreateEmployee = new Employee CreateEmployee.InitiateProperties name:=name, age:=age End Function 

Ce qui est mieux

J’utilise un module Factory contenant un (ou plusieurs) constructeur par classe qui appelle le membre Init 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 

Une classe de Line

 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’elle facilite l’utilisation des fonctions 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 elle le fait de manière cohérente sur tous les objects, juste la création et un appel d’ Init à chaque créateur .

Et c’est assez orienté object: les fonctions Init sont définies à l’intérieur des objects.

MODIFIER

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

 NewLine.DeleteAllLinesShorterThan 10 

Malheureusement, une nouvelle instance de l’object est créée à chaque fois, donc toute 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 doivent être définies dans un module.

Lorsque vous exportez un module de classe et ouvrez le fichier dans le Bloc-notes, vous remarquerez, en haut, un tas d’atsortingbuts cachés (le VBE ne les affiche pas et n’expose pas de fonctionnalités pour modifier la plupart d’entre eux). L’un d’eux est VB_PredeclaredId :

 Atsortingbute VB_PredeclaredId = False 

Réglez-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 que l’atsortingbut predeclaredId est défini sur true).

Beaucoup de gens utilisent simplement l’instance prédéfinie 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 exploitez cette instance par défaut pour implémenter votre méthode d’usine:

[Classe d’ Employee ]

 '@PredeclaredId Option Explicit Private Type TEmployee Name As Ssortingng Age As Integer End Type Private this As TEmployee Public Function Create(ByVal emplName As Ssortingng, 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 Ssortingng Name = this.Name End Property Public Property Let Name(ByVal value As Ssortingng) this.Name = value End Property Public Property Get Age() As Ssortingng Age = this.Age End Property Public Property Let Age(ByVal value As Ssortingng) 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’il est considéré comme un membre du type et invoqué à partir de l’instance par défaut uniquement.

Le problème est que ceci est également parfaitement légal:

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

Et ça craint, car maintenant vous avez une API déroutante. Vous pouvez utiliser '@Description annotations / VB_Description pour utiliser les documents, mais sans Rubberduck, il n’y a rien dans l’éditeur qui affiche ces informations sur les sites d’appels.

En outre, les membres Property Let sont accessibles, de sorte que votre instance Employee est mutable :

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

L’astuce consiste à faire en sorte que votre classe implémente une interface qui expose uniquement ce qui doit être exposé:

[Classe IEmployee ]

 Option Explicit Public Property Get Name() As Ssortingng : End Property Public Property Get Age() As Integer : End Property 

Et maintenant, vous faites en sorte que Employee implémente IEmployee – la classe finale pourrait ressembler à ceci:

[Classe d’ Employee ]

 '@PredeclaredId Option Explicit Implements IEmployee Private Type TEmployee Name As Ssortingng Age As Integer End Type Private this As TEmployee Public Function Create(ByVal emplName As Ssortingng, 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 Ssortingng Name = this.Name End Property Public Property Let Name(ByVal value As Ssortingng) this.Name = value End Property Public Property Get Age() As Ssortingng Age = this.Age End Property Public Property Let Age(ByVal value As Ssortingng) this.Age = value End Property Private Property Get IEmployee_Name() As Ssortingng IEmployee_Name = Name End Property Private Property Get IEmployee_Age() As Integer IEmployee_Age = Age End Property 

Notez que la méthode Create renvoie désormais l’interface et que l’interface n’expose pas les membres Property Let ? Maintenant, le code d’appel peut ressembler à ceci:

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

Et comme le code client est écrit sur l’interface, les seuls membres empl exposent sont les membres définis par l’interface IEmployee , ce qui signifie qu’il ne voit ni la méthode Create , ni le Self getter, ni aucun des mutateurs Property Let : au lieu de travailler avec la classe Employee “concrète”, le rest du code peut fonctionner avec l’interface IEmployee “abstraite” et profiter d’un object immuable et polymorphe.

Utiliser le tour

 Atsortingbute 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). Le seul problème est que, s’il est appelé sur l’instance non-statique, il réinitialisera le membre privé. mais cela peut être évité en mettant un drapeau.

Une autre approche

Disons que vous créez une classe clsBitcoinPublicKey

Dans le module de classe, créez un sous-programme ADDITIONAL, qui agit comme vous le souhaitez pour le vrai constructeur. Je l’ai nommé ci-dessous ConstructorAdjunct.

 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 conserver dans le module de classe et le débogage devient plus facile.