If you’ve ever exported a VBA class module and opened it in Notepad, you’ve probably already seen this:
VERSION 1.0 CLASS
MultiUse = -1 ‘True
Attribute VB_Name = “Class1”
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
These attributes unlock the true OOP potential of VBA.
You know how every UserForm comes with a “default instance” for free? That’s because user forms have this attribute set to True, and that instructs VBA to create a global-scope object named after the type, so you can do this:
Without even creating an instance of MyForm. That’s not very OOP though – in fact it’s pretty much anti-OOP, since by doing that you’re not creating any objects… So what’s the use of this attribute in an OOP discussion? Keep reading.
VBA classes are Public, not creatable. This means when you reference a VBA project, you can see and use the classes in that project, but you can’t create instances of them. You need a way to expose functionality to the referencing VBA project, to return instances of such a class. Oh sure, you could add a standard module and expose a public function that does it – but standard modules don’t quite encapsulate their members, and it’s up to the client code to properly qualify function calls (e.g. FactoryModule.CreateMyClass). A better option is to create a dedicated object whose sole responsibility is to create objects of a given type – enter the factory patern.
In OOP design patterns, factories are often combined with the Singleton pattern – after all, there only ever needs to be one single instance of a factory class. Given that the class can’t be created by the client code with the New keyword, that’s precisely what setting the VB_PredeclaredId attribute to True will do.
Say you have a Car class, with Make, Model and Manufacturer properties. It wouldn’t make sense for any of these properties to be changed after they’re set, right?
Private Type TCar Make As Integer Model As String Manufacturer As String End Type Private this As TCar
Public Property Get Model() As String Model = this.Model End Property Friend Property Let Model(ByVal value As String) this.Model = value End Property '...other properties
The Friend access modifier makes the Model property immutable, because client code located outside the VBA project this Car class is defined in, simply won’t see the Property Let member. However a CarFactory class defined in the same VBA project can:
Public Function Create(ByVal carMake As Integer, ByVal carModel As String, ByVal carManufacturer As String) As Car Dim result As New Car result.Make = carMake result.Model = carModel result.Manufacturer = carManufacturer Set Create = result End Function
Because this CarFactory class has a PredeclaredId, the referencing VBA code can do this:
Dim myCar As Car Set myCar = CarFactory.Create(2016, "Civic", "Honda")
And then the myCar object can’t be turned into a 2014 Honda Fit – not even by accident.
Future versions of Rubberduck will make it easy to set a class’ PredeclaredId attribute, and might actually provide tools to automate the creation of factories.