So far, this blog has not opined on VBA inheritance or more accurately the simulation of inheritance; time to rectify that here. We could use composition along with a standard naming convention to mimic inheritance as found in languages such as C++ and C#. But we can use the default member trick to make the syntax even tighter.
C# and C++ Inheritance and the Fragile Base Class Problem
Before people complain that follows is not like the true inheritance found in C# and C++ I would say that what follows has some superior benefits. C++ and C# inheritance breaks encapsulation because derived classes can gain access to the base class's members. Also, once derived classes are written it becomes difficult to re-engineer the base class without breaking the derived classes, this is known as the fragile base class problem. Indeed, languages such as C# have to invent keywords such sealed to prevent derived classes inspecting a base class's private variables.
VBA can use Composition and the Default Member Trick to simulate Inheritance
What follows is VBA composition dressed up as inheritance by using the default member trick to tighten the syntax. So it has all the benefits of composition over inheritance
Code Listings
In the following listings, to effect Class1 and Class2 as intended it is not sufficient to cut and paste into VBA environment, it is also required to export to disk, load into editor, edit the file, save and then re-import; details are given in the code.
Class1 Listing
Option Explicit
Private moBase As Class2
'* To do the default member trick
'* 1) Export this module to disk;
'* 2) load into text editor;
'* 3) uncomment line with text Attribute Item.VB_UserMemId = 0 ;
'* 4) save the file back to disk
'* 5) remove or rename original file from VBA project to make room
'* 6) Re-import saved file
Private Sub Class_Initialize()
Set moBase = New Class2
End Sub
Public Function Base() As Class2
'Attribute Item.VB_UserMemId = 0
Set Base = moBase
End Function
Public Function Foo() As String
Foo = "Class1.Foo:" & 23
End Function
Public Function Common() As String
Common = "Class1.Common"
End Function
Class2 Listing
Option Explicit
Private moBase As Class3
'* To do the default member trick
'* 1) Export this module to disk;
'* 2) load into text editor;
'* 3) uncomment line with text Attribute Item.VB_UserMemId = 0 ;
'* 4) save the file back to disk
'* 5) remove or rename original file from VBA project to make room
'* 6) Re-import saved file
Private Sub Class_Initialize()
Set moBase = New Class3
End Sub
Public Function Base() As Class3
'Attribute Item.VB_UserMemId = 0
Set Base = moBase
End Function
Public Function Bar() As String
Bar = "Class2.Bar:" & 42
End Function
Public Function Common() As String
Common = "Class2.Common"
End Function
Class3 Listing
Option Explicit
Public Function Baz() As String
Baz = "Class3.Baz:" & -5
End Function
Public Function Common() As String
Common = "Class3.Common"
End Function
Test Module
So hopefully you have imported the above classes and edited Class1 and Class2 necessarily to effect the default member trick. So now one can see how this works using the test code below. One can access a class's base class simply by adding .Base qualifier but we can tighten the syntax to just a pair of round brackets using the default member trick.
Option Explicit
Sub Test()
Dim oClass1 As Class1
Set oClass1 = New Class1
'* without using default member trick
'* access common method method
Debug.Print oClass1.Common '* prints Class1.Common
Debug.Print oClass1.Base.Common '* prints Class2.Common
Debug.Print oClass1.Base.Base.Common '* prints Class3.Common
'* using default member trick (Attribute Item.VB_UserMemId = 0)
'* access common method method
Debug.Print oClass1.Common '* prints Class1.Common
Debug.Print oClass1().Common '* prints Class2.Common
Debug.Print oClass1()().Common '* prints Class3.Common
'* access a base class's unique method (using default member trick)
Debug.Print oClass1.Foo '* prints Class1.Foo:23
Debug.Print oClass1().Bar '* prints Class2.Bar:42
Debug.Print oClass1()().Baz '* prints Class3.Baz:-5
End Sub
No need for Virtual or Overides keywords
Of note is the test code above that calls the Common method. So you see how without the virtual and overrides keywords found in other languages we can in fact easily determine which implementation of Common in the 'inheritance' chain to call by changing the number of bracket pairs.
Final Thoughts
I felt the need to post this because whilst investigating COM Type libraries, I thought I had found a secret way to aggregate a VBA class. (Aggregation is COM's reuse feature.) That turned our to be a mirage (details to follow in a separate post, maybe). I wanted somewhere on this blog to demonstrate how VBA developers can use something very conceptually close to inheritance in their designs.
Must replace Item in Attribute Item.VB_UserMemId = 0, with Base.
ReplyDelete