VBA: Initialize object with values?
Asked Answered
M

3

27

I want to be able to initialize a

CArticle having the following properties:

Private pNumber As String
Private pQuantity As Double

with either empty, pre-defined or current values. How can I achieve this? I'm thinking something along the lines of:

New empty CArticle

pNumber
pQuantity

New dummy CArticle

pNumber
pQuantity = 99999

New init CArticle(number, quantity)

pNumber = number
pQuantity = quantity
Mesocarp answered 23/3, 2012 at 14:16 Comment(3)
Possible duplicate of Pass arguments to Constructor in VBAWakerly
Yes, it is exactly a duplicate of #15224613Caridadcarie
Matt's answer is the way to go in these cases see his answer hereMaiduguri
A
38

It is a pain in the neck but this is the only way to do it.

File CArticle

Option Explicit

Private pNumber As String
Private pQuantity As Double

Private Sub Class_Initialize()
    pNumber = vbNullString
    pQuantity = 0
End Sub

Public Sub InitializeWithValues(ByVal number As String, ByVal quantity As Double)
    pNumber = number
    pQuantity = quantity
End Sub

Public Sub InitializeDefaultValues()
    pNumber = vbNullString
    pQuantity = 99999
End Sub

and in the calling module

Dim art As New CArticle       ' Initialize value to empty
art.InitializeWithValues "Bowtie", 100     ' and assign values

Set art = New CArticle        ' Initialize values to empty
art.InitializeDefaultValues   ' Initialize values to default
Andriette answered 23/3, 2012 at 14:27 Comment(1)
It might be useful to create a InitializeFromRange(ByVal r as Range) method in order to pull values from a worksheet table.Andriette
C
3

If anyone gets here by a search, as did I. I recommend looking instead at the answers in StackOverFlow: Pass arguments to Constructor in VBA

This is not my answer, it came from Bgusach. I am including it here because I see that a useful answer is more than just a link.

Pass arguments to Constructor in VBA

Here's a little trick I'm using lately and brings good results. Bgusach would like to share with those who have to fight often with VBA.

1.- Implement a public initiation subroutine in each of your custom classes. I call it InitiateProperties throughout all my classes. This method has to accept the arguments you would like to send to the constructor.

2.- Create a module called factory, and create a public function with the word "Create" plus the same name as the class, and the same incoming arguments as the constructor needs. This function has to instantiate your class, and call the initiation subroutine explained in point (1), passing the received arguments. Finally returned the instantiated and initiated method.

Example:

Let's say we have the custom class Employee. As the previous example, is has to be instantiated with name and age.

This is the InitiateProperties method. m_name and m_age are our private properties to be set.

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

    m_name = name
    m_age = age

End Sub

And now in the factory module:

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

And finally when you want to instantiate an employee

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

Especially useful when you have several classes. Just place a function for each in the module factory and instantiate just by calling factory.CreateClassA(arguments), factory.CreateClassB(other_arguments), etc.

EDIT

As stenci pointed out, you can do the same thing with a terser syntax by avoiding to create a local variable in the constructor functions. For instance the CreateEmployee function could be written like this:

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

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

End Function

Which is nicer.

Caridadcarie answered 25/11, 2019 at 22:6 Comment(0)
B
0

I know this is an old question, but I can at least bring runtime enforcement of using the Factory function to the table.

Basically, you can't pass arguments to a class initializer, so using code like somevar = New SomeUserClass("Some Initializer") doesn't work. Using a public create function is a nice alternative to bring initializer arguments into the fold, however it still allows the use of somevar = New SomeUserClass without penalty. In this case, you could set an initialized flag and check for it in each function, but this is also tedious.

The solution I propose below throws a runtime error if New SomeUserClass is used instead of the Factory function. This way, your exposed member functions can operate knowing the object is fully initialized before its code is allowed to execute.

' Factory.bas

Option Explicit

Private m_globalInitializer As Variant

' This could also just be a function
Public Property Get GlobalInitializer() As Variant
    If IsEmpty(m_globalInitializer) Then _
        Err.Raise 5, , "The factory function must be used to create an instance of this class"
        ' Error #5 is 'Invalid procedure call or argument'

    ' Coerce any type of value to be returned
    If IsObject(m_globalInitializer) Then
        Set GlobalInitializer = m_globalInitializer
    Else
        GlobalInitializer = m_globalInitializer
    End If

    ' Make this getter a one-time use
    m_globalInitializer = vbEmpty
End Property

Public Function CreateSomeUserClass(ByVal somearg1 As String, _
    ByVal somearg2 As Object) As SomeUserClass

    ' You can set m_globalInitializer to anything your class initializer expects
    m_globalInitializer = Array(somearg1, somearg2)

    ' This is the only place this is allowed
    Set CreateSomeUserClass = New SomeUserClass 
End Function
' SomeUserClass.cls

Option Explicit

Private m_somevar1 As String
Private m_somevar2 As Object

Private Sub Class_Initialize()
    Dim args() As Variant
    args = GlobalInitializer ' This clears the global initializer as well
        ' allowing other objects to be factory created

    m_somevar1 = args(0)
    Set m_somevar2 = args(1)

    ' Continue rest of initialization, including calling other factories
    ' that use the GlobalInitializer idiom.
End Sub

With this, if New is ever called for your class outside of the factory function, you'll get a runtime error. There is no way for anything to set the private m_globalInitializer variable prior to using New, so your initializer arguments are enforced.

I hope this helps!

Bonanno answered 7/6, 2023 at 19:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.