Note: this article was updated 2021-04-13 with screenshots from the latest v2.5.1.x pre-release build; the extract interface enhancements shown will green-release with v2.5.2.
We’ve seen how to leverage the default instance of a class module to define a stateless interface that’s perfect for a factory method. At the right abstraction level, most objects will not require more than just a few parameters. Often, parameters are related and can be abstracted/regrouped into their own object. Sometimes that makes things expressive enough. Other times, there’s just nothing we can do to work around the fact that we need to initialize a class with a dozen or more values.
The example code for this article can be found in our Examples repository.
A class with many properties
Such classes are actually pretty common; any entity object representing a database record would fit the bill. Let’s make a User
class. We’re using Rubberduck, so this will be quick!
We start with a public field for each property we want:
Option Explicit
Public Id As String
Public UserName As String
Public FirstName As String
Public LastName As String
Public Email As String
Public EmailVerified As Boolean
Public TwoFactorEnabled As Boolean
Public PhoneNumber As String
Public PhoneNumberVerified As Boolean
Public AvatarUrl As String
Now we hit Ctrl+` to trigger a parse, right-click any of the variables and select Encapsulate Field from the Refactor menu (or Ctrl+Shift+F if you haven’t tweaked the default hotkeys):

Check the wrap fields in private type box, then click the Select all button and hit OK.

Now the module looks like this, and all you had to do was to declare a bunch of public fields:
Option Explicit
Private Type TUser
Id As String
UserName As String
FirstName As String
LastName As String
Email As String
EmailVerified As Boolean
TwoFactorEnabled As Boolean
PhoneNumber As String
PhoneNumberVerified As Boolean
AvatarUrl As String
End Type
Private this As TUser
Public Property Get Id() As String
Id = this.Id
End Property
Public Property Let Id(ByVal value As String)
this.Id = value
End Property
Public Property Get UserName() As String
UserName = this.UserName
End Property
Public Property Let UserName(ByVal value As String)
this.UserName = value
End Property
Public Property Get FirstName() As String
FirstName = this.FirstName
End Property
Public Property Let FirstName(ByVal value As String)
this.FirstName = value
End Property
Public Property Get LastName() As String
LastName = this.LastName
End Property
Public Property Let LastName(ByVal value As String)
this.LastName = value
End Property
Public Property Get Email() As String
Email = this.Email
End Property
Public Property Let Email(ByVal value As String)
this.Email = value
End Property
Public Property Get EmailVerified() As Boolean
EmailVerified = this.EmailVerified
End Property
Public Property Let EmailVerified(ByVal value As Boolean)
this.EmailVerified = value
End Property
Public Property Get TwoFactorEnabled() As Boolean
TwoFactorEnabled = this.TwoFactorEnabled
End Property
Public Property Let TwoFactorEnabled(ByVal value As Boolean)
this.TwoFactorEnabled = value
End Property
Public Property Get PhoneNumber() As String
PhoneNumber = this.PhoneNumber
End Property
Public Property Let PhoneNumber(ByVal value As String)
this.PhoneNumber = value
End Property
Public Property Get PhoneNumberVerified() As Boolean
PhoneNumberVerified = this.PhoneNumberVerified
End Property
Public Property Let PhoneNumberVerified(ByVal value As Boolean)
this.PhoneNumberVerified = value
End Property
Public Property Get AvatarUrl() As String
AvatarUrl = this.AvatarUrl
End Property
Public Property Let AvatarUrl(ByVal value As String)
this.AvatarUrl = value
End Property
I love this feature! Rubberduck has already re-parsed the module, so next we right-click anywhere in the module and select the Extract Interface refactoring, and check the box to select all Property Get
accessors (skipping Property Let
):

Having a read-only interface for client code that doesn’t need the Property Let
accessors makes an objectively cleaner API: assignments are recognized as invalid at compile time.
We get a read-only IUser
interface for our efforts (!), and now the User
class has an Implements IUser
instruction at the top, …and these new members at the bottom:
Private Property Get IUser_ThingId() As String
IUser_ThingId = ThingId
End Property
Private Property Get IUser_UserName() As String
IUser_UserName = UserName
End Property
Private Property Get IUser_FirstName() As String
IUser_FirstName = FirstName
End Property
Private Property Get IUser_LastName() As String
IUser_LastName = LastName
End Property
Private Property Get IUser_Email() As String
IUser_Email = Email
End Property
Private Property Get IUser_EmailVerified() As Boolean
IUser_EmailVerified = EmailVerified
End Property
Private Property Get IUser_TwoFactorEnabled() As Boolean
IUser_TwoFactorEnabled = TwoFactorEnabled
End Property
Private Property Get IUser_PhoneNumber() As String
IUser_PhoneNumber = PhoneNumber
End Property
Private Property Get IUser_PhoneNumberVerified() As Boolean
IUser_PhoneNumberVerified = PhoneNumberVerified
End Property
Private Property Get IUser_AvatarUrl() As String
IUser_AvatarUrl = AvatarUrl
End Property
The scary part is that it feels as though if Extract Interface accounted for the presence of a Update: automagic implementation completed!Private Type
in a similar way Encapsulate Field does, then even the TODO
placeholder bits could be fully automated. Might be something to explore there…
Now we have our read-only interface worked out, if we go by previous posts’ teachings, , that is where we make our User
class have a predeclared instance, and expose a factory method that I’d typically name Create
:
'@Description "Creates and returns a new user instance with the specified property values."
Public Function Create(ByVal Id As String, ByVal UserName As String, ...) As IUser
'...
End Function
Without Rubberduck, in order to have a predeclared instance of your class you would have to export+remove the class module, locate the exported .cls file, open it in Notepad++, edit the VB_PredeclaredId
attribute value to True
, save+close the file, then re-import it back into your VBA project.
With Rubberduck, there’s an annotation for that: simply add '@PredeclaredId
at the top of the class module, parse, and there will be a result for the AttributeValueOutOfSync inspection informing you that the class’ VB_PredeclaredId
attribute value disagrees with the @PredeclaredId
annotation, and then you apply the quick-fix you want, and you just might have synchronized hidden attributes across the with a single click.
'@PredeclaredId
Option Explicit
When it’s a factory method for a service class that takes in dependencies, 2-3 parameters is great, 5+ is suspicious. But here we’re taking in values, pure data – not some IFileWriter
or other abstraction. And we need quite a lot of them (here 10, but who knows how many that can be!), and that’s a problem, because this is very ugly:
Set identity = User.Create("01234", "Rubberduck", "contact@rubberduckvba.com", False, ...)
Using named parameters can help:
Set identity = User.Create( _
Id:="01234", _
UserName:="Rubberduck", _
Email:="contact@rubberduckvba.com", _
EmailVerified:=False, _
Phone:="555-555-5555", _
PhoneVerified:=False, _
...)
But the resulting code still feels pretty loaded, and that’s with consistent line breaks. Problem is, that limits the number of factory method parameters to 20-ish (if we’re nice and stick to one per line), since that’s how many line continuations the compiler will handle for a single logical line of code.
Surely there’s a better way.
Building the Builder
I wrote about this pattern in OOP Design Patterns: The Builder, but in retrospect that article was really just a quick overview. Let’s explore the builder pattern.
I like to design objects from the point of view of the code that will be consuming them. In this case what we want to end up with, is something like this:
Set identity = UserBuilder.Create("01234", "Rubberduck") _
.WithEmail("contact@rubberduckvba.com", Verified:=False) _
.WithPhone("555-555-5555", Verified:=False) _
.Build
This solves a few problems that the factory method doesn’t:
- Optional arguments become explicitly optional member calls; long argument lists are basically eliminated.
- Say
Id
andUserName
are required, i.e. aUser
object would be invalid without these values; the builder’s ownCreate
factory method can take these required values as arguments, and that way anyUser
instance that was built with aUserBuilder
is guaranteed to at least have these values. - If we can provide a value for
EmailVerified
but not forEmail
, or forPhoneVerified
but not forPhone
, and neither are required… then with individual properties the best we can do is raise some validation error after the fact. With aUserBuilder
, we can haveWithEmail
andWithPhone
methods that take aVerified
Boolean parameter along with the email/phone, and guarantee that ifEmailVerified
is supplied, thenEmail
is supplied as well.
I like to start from abstractions, so let’s add a new class module – but don’t rename it just yet, otherwise Rubberduck will parse it right away. Instead, copy the IUser
interface into the new Class1
module, select all, and Ctrl+H to replace “Property Get ” (with the trailing space) with “Function With” (without the trailing space). Still with the whole module selected, we replace “String” and “Boolean” with “IUserBuilder”. The result should look like this:
'@Interface
Option Explicit
Public Function WithId() As IUserBuilder
End Function
Public Function WithUserName() As IUserBuilder
End Function
Public Function WithFirstName() As IUserBuilder
End Function
Public Function WithLastName() As IUserBuilder
End Function
Public Function WithEmail() As IUserBuilder
End Function
Public Function WithEmailVerified() As IUserBuilder
End Function
Public Function WithTwoFactorEnabled() As IUserBuilder
End Function
Public Function WithPhoneNumber() As IUserBuilder
End Function
Public Function WithPhoneNumberVerified() As IUserBuilder
End Function
Public Function WithAvatarUrl() As IUserBuilder
End Function
We’re missing a Build
method that returns the IUser
we’re building:
Public Function Build() As IUser
End Function
Now we add the parameters and remove the members we don’t want, merge the related ones into single functions – this is where we define the shape of our builder API: if we want to make it hard to create a User
with a LastName
but without a FirstName
, or one with TwoFactorEnabled
and PhoneNumberVerified
set to True
but without a PhoneNumber
value… then with a well-crafted builder interface we can make it do exactly that.
Once we’re done, we can rename the class module to IUserBuilder
, and that should trigger a parse. The interface might look like this now:
'@Interface
'@ModuleDescription("Incrementally builds a User instance.")
Option Explicit
'@Description("Returns the current object.")
Public Function Build() As IUser
End Function
'@Description("Builds a user with a first and last name.")
Public Function WithName(ByVal FirstName As String, ByVal LastName As String) As IUserBuilder
End Function
'@Description("Builds a user with an email address.")
Public Function WithEmail(ByVal Email As String, Optional ByVal Verified As Boolean = False) As IUserBuilder
End Function
'@Description("Builds a user with SMS-based 2FA enabled.")
Public Function WithTwoFactorAuthentication(ByVal PhoneNumber As String, Optional ByVal Verified As Boolean = False) As IUserBuilder
End Function
'@Description("Builds a user with an avatar at the specified URL.")
Public Function WithAvatar(ByVal Url As String) As IUserBuilder
End Function
Then we can add another class module, and type Implements IUserBuilder
under Option Explicit
, then hit Ctrl+` to parse. Unless you disabled the “check if code compiles before parsing” setting (it’s enabled by default), you should be seeing this warning:

