Comment pouvez-vous construire des objets 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 être capable de faire cela est très ennuyant, et vous vous retrouvez avec des solutions sales pour résoudre ce problème.
Voici un petit truc que j'utilise dernièrement et qui donne de bons résultats. Je voudrais partager avec ceux qui doivent souvent se battre 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", ainsi que le même nom que la classe et les mêmes arguments entrants que le constructeur en a besoin. Cette fonction doit instancier votre classe et appeler le sous-programme d'initiation expliqué au point (1) en transmettant les arguments reçus. Enfin retourné la méthode instanciée et initiée.
Exemple:
Disons que nous avons la classe personnalisée Employee. 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 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 cours. Il suffit de placer une fonction pour chacun dans la fabrique de modules et d’instancier simplement en appelant factory.CreateClassA (arguments), factory.CreateClassB (other_arguments), etc.
Comme l'a souligné Stenci, vous pouvez faire la même chose avec une syntaxe de testeur en évitant de créer une variable locale dans les fonctions du constructeur. Par exemple, la fonction CreateEmployee pourrait être écrite comme suit:
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 gentil.
J'utilise un module Factory
contenant un (ou plus) 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 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 bel aspect de cette approche est qu’il est facile d’utiliser les 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 pour tous les objets, juste la création et un appel Init
à chaque créateur .
Et c'est plutôt orienté objet: les fonctions Init
sont définies à l'intérieur des objets.
EDIT
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. Toute variable statique sera donc 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 presque en haut un ensemble d'attributs cachés (le VBE ne les affiche pas et n'expose pas non plus de fonctionnalités à Tweak). 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 PredeclaredId
ont une "instance globale" que vous obtenez gratuitement - exactement comme UserForm
modules (exportez un formulaire utilisateur, vous verrez que son attribut predeclaredId 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 d'une instance dans une classe statique!
Au lieu de cela, vous utilisez cette instance par défaut pour implémenter votre méthode factory:
[Employee
class]
'@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 ça, vous pouvez faire ceci:
Dim empl As Employee
Set empl = Employee.Create("Johnny", 69)
Employee.Create
travaille sur le instance par défaut, c’est-à-dire qu’il est considéré comme un membre du type et n’est appelé que par l’instance par défaut.
Le problème, 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 déroutante. Vous pouvez utiliser '@Description
annotations/VB_Description
attributs de documenter l'utilisation, mais sans Rubberduck il n'y a rien dans l'éditeur qui vous montre cette information sur les sites d'appels.
En outre, le Property Let
membres sont accessibles, votre instance Employee
est donc mutable:
empl.Name = "Booba" ' Johnny no more!
L'astuce consiste à faire en sorte que votre classe implémente un interface qui expose uniquement ce qui doit être exposé:
[IEmployee
class]
Option Explicit
Public Property Get Name() As String : End Property
Public Property Get Age() As Integer : End Property
Et maintenant vous faites Employee
implémenterIEmployee
- la classe finale pourrait ressembler à ceci:
[Employee
class]
'@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
Notez que la méthode Create
renvoie maintenant l'interface, et l'interface ne le fait pas expose le Property Let
membres? Le code appelant peut ressembler à ceci:
Dim empl As IEmployee
Set empl = Employee.Create("Immutable", 42)
Et puisque le code client est écrit sur l'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 Self
getter, ni aucun des Property Let
mutateurs: au lieu de travailler avec la classe "concrete" Employee
, le reste du code peut fonctionner avec l'interface "abstraite" IEmployee
et profiter d'un objet polymorphe immuable.
En utilisant le truc
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 constater, le constructeur New_ est appelé pour créer et définir les membres privés de la classe (comme init). Le seul problème est que, si elle est appelée sur une instance non statique, elle réinitialise le membre privé. mais cela peut être évité en plaçant un drapeau.