Creating objects is something we do all the time. When we
Set foo = New Something, we create a new instance of the
Something class and assign that object reference to the
foo variable, which would have been declared locally with
Dim foo As Something.
Often, you wish to instantiate
Something with initial values for its properties – might look like this:
Dim foo As Something Set foo = New Something With foo .Bar = 42 .Ducky = "Quack" '... End With
Or, you could be fancy and make
Something have a
Self property that returns, well, the instance itself, like this:
Public Property Get Self() As Something Set Self = Me End Property
But why would we do that? Because then we can leverage the rather elegant
With New syntax:
Dim foo As Something With New Something .Bar = 42 .Ducky = "Quack" '... Set foo = .Self End With
The benefits are perhaps more apparent with a factory method:
Public Function NewSomething(ByVal initialBar As Long, ByVal initialDucky As String) As Something With New Something .Bar = initialBar .Ducky = initialDucky Set NewSomething = .Self End With End Function
See, no local variable is needed here, the
With block holds the object reference. If we weren’t passing that reference down the call stack by returning it to the caller, the
End With would have terminated that object. Not everybody knows that a
With block can own an object reference like this, using
With New. Without the
Self property, a local variable would be needed in order to be able to assign the return value, because a
With block doesn’t provide a handle to the object reference it’s holding.
Now the calling code can do this:
Dim foo As Something Set foo = Factories.NewSomething(42, "Quack")
NewSomething function is located in a standard module (.bas) named
Factories. The code would have also been legal without qualifying
NewSomething with the module name, but if someone is maintaining that code without Rubberduck to tell them by merely clicking on the identifier,
meh, too bad for them they’ll have to Shift+F2 (go to definition) on
NewSomething and waste time and break their momentum navigating to the
Factories module it’s defined in – or worse, looking it up in the Object Browser (F2).
Where to put it?
In other languages, objects can be created with a constructor. In VBA you can’t have that, so you use a factory method instead. Factories manufacture objects, they create things.
In my opinion, the single best place to put a factory method isn’t in a standard/procedural module though – it’s on the class itself. I want my calling code to look something like this:
Dim foo As Something Set foo = Something.Create(42, "Quack")
Last thing I want is some “factory module” that exposes a method for creating instances of every class in my project. But how can we do this? The
Create method can’t be invoked without an instance of the
Something class, right? But what’s happening here, is that the instance is being automatically created by VBA; that instance is named after the class itself, and there’s a
VB_Attribute in the class header that you need to tweak to activate it:
VERSION 1.0 CLASS BEGIN MultiUse = -1 'True END Attribute VB_Name = "Something" '#FunFact controlled by the "Name" property of the class module Attribute VB_GlobalNameSpace = False '#FunFact VBA ignores this attribute Attribute VB_Creatable = False '#FunFact VBA ignores this attribute Attribute VB_PredeclaredId = True '<~ HERE! Attribute VB_Exposed = False '#FunFact controlled by the "Instancing" property of the class module
The attribute is
VB_PredeclaredId, which is
False by default. At a low level, each object instance has an ID; by toggling this attribute value, you tell VBA to pre-declare that ID… and that’s how you get what’s essentially a global-scope free-for-all instance of your object.
That can be a good thing… but as is often the case with forms (which also have a predeclared ID), storing state in that instance leads to needless bugs and complications.
The real problem is that we really have two
interfaces here, and one of them (the factory) shouldn’t be able to access instance data… but it needs to be able to access the properties of the object it’s creating!
If only there was a way for a VBA class to present one interface to the outside world, and another to the
Create factory method!
VERSION 1.0 CLASS BEGIN MultiUse = -1 'True END Attribute VB_Name = "ISomething" Attribute VB_GlobalNameSpace = False Attribute VB_Creatable = False Attribute VB_PredeclaredId = False Attribute VB_Exposed = False Option Explicit Public Property Get Bar() As Long End Property Public Property Get Ducky() As String End Property
This would be some
ISomething class: an interface that the
Something class will implement.
Something class would look like this- Notice that it only exposes
Property Get accessors, and that the
Create method returns the object through the
VERSION 1.0 CLASS BEGIN MultiUse = -1 'True END Attribute VB_Name = "Something" Attribute VB_GlobalNameSpace = False Attribute VB_Creatable = False Attribute VB_PredeclaredId = True Attribute VB_Exposed = False Option Explicit Private Type TSomething Bar As Long Ducky As String End Type Private this As TSomething Implements ISomething Public Function Create(ByVal initialBar As Long, ByVal initialDucky As String) As ISomething With New Something .Bar = initialBar .Ducky = initialDucky Set Create = .Self End With End Function Public Property Get Self() As ISomething Set Self = Me End Property Public Property Get Bar() As Long Bar = this.Bar End Property Friend Property Let Bar(ByVal value As Long) this.Bar = value End Property Public Property Get Ducky() As String Ducky = this.Ducky End Property Friend Property Let Ducky(ByVal value As String) this.Ducky = value End Property Private Property Get ISomething_Bar() As Long ISomething_Bar = Bar End Property Private Property Get ISomething_Ducky() As String ISomething_Ducky = Ducky End Property
Friend properties would only be accessible within that project; if that’s not a concern then they could also be
Public, doesn’t really matter – the calling code only really cares about the
With Something.Create(42, "Quack") Debug.Print .Bar 'prints 42 .Bar = 42 'illegal, member not on interface End With
Here the calling scope is still tightly coupled with the
Something class though. But if we had a factory interface…
VERSION 1.0 CLASS BEGIN MultiUse = -1 'True END Attribute VB_Name = "ISomethingFactory" Attribute VB_GlobalNameSpace = False Attribute VB_Creatable = False Attribute VB_PredeclaredId = False Attribute VB_Exposed = False Option Explicit Public Function Create(ByVal initialBar As Long, ByVal initialDuck As String) As ISomething End Function
Something implement that interface…
Implements ISomething Implements ISomethingFactory Public Function Create(ByVal initialBar As Long, ByVal initialDucky As String) As ISomething With New Something .Bar = initialBar .Ducky = initialDucky Set Create = .Self End With End Function Private Function ISomethingFactory_Create(ByVal initialBar As Long, ByVal initialDucky As String) As ISomething Set ISomethingFactory_Create = Create(initialBar, initialDucky) End Function
…now we basically have an abstract factory that we can pass around to everything that needs to create an instance of
Something or, even cooler, of anything that implements the
Option Explicit Public Sub Main() Dim factory As ISomethingFactory Set factory = Something.Self With MyMacro.Create(factory) .Run End With End Sub
Of course this is a contrived example. Imagine
Something is rather some
SqlDataService encapsulating some ADODB data access, and suddenly it’s possible to execute
MyMacro.Run without hitting a database at all, by implementing the
ISomethingFactory interfaces in some
FakeDataService class that unit tests can use to test-drive the logic without ever needing to hit a database.
A factory is a creational pattern that allows us to parameterize the creation of an object, and even abstract away the very concept of creating an instance of an object, so much that the concrete implementation we’re actually coding against, has no importance anymore – all that matters is the interface we’re using.
Using interfaces, we can segregate parts of our API into different “views” of the same object and, benefiting from coding conventions, achieve get-only properties that can only be assigned when the object is initialized by a factory method.
If you really want to work with a specific implementation, you can always couple your code with a specific
Something – but if you stick to coding against interfaces, you’ll find that writing unit tests to validate your logic without testing your database connections, the SQL queries, the presence of the data in the database, the network connectivity, and all the other things that can go wrong, that you have no control over, and that you don’t need to cover in a unit test, …will be much easier.
The whole setup likely isn’t a necessity everywhere, but abstract factories, factory methods, and interfaces, remain useful tools that are good to have in one’s arsenal… and Rubberduck will eventually provide tooling to generate all that boilerplate code.
Sounds like fun? Help us do it!