ArcherBird
ArcherBird

Reputation: 2134

How to handle a function where the returned value type is not known at run-time (Object or Non-Object)

This question centers around the return value of a call to CallByName. I have a class called PropertyPtr which is meant to act as a generic pointer to an object property. It holds a reference to an Object, and the name of one of its properties. It exposes a Getter and Setter method.

PropertyPtr:

Option Explicit

Public Obj As Object
Public PropertyName As String

Public Sub Setter(Val As Variant)
    If IsObject(Val) Then
        CallByName Me.Obj, Me.PropertyName, VbSet, Val
    Else
        CallByName Me.Obj, Me.PropertyName, VbLet, Val
    End If
End Sub

Public Function Getter() As Variant
    If IsObject(CallByName(Me.Obj, Me.PropertyName, VbGet)) Then
        Set Getter = CallByName(Me.Obj, Me.PropertyName, VbGet)
    Else
        Getter = CallByName(Me.Obj, Me.PropertyName, VbGet)
    End If
End Function

In the Getter, my CallByName could return a object or not. But the only way I can see to test if the CallByName value will be an object is to end up running it twice - once to test inside an IsObject and then again to get a reference to the value. The only other way I could see doing this is trapping for an error. Then, you at least only SOMETIMES run the CallByName twice.

My question is: is there some other way to do this without running CallByName twice?

Upvotes: 0

Views: 129

Answers (1)

Ambie
Ambie

Reputation: 4977

Okay, so if you really want to follow that route then I think you'll have to set an IsObj flag - probably at the point you set the property name.

However, I'd still maintain that using a Variant for either an Object or primitive type isn't a great idea, and the CallByName() function in this context comes with issues. My hesitations are that performance will be diminished and you'll have quite a task to keep the property strings aligned with the property names (should you update things in the future).

It is possible to implement a Mediator Pattern in VBA and I do feel you should consider this route. Below is a really basic example of how you could do it. I haven't bothered with an interface for the mediator, but I have created an interface for my participating classes (to cover the possibility that you're dealing with your own 'groups' of classes).

Mediator class (called cMediator):

Option Explicit

Private mSweets As Collection

Private Sub Class_Initialize()
    Set mSweets = New Collection
End Sub

Public Sub RegisterSweet(sweet As ISweet)
    Set sweet.Mediator = Me
    mSweets.Add sweet
End Sub

Public Sub SendSugarLimit(limit As Long)
    Dim sweet As ISweet

    For Each sweet In mSweets
        sweet.ReceiveSugarLimit limit
    Next
End Sub

Public Sub ReceiveMeltingAlert(offender As String)
    Dim sweet As ISweet

    For Each sweet In mSweets
        sweet.ReceiveEatNow offender
    Next
End Sub

Participating classes Interface (called ISweet):

Option Explicit

Public Property Set Mediator(RHS As cMediator)
End Property

Public Sub ReceiveSugarLimit(g_perDay As Long)
End Sub

Public Sub ReceiveEatNow(offender As String)
End Sub

My two participating classes (cQtySweet and cWeightSweet):

Option Explicit
Implements ISweet

Public Name As String
Public SugarPerItem As Long
Public CanMelt As Boolean
Private pMediator As cMediator

Public Sub OhNoItsMelting()
    pMediator.ReceiveMeltingAlert Name
End Sub

Private Property Set ISweet_Mediator(RHS As cMediator)
    Set pMediator = RHS
End Property

Private Sub ISweet_ReceiveEatNow(offender As String)
    If CanMelt Then Debug.Print offender & " is melting. Eat " & Name & "s now!"
End Sub

Private Sub ISweet_ReceiveSugarLimit(g_perDay As Long)
    Dim max As Long

    max = g_perDay / SugarPerItem
    Debug.Print "Max " & Name & "s: " & max & "."
End Sub

Option Explicit
Implements ISweet

Public Name As String
Public SugarPer100g As Long
Public CanMelt As Boolean
Private pMediator As cMediator

Public Sub OhNoItsMelting()
    pMediator.ReceiveMeltingAlert Name
End Sub
Private Property Set ISweet_Mediator(RHS As cMediator)
    Set pMediator = RHS
End Property

Private Sub ISweet_ReceiveEatNow(offender As String)
    If CanMelt Then Debug.Print offender & " is melting. Eat " & Name & " now!"
End Sub

Private Sub ISweet_ReceiveSugarLimit(g_perDay As Long)
    Dim max As Long

    max = g_perDay / (SugarPer100g / 100)
    Debug.Print "Max " & Name & ": " & max & "g."
End Sub

Module Code:

Public Sub RunMe()
    Dim m As cMediator
    Dim qtySweet As cQtySweet
    Dim weightSweet As cWeightSweet

    Set m = New cMediator

    Set qtySweet = New cQtySweet
    With qtySweet
        .Name = "Gobstopper"
        .SugarPerItem = 5
        .CanMelt = False
    End With
    m.RegisterSweet qtySweet

    Set qtySweet = New cQtySweet
    With qtySweet
        .Name = "Wine Gum"
        .SugarPerItem = 2
        .CanMelt = True
    End With
    m.RegisterSweet qtySweet

    Set weightSweet = New cWeightSweet
    With weightSweet
        .Name = "Sherbert"
        .SugarPer100g = 80
        .CanMelt = False
    End With
    m.RegisterSweet weightSweet

    Set weightSweet = New cWeightSweet
    With weightSweet
        .Name = "Fudge"
        .SugarPer100g = 50
        .CanMelt = True
    End With
    m.RegisterSweet weightSweet

    'Blasted government has reduced sugar allowance.
    Debug.Print "New govt. limits..."
    m.SendSugarLimit 200

    'Phew what a scorcher - the fudge is melting in my pocket.
    Debug.Print "Sweet alarm..."
    weightSweet.OhNoItsMelting
End Sub

… and the output looks like this:

New govt. limits...

Max Gobstoppers: 40.

Max Wine Gums: 100.

Max Sherbert: 250g.

Max Fudge: 400g.

Sweet alarm...

Fudge is melting. Eat Wine Gums now!

Fudge is melting. Eat Fudge now!

Upvotes: 1

Related Questions