Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I pass class names as arguments?

Tags:

vba

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
like image 949
Igor Cheglakov Avatar asked Sep 15 '20 18:09

Igor Cheglakov


People also ask

How do you pass a class name as an argument in Python?

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.

Can we pass class name as parameter in Java?

You can pass the class as parameter.

How do you pass an argument as a class?

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.

How do you pass a class name as a parameter in C++?

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.


Video Answer


2 Answers

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.:

OP's revision 2 code under ducky scrutiny

The "object required" error is signaled by its underlying causes, here in cascading order leading to this specific error:

  • 'Option Explicit' is not specified in 'Module1'.
  • Local variable 'result' is not declared.
  • Variable 'result' is used but not assigned.

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
like image 154
Mathieu Guindon Avatar answered Nov 10 '22 00:11

Mathieu Guindon


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

like image 24
this Avatar answered Nov 10 '22 01:11

this