Is there a way to write an equality test for a VBA class with private members without exposing knowledge of the existence of those private members?

前端 未结 3 2224

I do a fair amount of Excel VBA programming, but not a lot of it is object-oriented. Here is something that comes up every now and then that bugs me, and I\'m wondering if t

相关标签:
3条回答
  • 2020-12-31 23:11

    I know this is an old post, but I would still like to answer. The following code exposes no private variables to the user and still allows comparison between objects:

    'MyClass
    Private m_data As Double
    Private Const epsilon As Double = 0.00001
    
    
    Public Function IsEqual(ByRef cls As MyClass) As Boolean
        IsEqual = cls.Comparator(m_data)
    End Function
    
    Public Function Comparator(ByVal d As Double) As Boolean
        Comparator = Abs((d - m_data)) < epsilon
    End Function
    
    Property Get MyString() As String
        MyString = Chr((CLng(m_data) Mod 26) + 65) & Chr((CLng(m_data * 100) Mod 26) + 65)
    End Property
    
    Property Let MyString(s As String)
        If Len(s) = 0 Then
            m_data = epsilon / 10
            Exit Property
        End If
        m_data = Len(s) / (Len(s) - Len(Replace(s, " ", "")))
    End Property
    
    'Module
    Public Sub mySub2()
    Dim s As String
    Dim obj1 As MyClass
    Dim obj2 As MyClass
    
        Set obj1 = New MyClass
        Set obj2 = New MyClass
    
        s = InputBox("Enter a sentence:")
        Debug.Print s
    
        obj1.MyString = s
        obj2.MyString = ""
    
        Debug.Print "Does Obj1(" & obj1.MyString & ") = Obj2(" & obj2.MyString & ")?  " & obj1.IsEqual(obj2)
    
        obj2.MyString = s
    
        Debug.Print "Does Obj1(" & obj1.MyString & ") = Obj2(" & obj2.MyString & ")?  " & obj1.IsEqual(obj2)
    
    End Sub
    

    With the following output:

    The quick brown fox jumps over the lazy moon
    Does Obj1(GE) = Obj2(AA)?  False
    Does Obj1(GE) = Obj2(GE)?  True
    
    0 讨论(0)
  • 2020-12-31 23:18

    I would write the class like this

    Private mdhidden1_ As Double
    Private mdhidden2_ As Double
    
    Public Property Get hidden1_() As Double
    
        hidden1_ = mdhidden1_
    
    End Property
    
    Public Property Get hidden2_() As Double
    
        hidden2_ = mdhidden2_
    
    End Property
    
    Private Sub Class_Initialize()
    
        'some method of setting variables private to the class
        mdhidden1_ = 1
        mdhidden2_ = 2
    
    End Sub
    
    Public Property Get IsEquivalent(clsCompare As C) As Boolean
    
        IsEquivalent = Me.hidden1_ = clsCompare.hidden1_ And Me.hidden2_ = clsCompare.hidden2_
    
    End Property
    

    If you're forced to expose knowledge of the member anyway, you may as well make it a read-only property (Get, but no Let). Then you can make the IsEquivalent boolean property inside the class.

    0 讨论(0)
  • 2020-12-31 23:28

    After looking into this again, I have an answer, but it's not exactly satisfying. As with most OOP in VBA, it involves using an appropriate interface, but the fact that every class (and every interface) has to go in a separate class module makes it a pretty clunky way to do things. So here goes:

    Say I have a class called MyClass (which I was calling 'C' above but I'm now calling 'MyClass' to make this clearer.)

    The thing to do would be to define an interface that I would actually use in my code that exposed just the things about MyClass that I wanted truly public. Say this code is in a class module called IMyClass:

    Public Function calcSomething()
    
    End Function
    
    Public Function equal(cinst As IMyClass) As Boolean
    
    End Function
    

    Then I have my class called MyClass, defined with this code in a class module:

    Implements IMyClass
    
    Private hidden1_ As Double
    Private hidden2_ As Double
    
    Public Sub init(h1 As Double, h2 As Double)
        hidden1_ = h1
        hidden2_ = h2
    End Sub
    
    Public Function equalDef(hidden1 As Double, hidden2 As Double) As Boolean
        equalDef = (hidden1_ = hidden1 And hidden2_ = hidden2)
    End Function
    
    Private Function IMyClass_calcSomething() As Variant
        IMyClass_calcSomething = hidden1_ * hidden2_
    End Function
    
    Private Function IMyClass_equal(cinst As IMyClass) As Boolean
        If TypeOf cinst Is MyClass Then
            Dim asMyClass As MyClass
            Set asMyClass = cinst
    
            IMyClass_equal = asMyClass.equalDef(hidden1_, hidden2_)
        End If
    End Function
    

    And here is some code that would go in a regular module:

    Public Function mkMyClass(h1 As Double, h2 As Double) As IMyClass
        Dim ret As MyClass
        Set ret = New MyClass
    
        Call ret.init(h1, h2)
    
        Set mkMyClass = ret
    End Function
    
    Public Sub useMyClass()
        Dim mc1 As IMyClass
        Set mc1 = mkMyClass(42, 99)
    
        Dim mc2 As IMyClass
        Set mc2 = mkMyClass(42, 99)
    
        Dim mc3 As IMyClass
        Set mc3 = mkMyClass(99, 42)
    
        Debug.Print mc1.calcSomething
        Debug.Print mc1.equal(mc2)
    
        Debug.Print mc3.calcSomething
        Debug.Print mc3.equal(mc2)
    End Sub
    

    In the 'useMyClass' routine, you can verify that the various mc1, mc2, and mc3 variables can't see the 'init' or 'equalDef' methods of MyClass. They can only see the 'calcSomething' and 'equal' methods that are part of the interface.

    So, question officially but unsatisfyingly answered, I guess. At least it gives me a chance to repeat "OOP in VBA is a PITA (TM)"...

    Here are some related stackoverflow answers:

    VBA inheritance, analog of super

    Is there a way to overload the constructor / initialize procedure for a class in VBA?

    0 讨论(0)
提交回复
热议问题