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
.
With New
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")
Here the 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.
Interfaces
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.
The Something
class would look like this- Notice that it only exposes Property Get
accessors, and that the Create
method returns the object through the ISomething
interface:
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
The 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 ISomething
interface:
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
…and made 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 ISomething
interface:
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 ISomething
and 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!
By using the factory method, does this now allow VBA to create an arbitrary number of an object and have each of those objects set to a different instance? In so much as a parallel to a `List` instead of needing to create a sized array of the object defined at runtime?
LikeLike
Not sure I understand what you’re saying, but a factory is merely an object whose job is to create objects. I think what you have in mind is more like a *builder* pattern, which is another creational design pattern for creating complex objects, when the ‘how’ is on the calling code; with a factory, the ‘how’ is on the factory itself: it can take parameters, but given these parameters it knows how to initialize the object. Whereas a builder could also take parameters, but the builder doesn’t know what the final object will look like… Not sure how clear that is… I’m planning to write about the builder pattern as well.
You could see the factory as a method that builds pizza boxes; you give it a size, and it gives you a pizza box of that size. The builder would be responsible for making the pizza; it knows you could want pepperoni, cheese (mozzarella? cheddar? goat?), bacon (bits? slices?), peppers (red? green? hot?), mushrooms, and whatnot – but it doesn’t know whether the pizza being built is going to have all that.
Hope it helps!
LikeLike
What you are looking for may be a Custom Collection (which is really just a class wrapping around a collection), with some additional code they even support “for each” functionality etc.
See here: http://dailydoseofexcel.com/archives/2010/07/04/custom-collection-class/
LikeLike
Hi Mathieu, I’ve tried to utilise the above and I’ve ytiple checked the code. But I don’t seem to get it to work. If I specify a variable to be ISomethingFactory the yes it instanstiates a ISomething object but for some reason as it’s contained within the ISomethingFactory the none of the methods are visible. Is that expected?
LikeLike
Not sure I understand what you mean by “container within the ISomethingFactory”, does the ISomething interface not have any public members for the concrete type to implement?
LikeLike
Sorry should have proof read my entry.
Contained rather than container or more accurately constrained by the interface specification of the factory class even if it returns a ISomething object. So when I try to do like:
Dim Anything as ISomethingFactory
Set Anything = Something.Create(1, “test”)
Debug.Print Anything.Bar
then I get “complies error: Method or data member not found”.
Hope that makes more sense.
LikeLike
Yeah sorry for that typo! I did mean to write “contained” =) ok I see now. ‘Anything’ should be ‘As ISomething`, and in this case ‘Something’ *is* the factory. If ISomething has a Bar member then it should compile.
LikeLike
Yes, that works and I understand that this technique provides a method to create different views of the same concrete class. But are we saying that the factory class would have to contain any and all members needed from the concrete class? If so the I suppose I had mis-understood and thought that you could create using the factory but pass around by the interface. So continuing the example above:
Function PassObject(ByRef IAnything as ISomething) As ISomething
Set PassObject = Something.Create( 2. iAnything.Ducky)
End Fuction
To call the above as:
Dim Anybody As ISomethingFactory
Set Anybody = Something.Create( 1, “Test”)
Dim Another as ISomethingFactory
Set Another = PassObject( Anybody)
I know it’s contrived but is that the right concept? But equally anything that implements ISomethingFactory interface could be passed to PassObject. As then you could create another class Widget and IWidget but the interface ISomethingFactory. Or have I completely lost the plot.
LikeLike
Sorry to say, I think you’re confused: the result of the Create factory method being assigned to some ISomethingFactory doesn’t make sense; the factory interface only has a Create method, i.e. Public Function Create(…) As ISomething – not As ISomethingFactory… Unless you’re having a SomethingFactoryFactory… but then you’d be writing Java 😉
LikeLike
Hi Matthieu,
I was understanding this, until the final bit of code (below), and thereafter I got lost.
I think it was the post’s big reveal too 🙂
I was wondering what is MyMacro, and what’s the method .Run?
I interpreted it as MyMacro was perhaps a variable of ISomething.
But there’s no Create or Run methods?
So maybe its another Class?
But I was unable to successfuly get it working.
Public Sub Main()
Dim factory As ISomethingFactory
Set factory = Something.Self
With MyMacro.Create(factory)
Thanks in advance for any clarifying comments,
Chris
LikeLike
MyMacro is a class with the VB_PredeclaredId attribute set to True. That makes its methods and members accessible without creating an instance, using the *default instance*. That’s how you can do UserForm1.Show and display a form (although, now you’re storing state in the default instance, and that’s a very bad idea).
LikeLike
It took a little to fully comprehend the usefulness of the Factory Pattern apart from unit testing. So it’s basically creating a rule for the creation of the Something class using an interface?
So if had multiple constructors with varying parameters instead of having functions CreateA and CreateB or Load etc you could split each up into Factory Interfaces. eg ISomethingFactory1 Create(a,b,c) as ISomething; ISomethingFactory2 Create(ParmArray args()) as ISomething .
LikeLiked by 1 person
I found this article to be very helpful. In playing around with the idea it seems it is perfectly possible to pass the parameters sent to Create to the Self Function. Here’s a small example.
Class NumberReporter (with predeclared ID)
Option Explicit
Private Type Properties
dummy As Long
End Type
Private Type State
first As Long
second As Long
third As Long
assigned As Boolean
End Type
Private p As Properties
Private s As State
Public Function Create(ByVal this_first As Long, ByVal this_second As Long, ByVal this_third As Long) As NumberReporter
With New NumberReporter
Set Create = .Self(this_first, this_second, this_third)
End With
End Function
Public Function Self(ByVal this_first As Long, ByVal this_second As Long, ByVal this_third As Long) As NumberReporter
With s
.first = this_first
.second = this_second
.third = this_third
End With
Set Self = Me
End Function
Public Sub report()
With s
Debug.Print .first, .second, .third
End With
End Sub
Tested with
Public reporter_1 As NumberReporter
Public reporter_2 As NumberReporter
Public reporter_3 As NumberReporter
Sub test()
Set reporter_1 = NumberReporter.Create(1, 2, 3)
Set reporter_2 = NumberReporter.Create(4, 5, 6)
Set reporter_3 = NumberReporter.Create(7, 8, 9)
reporter_1.report
reporter_2.report
reporter_3.report
End Sub
Which gives the expected output of
1, 2, 3
4, 5, 6
7, 8, 9
The only downside I found so far is that you need to be careful putting code in the Initialize or Terminate methods as the first ever call to NumberReporter will cause this code to run twice.
If the above is common knowledge I’ll go stand in the corner with a paper bag over my head.
PS The p and s variables are a derivation of the TSomething idea described in another RubberDuck blog. properties is reserved for private members which have public getters and setters.
LikeLike
Is the Self method actually necessary? Why couldn’t the Create method do it this way:
With New Something
.Bar = initialBar
.Ducky = initialDucky
Set Create = Me
End With
LikeLike
That would always return the default instance though, not the New object that was created (and then discarded!).
The Self property isn’t needed, but then you can’t have a With block:
Dim result As Something
Set result = New Something
result.Bar = initialBar
result.Ducky = initialDucky
Set Create = result
LikeLiked by 1 person
Thanks for the reply… I’m gonna have to noodle on this a bit 🙂
LikeLike
Got it! A great mentor of mine once told me, “one test is always worth a thousand good opinions.” I set up two Create methods one using Self and the other going to Me in the With New block. Stepping through the two different approaches shows the subtlety in the different “Me” instances. Thank you!
LikeLike
[…] classes, and this opens up a vast array of new possibilities, most notably the ability to now write factory methods in the same class module as the class being factory-created, effectively giving you the ability to […]
LikeLike
[…] create factories. It could be an actual class that you could allow the use of New on. It could be a member on the predeclared class module. It depends on what you need. The key takeaway here is that by separating the concern of creating […]
LikeLike
Also trying to wrap my head around an encapsulated factory. Using the example started in https://rubberduckvba.wordpress.com/2020/02/27/vba-classes-gateway-to-solid/, I created an IPersonFactory class as an interface, similar to the Something example above. Using this code in my main module:
Public Sub Test()
Dim factory As IPersonFactory
Set factory = Person.Self
Dim p As IPerson
Set p = factory.Create(“Joe”, “Smith”, #1/1/2000#)
End Sub
Compiles fine but throws a run time 91, Object variable or with block variable not set. Verified that my person class has VB_PredeclareId = True…. any help would be appreciated!
LikeLike
Hm if you have an IPersonFactory interface, you’re looking at an abstract factory ..which is probably overkill here, since the dedault instance of Person already exposes a perfectly fine Create factory method. Does Set p = Person.Create(…) work? I suspect a missing Set keyword inside the Create method…
LikeLike
Hi, thanks Mathieu for sparing a few minutes, much appreciate. You were right, I missed a Set keyword, not in the Create() method but in implementing IPersonFactory_Create() in the Person class. Sometimes this language makes me feel like an idiot!
LikeLiked by 1 person
Note, there’s quite a leap between using a factory method on a default instance, vs leveraging abstract factories – the latter is extremely useful & I use all the time for literally everything, the former is useful in more advanced dependency injection scenarios where you need to keep coupling low to retain testability, but still need to provide a dependency at the entry point – e.g. you’d want to inject an abstract factory to spawn some IDbConnection object but you don’t know at startup if that’s going to be a SqlServerConnection or a MySqlConnection because the user might pick either from some dialog. …I hope I’m not making things more confusing!
LikeLike
Hi Mathieu, thanks for taking the time to explain the factory concept as it applies to VBA. One challenge I’m experiencing is in implementing standard code for the Class_Initialize and Class_Terminate procedures for objects created by the factory instance. VBA expects these events to be private. Subsequently they aren’t captured as part of the interface definition and therefore it seems I have no way of prepopulating code for these events.
Any suggestions?
LikeLike
There should be very little (if any) code in these handlers, you want the factory method to initialize things.
What is this “standard code” doing?
Everything that the factory receives as parameters, needs to be exposed somehow, so that the factory is able to create objects and initialize them. So if your factory method receives a `Something` argument, you’ll want a `Something` property to set.
LikeLike
The add-in I’m developing supports various levels of internal instrumentation (e.g. logging Info, Warning, Error, Trace, Debug events), and I’m creating an AppLog service class to manage this functionality. Any standard module or class needing instrumentation calls AppLogFactory.Create to instantiate the object using the interface IAppLog. The factory pattern is useful here as it allows me to confine any setup complexities to the factory object and allows the object instance to have a number of read-only properties
Ignoring Class_Initialize (which the factory create method can replace), I want the Class_Terminate event to write to the trace log, and optionally record any unhandled error.
As an example, an entry point procedure in a standard module calls the factory to create an instance of IAppLog as part of of it’s own initiation routine. During execution, various procedures are instrumented to call the IAppLog object’s methods (e.g. myApplog.Trace “something interesing happened”). If an unhandled error in the parent object or calling procedure causes the causes the IAppLog instance to terminate, the Class_Terminate event of IAppLog should record the unhandled error condition to the add-in’s event log.
The issue I’m facing is that there doesn’t appear to be a way to ‘template’ the VB/VBA built-in Class_Terminate event in the Interface definition as it’s a private procedure, and therefore the factory can’t populate the code for this event. I could avoid using the factory method altogether and just instantiate the object directly (e.g. Set MyApplog = New Applog), but the object becomes overly complex with read/write properties and initialization code that would likely need to break encapsulation.
LikeLike
Your client code simply cannot New up an AppLog instance, unless it’s newed up in the same project.
Events are always private implementation details in VBA, Initialize and Terminate are no exception. I’m not sure what you mean by “populating code”, factories don’t populate any code anywhere…
Also you should probably be using a factory method rather than a factory class, since the client code will not be able to create the factory instance anyway (or does the factory have its own factory method?)…
Following the pattern in this article, your AppLog class should say “Implements IAppLog” and have a Create factory method that works off the class’ default instance and returns an IAppLog object. IAppLog interface/class defines the public API your add-in needs to expose for its clients; the AppLog class itself needs to expose Public/Friend Property Set/Let mutators for every single one of its dependencies (filewriter, config, etc.), so that the Create method is able to assign these properties on the object being created (it only breaks encapsulation if you are not coding against the interface, plus Friend members are only visible within the same project), and then its Class_Initialize handler can do whatever you need it to be doing – a factory method does not change that.
LikeLike
I am confused why you still have the regular Get and Let properties, for example, what is wrong with doing:
Private Property Get ISomething_Bar() As Long
ISomething_Bar = this.Bar
End Property
instead of calling the public property Get Bar within the private property Get ISomething_Bar like this:
Private Property Get ISomething_Bar() As Long
ISomething_Bar = Bar
End Property
I had read that it is not good to expose other public properties to the interface, but isn’t that what is happening in the code above because for every implemented method you are still exposing the public non implemented properties?
LikeLike
The properties need to be writable for the factory method to be able to assign them. Remove the properties and the code stops compiling!
Only the factory method needs to access the class’ default interface exposing the setters: the rest of the code uses the ISomething interface (which only exposes the getters) to work with the same object, leaving the mutable default interface only ever accessed by the factory method. Eventually an `@Inject` Rubberduck annotation could decorate write-only properties to let Rubberduck know that these members are being property-injected by a factory method; the default interface only needs the Let/Set members, Get members are the unused, questionable ones here.
LikeLike
[…] 我在这里阅读了关于创建类工厂的资料。https:/rubberduckvba.wordpress.com20180424factories-parameterized-object-initialization。 我很困惑,为什么他们要把实现的函数做成私有的,难道我们不希望它们是公共的,这样我们就可以访问它们吗? […]
LikeLike
[…] Mathieu Guindon’s Factories: Parameterized Object Initialization […]
LikeLike