In my last post I mentioned the ability to create a factory at run-time by accessing the VBComponents collection. To do this you must first create a Reference to:
Microsoft Visual Basic for Applications Extensibility 5.3
(C:\Program Files\Common Files\Microsoft Shared\VBA\VB6\VBE6.DLL)
Having done this you now have access to the VBIDE library and, in particular, the VBComponents collection.
Below is a Complex Factory. Please note that some lines continue onto the next line and I’ve not included line continuation characters, but then, you never blindly copy and paste from a HOWTO, do you!
Public Function CreateFactory() As Variant
Dim str As String: str = BuildString("IAnimal")
Private Function BuildString(ByRef interfaceName As String) As String
Dim str As String
str = "Public Function Instantiate(ByRef className As String, ByRef varState As Variant) As " & interfaceName & vbCrLf
str = str & "Select Case className" & vbCrLf
' go through all classes and find the ones that implement interfaceName
Dim comp As VBIDE.VBComponent
For Each comp In VBE.ActiveVBProject.VBComponents
If comp.Type = vbext_ct_ClassModule Then
If comp.CodeModule.Find("Implements " & interfaceName, 1, 1, comp.CodeModule.CountOfDeclarationLines + 1, 1) Then
str = str & vbTab & "Case """ & comp.Name & """" & vbCrLf
str = str & vbTab & vbTab & "Set Instantiate = New " & comp.Name & vbCrLf
str = str & vbTab & vbTab & "Call Instantiate.Constructor(varState)" & vbCrLf
str = str & "End Select" & vbCrLf
str = str & "End Function"
BuildString = str
Private Sub WriteCode(ByRef str As String)
Dim cls As VBIDE.CodeModule
Set cls = VBE.ActiveVBProject.VBComponents("Factory").CodeModule
' remove any existing code lines
If cls.Find("Instantiate", 1, 1, 2, 1) Then
Dim start As Long: start = cls.ProcStartLine("Instantiate", vbext_pk_Proc)
Dim count As Long: count = cls.ProcCountLines("Instantiate", vbext_pk_Proc)
Call cls.DeleteLines(start, count)
' write the new code lines
As you can see, the process in creating the complex factory is two-step: first create a string of VBA code and then write it to a code module – see the procedure “CreateFactory”. Creating the string (function “BuildString”) should be pretty simple to understand. Note that I’m create a polymorphic factory that creates instances of all classes that conform to the “IAnimal” interface I have created. To find these classes I loop through all of the VBComponents and only examine the class modules (VBComponent.Type = vbext_ct_ClassModule). For each class module, I’m only interested in the ones that contain the string “Implements IAnimal” in its declaration lines. Having passed these conditions, I then concatenate the string I’m building with the necessary “meat” in the Select Case sandwich. Note that my interface definition for IAnimal includes a procedure “Constructor” that accepts a Variant as an input (“varState”). Each class that implements IAnimal will contain its own code that operates on the Variant input in order to set the state of the instantiated object before this newly created object is returned by the factory. In practice, being a Variant, varState could be as simple as a string containing the value for one of the class’s fields or, it could be a complex array containing many different objects that are used in the creation of an object that conforms to the class.
Having created the string “str” and passed it back to CreateFactory, the latter then goes on to write the code in the CodeModule of the dedicated “Factory” module. As I stated in my previous post on the Simple Factory, “Factory” could be a Standard Module, a Class Module or a Static Class Module. Before writing the code, however, “WriteCode” removes any existing code in “Factory”. it does this because “CreateFactory” is designed to be called when the Access/Excel file is opened. In Access, this is achieved by creating a Macro that runs “CreateFactory” upon opening of the file. In Excel, this is achieved by calling “CreateFactory” when the Workbook_Open event is triggered (in the “ThisWorkbook” code module).
And that’s it really. Although it’s called “Complex” the only thing complex about it is the unfamiliarity of working with the VBComponents collection. In reality it’s quite simple when you understand what’s being done. The beauty of the above Complex Factory comes when expanding the number of classes that implement the chosen interface (IAnimal in my example). All the developer need do is create the class module and save the file. The next time the file is opened, the above code will capture this new class and include it in the factory – the developer does not need to worry about manually updating the Factory object.