Click Yes to parse anyway (normally we only want compilable code, but in this case we know what we’re doing, I promise), then right-click somewhere in the Implements IUserBuilder
statement, and select the Implement Interface refactoring:

The result is as follows, and makes a good starting point:
Option Explicit
Implements IUserBuilder
Private Function IUserBuilder_Build() As IUser
Err.Raise 5 'TODO implement interface member
End Function
Private Function IUserBuilder_WithName(ByVal FirstName As String, ByVal LastName As String) As IUserBuilder
Err.Raise 5 'TODO implement interface member
End Function
Private Function IUserBuilder_WithEmail(ByVal Email As String, Optional ByVal Verified As Boolean = False) As IUserBuilder
Err.Raise 5 'TODO implement interface member
End Function
Private Function IUserBuilder_WithTwoFactorAuthentication(ByVal PhoneNumber As String, Optional ByVal Verified As Boolean = False) As IUserBuilder
Err.Raise 5 'TODO implement interface member
End Function
Private Function IUserBuilder_WithAvatar(ByVal Url As String) As IUserBuilder
Err.Raise 5 'TODO implement interface member
End Function
We’re “building” an IUser
object. So we have a module-level User
object (we need the class’ default interface here, so that we can access the Property Let
members), and each With
method sets one property or more and then returns the current object (Me
). That last part is critical, it’s what makes the builder methods chainable. We’ll need a Build
method to return an encapsulated IUser
object. So the next step will be to add a @PredeclaredId
annotation and implement a Create
factory method that takes the required values and injects the IUser
object into the IUserBuilder
instance we’re returning; then we can remove the members for these required values, leaving only builder methods for the optional ones. We will also add a value
parameter of the correct type to each builder method, and make them all return the current object (Me
). Once the class module looks like this, we can rename it to UserBuilder
, and Rubberduck parses the code changes – note the @PredeclaredId
annotation (needs to be synchronized to set the hidden VB_PredeclaredId
attribute to True
:
'@PredeclaredId
'@ModuleDescription("Builds a User object.")
Option Explicit
Implements IUserBuilder
Private internal As User
'@Description("Creates a new UserBuilder instance.")
Public Function Create(ByVal Id As String, ByVal UserName As String) As IUserBuilder
Dim result As UserBuilder
Set result = New UserBuilder
'@Ignore UserMeaningfulName FIXME
Dim obj As User
Set obj = New User
obj.Id = Id
obj.UserName = UserName
Set result.User = internal
Set Create = result
End Function
'@Ignore WriteOnlyProperty
'@Description("For property injection of the internal IUser object; only the Create method should be invoking this member.")
Friend Property Set User(ByVal value As IUser)
If Me Is UserBuilder Then Err.Raise 5, TypeName(Me), "Member call is illegal from default instance."
If value Is Nothing Then Err.Raise 5, TypeName(Me), "'value' argument cannot be a null reference."
Set internal = value
End Property
Private Function IUserBuilder_Build() As IUser
If internal Is Nothing Then Err.Raise 91, TypeName(Me), "Builder initialization error: use UserBuilder.Create to create a UserBuilder."
Set IUserBuilder_Build = internal
End Function
Private Function IUserBuilder_WithName(ByVal FirstName As String, ByVal LastName As String) As IUserBuilder
internal.FirstName = FirstName
internal.LastName = LastName
Set IUserBuilder_WithName = Me
End Function
Private Function IUserBuilder_WithEmail(ByVal Email As String, Optional ByVal Verified As Boolean = False) As IUserBuilder
internal.Email = Email
internal.EmailVerified = Verified
Set IUserBuilder_WithEmail = Me
End Function
Private Function IUserBuilder_WithTwoFactorAuthentication(ByVal PhoneNumber As String, Optional ByVal Verified As Boolean = False) As IUserBuilder
internal.TwoFactorEnabled = True
internal.PhoneNumber = PhoneNumber
internal.PhoneNumberVerified = Verified
Set IUserBuilder_WithTwoFactorAuthentication = Me
End Function
Private Function IUserBuilder_WithAvatar(ByVal Url As String) As IUserBuilder
internal.AvatarUrl = Url
Set IUserBuilder_WithAvatar = Me
End Function
Now, when I said default instances and factory methods (here too) are some kind of fundamental building block, I mean we’re going to be building on top of that, starting with this builder pattern; the Create
method is intended to be invoked off the class’ default instance, like this:
Set builder = UserBuilder.Create(internalId, uniqueName)
The advantages are numerous, starting with the possibility to initialize the builder with everything it needs (all the required values), so that the client code can call Build
and consume a valid User
object right away.
Side note about this FIXME
comment – there’s more to it than it being a signpost for the reader/maintainer:
'@Ignore UserMeaningfulName FIXME
Dim obj As User
By default only TODO
, BUG
, and NOTE
markers are picked up, but you can easily configure Rubberduck to find any marker you like in comments, and then the ToDo Explorer lets you easily navigate them all:

Another noteworthy observation:
'@Ignore WriteOnlyProperty
'@Description("For property injection of the internal IUser object; only the Create method should be invoking this member.")
Friend Property Set User(ByVal value As IUser)
If Me Is UserBuilder Then Err.Raise 5, TypeName(Me), "Member call is illegal from default instance."
If value Is Nothing Then Err.Raise 5, TypeName(Me), "'value' argument cannot be a null reference."
Set internal = value
End Property
Me
is always the current object, as in, an instance of this class module, presenting the default interface of this class module: the If Me Is UserBuilder
condition evaluates whether Me
is the object known as UserBuilder
– and right now there’s no such thing and the code doesn’t compile.
Synchronizing Attributes & Annotations
Rubberduck knows we mean that class to have a VB_PredeclaredId
attribute value of True
because of the @PredeclaredId
annotation, but it’s still just a comment at this point. Bring up the inspection results toolwindow, and find the results for the MissingAttribute inspection under Rubberduck Opportunities:

That didn’t fix the VB_PredeclaredId
attributes! Why?! The reason is that the attribute isn’t missing, only its value is out of sync. We’ll have to change this (pull requests welcome!), but for now you’ll find the AttributeValueOutOfSync inspection results under the Code Quality Issues group. If you group results by inspection, its miscategorization doesn’t matter though:

Adjust the attribute value accordingly (right-click the inspection result, or select “adjust attribute value(s)” from the “Fix” dropdown menu), and now your UserBuilder
is ready to use:
Dim identity As IUser
Set identity = UserBuilder.Create(uniqueId, uniqueName) _
.WithName(first, last) _
.WithEmail(emailAddress) _
.Build
…and misuse:
Set UserBuilder.User = New User '<~ runtime error, illegal from default instance
Debug.Print UserBuilder.User.AvatarUrl '<~ compile error, invalid use of property
Set builder = New UserBuilder
Set identity = builder.Build '<~ runtime error 91, builder state was not initialized
Set builder = New UserBuilder
Set builder = builder.WithEmail(emailAddress) '<~ runtime error 91
Conclusions
Model classes with many properties are annoying to write, and annoying to initialize. Sometimes properties are required, other times properties are optional, others are only valid if another property has such or such value. This article has shown how effortlessly such classes can be created with Rubberduck, and how temporal coupling and other state issues can be solved using the builder creational pattern.
Using this pattern as a building block in the same toolbox as factory methods and other creational patterns previously discussed, we can now craft lovely fluent APIs that can chain optional member calls to build complex objects with many properties without needing to take a gazillion parameters anywhere.
Thank you Mathieu, I haven’t understand that part how you turned multiple todos of extracted inteface
Err.Raise 5 ‘TODO implement interface member
into this
IUser_Id = this.Id
Automatically with rubberduck or mannually?
LikeLiked by 1 person
That was done manually, but this was my first time actually laying out the steps like this, and that part really feels like Rubberduck already has all the information it needs to fill the blanks… the *implement interface* refactoring would need a few tweaks, but the result would be pretty cool!
LikeLike
Absolutely wonderful reading and explanation. This is very timely for me. I have finally gotten very comfortable with factory pattern; a short few months ago the factory pattern was anything but clear. This article takes things to the next level. As always it will take me some time to fully understand and get comfortable with implementing this next layer.
Thank you so much for the time and effort you put into these articles and, of course, Rubberduck. I don’t know how you have time for a day job and your family.
Thanks, Mathieu!
LikeLiked by 2 people
Hi, thanks for the article, clear and thorough as always! 2 quick things; First, shouldn’t there be a colon before FIXME to indicate it’s a comment? What if one of your TODO explorer identifiers clashed with a code inspection identifier? Also I wonder is there ever an argument for `builder.WithBlah()` being valid if say you wanted to set an (overridable?) default for different places in your code; a bit like subclassing the generic UserBuilder with an AdminUserBuilder which specifies an automatic 2 factor authentication phone number to your company’s security team, so all Admins get that as a minimum, or a GuestUserBuilder with a default avatar that is different to a RegisteredUserBuilder?
LikeLike
The fixme parsed fine, but you’re probably correct that a separator is cleaner – presumably that FIXME looks like an argument to the annotation in the AST. But the TODO markers could be whatever you like, doesn’t matter if they match an inspection name, a keyword, or whatever =)
As for the other question, …the problem is that the abstract builder interface would have to be the same regardless of the implementation, in adherence to LSP.
Builder isn’t a very commonly used pattern, TBH the one time I *really* needed a builder was for setting up unit testing in Rubberduck; the MockVbeBuilder allows us to configure a mock IDE to run our tests in, and the setup can get very intricate, so a builder makes complete sense: the pattern works well for building *complex objects*, but basically twists into a pretzel for simple ones =)
LikeLike
Thank you for your hard work on the Rubberduck add-in. I use it almost every day. It could be me but there appears to be a small error in the ‘Create’ Function of the UserBuilder class. it passes the ‘internal’ User instance to the User property.
LikeLike
Same here (with the “Thank You for the hard work […]” as well): Using example-codeline 29 in the UserBuilder-Class, i get the err.raise 5 – cannot be null reference. I assume, instead of “internal” it should be:
Set result.User = obj
At least, it seems to work then. “internal” is “nothing” at that point and the “obj”-object-variable despite giving it two values is not used anywhere else.
For rubberduck Version 2.5.1.5557 (german translation): It should be “Bibliothek” (not Biblo…).
Again thanks for sharing Your knowledge and all the related work.
LikeLiked by 2 people
Hello, first of all thanks a lot for the “New Vision” you provide regarding VBA programming, I’m using it for more a decade and to be honest I never read a so clear presentation of possible advantages of OOP applied to VBA applications, simply I was thinking not almost possible. Now I changed my idea !! Thank you.
Trying supporting your hard work on such amazing tool, I would provide my modest 1 cent contribution…
1) My development platform is based on Asus Z390-A – Intel I7 8700 3.20GHz – 16Gb RAM – Samsung 970 Pro NvMe 512Mb M2 disk. Software Win10 (64bit) – Office 365-MsAccess 2016 (32 bit) – MySQL.
I have an application counting about 44.000 loc (comments and blank lines excluded) with 152 forms+reports (27.000 loc), 32 modules (13.000 loc) and 11 class modules (4.000 loc).
I installed RubberdDuck 2.5.0 plugin in Access without problems (and this i very good).
With my application the first aspect emerged is a very low speed, mainly during reparsing .
By looking at Win10 performance data, I see an average 12-13% cpu (34% peak) and 600Mb RAM usage by MsAccess during reparsing tasks, but parsing time is about 60-62 sec each time, I mean not only the first reparsing after first application startup is so long. During this 60 secs MsAccess interface is actually not responding, even if the Rubberduck command bar in VBE reports Ready status.
IMHO it is very good to know some work is in progress for performance point, as I read in your posts (and for sure I agree on Functionality come before Performance).
2) Trying to implement the very intriguing Builder pattern I reach the point, at the very beginning of this post, where, if I well understand, after Encapsulate Field menu choice I should “Check the wrap fields in private type box, then click the Select all button and hit OK.” but I cannot see where the “wrap” box is…
The Encapsulate Field form appears showing to the current Public attribute in the module where the cursor is located and I can only change the Property Name and disable the Let checkbox, no Wrap Fields box nor Select All button appear. I tried both 2.5.0 stable and 2.5.0.5244 release, same behaviour.
I missing something about the Encapsulate Field procedure ?
Thank you for sharing inspiring ideas !!
LikeLike
…sorry for point 2) in my previous post…
It has been my misunderstanding, I installed latest RubberDuck rel. 2.5.0.5524 and it works perfectly !
Very nice feature, thank you.
LikeLiked by 1 person
[…] This particular refactoring has seen a terrific enhancement that makes it very easy to cleanly and quickly turn a set of public fields into Property Get/Let members, with a Private Type TClassName and a module-scope Private this As TClassName instance variable – and all properties automatically reading/writing from it. You can see this feature in action in the previous article. […]
LikeLike
First of all tyvm for this nice pattern.
But I don’t really understand how to use .WithXYZ after i already created an IUser object.
For instance your code:
“Dim identity As IUser
Set identity = UserBuilder.Create(uniqueId, uniqueName) _
.WithName(first, last) _
.WithEmail(emailAddress) _
.Build”
Now identity is “created” with uniqueId, uniqueName, first, ……
And for example I want to add ‘WithTwoFactorEnabled(“+49xxx”)’ later in my code. Is this even possible?
Like: identity.WithTwoFactorEnabled(“+49xxx”, true)
You wrote:
“The advantages are numerous, starting with the possibility to initialize the builder with everything it needs (all the required values), so that the client code can call Build and consume a valid User object right away.”
So I thought it should actually be possible.
LikeLike
It’s possible, as long as it’s the builder interface you’re passing around: once the object is “built”, you’re not looking at a builder anymore.
One way I can think of, would be to have a “FromInstance” factory method that can spawn/initialize a new builder from a given instance of an object, such that a builder can be created off an already-built “user” object.
LikeLiked by 1 person
Thx, your “CreateFromInstnace(ByVal value As IUser)” idea does the trick for now. This saves me a couple of If statements. Builder Pattern is probably not necessary but I want to learn as much as possible.
LikeLike
Very likely not necessary! I’m finding this pattern is most useful when designing an API / object model, like the validation adorners in the MVVM infrastructure code… and even then, I didn’t need an explicit abstract builder interface for it!
It does make a very nice way to dig into objects and properties though!
LikeLike
Yeah I will watch that In the near future. But for now it’s to complicated for me 😀
LikeLike
As a non-programmer whoended up writing a ton of VBA to automate stuff, I appreciate the OOP overview. If I understand correctly, the IUser and IUserBuilder classes are never directly invoked in the object creation. Rather these are used as ‘structures/patterns’ for the User / UserBuilder class objects.
Also, one issue/error I found in the example code — In the UserBuilder class, the code example never initializes the ‘internal’ object as a new user. The code gives me an error until I add this statement in the Public Create function.
LikeLike
Hello there,
The more I read and think about this approach, the more I find it appealing as my collection of parameters grows with each month! But in my application, I have more than 5 classes for distinct objects which share the same set of parameters but with different implementation details. I would like to create and build them incrementally with this Builder approach. Is there a way to refactor the Builder responsibility into a single Factory class, instead of having the Builder residing within each class? Could you please guide me on how this could be done?
LikeLike