Can we use Interfaces and Events together at the same time?

前端 未结 4 1997
情歌与酒
情歌与酒 2020-12-04 09:08

I\'m still trying to wrap my head around how Interfaces and Events work together (if at all?) in VBA. I\'m about to build a large application in Microsoft Access, and I want

4条回答
  •  有刺的猬
    2020-12-04 09:50

    An interface is, strictly speaking and only in OOP terms, what an object exposes to the outside world (i.e. its callers/"clients").

    So you can define an interface in a class module, say ISomething:

    Option Explicit
    Public Sub DoSomething()
    End Sub
    

    In another class module, say Class1, you can implement the ISomething interface:

    Option Explicit
    Implements ISomething
    
    Private Sub ISomething_DoSomething()
        'the actual implementation
    End Sub
    

    When you do exactly that, notice how Class1 doesn't expose anything; the only way to access its DoSomething method is through the ISomething interface, so the calling code would look like this:

    Dim something As ISomething
    Set something = New Class1
    something.DoSomething
    

    So ISomething is the interface here, and the code that actually runs is implemented in the body of Class1. This is one of the fundamental pillars of OOP: polymorphism - because you could very well have a Class2 that implements ISomething in a wildly different way, yet the caller wouldn't ever need to care at all: the implementation is abstracted behind an interface - and that's a beautiful and refreshing thing to see in VBA code!

    There are a number of things to keep in mind though:

    • Fields are normally considered as implementation details: if an interface exposes public fields, implementing classes must implement a Property Get and a Property Let (or Set, depending on the type) for it.
    • Events are considered implementation details, too. Therefore they need to be implemented in the class that Implements the interface, not the interface itself.

    That last point is rather annoying. Given Class1 that looks like this:

    '@Folder StackOverflowDemo
    Public Foo As String
    Public Event BeforeDoSomething()
    Public Event AfterDoSomething()
    
    Public Sub DoSomething()
    End Sub
    

    The implementing class would look like this:

    '@Folder StackOverflowDemo
    Implements Class1
    
    Private Sub Class1_DoSomething()
        'method implementation
    End Sub
    
    Private Property Let Class1_Foo(ByVal RHS As String)
        'field setter implementation
    End Property
    
    Private Property Get Class1_Foo() As String
        'field getter implementation
    End Property
    

    If it's any easier to visualize, the project looks like this:

    Rubberduck Code Explorer

    So Class1 might define events, but the implementing class has no way of implementing them - that's one sad thing about events and interfaces in VBA, and it stems from the way events work in COM - events themselves are defined in their own "event provider" interface; so a "class interface" can't expose events in COM (as far as I understand it), and therefore in VBA.


    So the events must be defined on the implementing class to make any sense:

    '@Folder StackOverflowDemo
    Implements Class1
    Public Event BeforeDoSomething()
    Public Event AfterDoSomething()
    
    Private foo As String
    
    Private Sub Class1_DoSomething()
        RaiseEvent BeforeDoSomething
        'do something
        RaiseEvent AfterDoSomething
    End Sub
    
    Private Property Let Class1_Foo(ByVal RHS As String)
        foo = RHS    
    End Property
    
    Private Property Get Class1_Foo() As String
        Class1_Foo = foo
    End Property
    

    If you want to handle the events Class2 raises while running code that implements the Class1 interface, you need a module-level WithEvents field of type Class2 (the implementation), and a procedure-level object variable of type Class1 (the interface):

    '@Folder StackOverflowDemo
    Option Explicit
    Private WithEvents SomeClass2 As Class2 ' Class2 is a "concrete" implementation
    
    Public Sub Test(ByVal implementation As Class1) 'Class1 is the interface
        Set SomeClass2 = implementation ' will not work if the "real type" isn't Class2
        foo.DoSomething ' runs whichever implementation of the Class1 interface was supplied
    End Sub
    
    Private Sub SomeClass2_AfterDoSomething()
    'handle AfterDoSomething event of Class2 implementation
    End Sub
    
    Private Sub SomeClass2_BeforeDoSomething()
    'handle BeforeDoSomething event of Class2 implementation
    End Sub
    

    And so we have Class1 as the interface, Class2 as the implementation, and Class3 as some client code:

    Rubberduck Code Explorer

    ...which arguably defeats the purpose of polymorphism, since that class is now coupled with a specific implementation - but then, that's what VBA events do: they are implementation details, inherently coupled with a specific implementation... as far as I know.

提交回复
热议问题