I want to make a function that accepts a class exemplar/class name and returns a collection of a certain number of exemplars of said class, initalized and populated. So far I have come up with this solution:
Function getCollectionObj(className as string) As Collection
Dim obj As Variant
Dim result As Collection
Dim objType As String
Dim I
For I = 1 To 5
'selecting the type of a class based on string name
Select Case className
Case "Cls1"
Set obj = New Cls1
Case "Cls2"
Set obj = New Cls2
Case "DocumentStart"
Set obj = New Cls3
End Select
'some code handling the exemplar of the object.
result.add obj
Next I
Set getCollectionObj = result
End Function
The problem with this is I have to explicitly put every class into select case
and that the code has to select and declare the cerain class every time, even though all object in the collection should be the exemplars of the same class. How do I improve it?
Edit: my testing code (gives "object required error")
Main module:
Private Function getCollectionObj(ByVal factory As IObjectFactory) As Collection
Dim I
For I = 1 To 5
result.Add factory.Create
Next I
End Function
Sub test()
Dim var As Object
Set var = getCollectionObj(New Class1ObjectFactory)
End Sub
IObjectFactory class:
Option Explicit
Public Function Create() As Object
End Function
Cls1Factory class (the right dropdown shows "create" when the function is selected):
Option Explicit
Implements IObjectFactory
Private Function IObjectFactory_Create() As Object
' "Object required" error here
Set IObjectFactory_Create = New Cls1
End Function
Cls1 class:
Option Explicit
Public I As String
In MyClass there is one method my_method which takes one more argument apart from self. In class Person, MyClass is also used so that is imported. In method display() object of MyClass is created. Then the my_method() method of class MyClass is called and object of Person class is passed as parameter.
You can pass the class as parameter.
We have a method coypObject() which accepts an object of the current class and initializes the instance variables with the variables of this object and returns it. In the main method we are instantiating the Student class and making a copy by passing it as an argument to the coypObject() method.
As Mankarse says, one solution would be to use a template to pass your class to the generator. Nevertheless, if ItemGenerator cannot be templated or if you do not want to use the templates here, you can still pass a function as you suggest by using either std::function or boost::function.
Ideally, you don't (to answer the question as titled) - that way your code doesn't silently break when a class gets renamed, for example.
There's a better way, one that gives you an immediate compile-time error as soon as something breaks, and that works nicely with static code analysis and refactoring tools, such as Rubberduck - full disclosure, that's my website, and I manage the Rubberduck open-source project.
You could formalize the task of creating an instance of a class by defining an abstract factory interface. Add a new class module, call it IObjectFactory
:
Option Explicit
Public Function Create() As Object
End Function
Now add a new class module, call it Cls1Factory
and make it implement the abstract factory interface:
Option Explicit
Implements IObjectFactory
Private Function IObjectFactory_Create() As Object
Set IObjectFactory_Create = New Cls1
End Function
Add a new class module, call it Cls2Factory
and make it also implement the abstract factory interface:
Option Explicit
Implements IObjectFactory
Private Function IObjectFactory_Create() As Object
Set IObjectFactory_Create = New Cls2
End Function
Add another implementation that can create Cls3
instances, call it Cls3Factory
.
Then change your function's signature as follows:
Public Function getCollectionObj(ByVal factory As IObjectFactory) As Collection
Now the entire Select Case
block becomes this single statement:
result.Add factory.Create
The calling code that used to do this:
Set things1 = getCollectionObj("Cls1")
Set things2 = getCollectionObj("Cls2")
Set things3 = getCollectionObj("Cls3")
Now needs to do this:
Set things1 = getCollectionObj(New Cls1Factory)
Set things2 = getCollectionObj(New Cls2Factory)
Set things3 = getCollectionObj(New Cls3Factory)
The function can now create a collection of 5 instances of any class you want, without modifying it at all: if you need to support a new class, simply implement a new factory for it, and then pass it as a parameter to the function.
RE: EDIT
Rubberduck's static code analysis can help you avoid a ton of traps, exactly like those in your edited snippet: the function return value isn't assigned, result variable is accessed before it's initialized, etc.:
The "object required" error is signaled by its underlying causes, here in cascading order leading to this specific error:
Option Explicit
not being specified has allowed the code to execute with result
not being declared. Since it's not declared, it's allocated as a Variant/Empty
until it's assigned... except it never is, and when a member call (.Add
) is tentatively being made against that variant, that's when VBA screams at run-time and says "object required", because an object is required for that member call to be valid. But all VBA is seeing is a Variant/Empty
pointer it just created on-the-spot.
The run-time error is raised after the .Create
factory method call returns, but before the returned object is passed as a parameter to an accidentally late-bound .Add
member call. Execution hasn't entered the .Add
method, VBA hasn't successfully dereferenced a Collection
object from the result
variable; but because the debugger doesn't let us step through operators, when we click "DEBUG" we're taken back on the failing statement, and the entire result.Add factory.Create
statement is highlighted (as opposed to just the result.Add
call that's the actually-failing operation here).
Because there are two statements on the same line, the debugger is not quite in the exact state it should be in right now. If we hit F8, we're taken back into the factory method, and if we don't realize what's going on then it's easy to let the debugger lead us astray into investigating a completely sane execution path.
Separate the statements:
Dim o As Object
Set o = factory.Create '<~ no problem here!
result.Add o '<~ object required
Now the debugger enters break mode with the correct failing statement highlighted.
The working code, for completeness' sake:
Option Explicit
Public Sub test()
Dim var As Collection
Set var = getCollectionObj(New Class1ObjectFactory)
Debug.Print var.Count
End Sub
Private Function getCollectionObj(ByVal factory As IObjectFactory) As Collection
Dim result As Collection
Set result = New Collection
Dim I As Long
For I = 1 To 5
result.Add factory.Create
Next I
Set getCollectionObj = result
End Function
FWIW, if you already have a license or are willing to buy a license for vbWatchDog Error Handler, one of the perk is the ability to create objects from strings:
Set myClass1 = ErrEx.LiveCallstack.ProjectCreateClassInstance("Class1")
Introduced in version 3.8.6
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With