The Builder Pattern is rarely something you need. Often a Factory Method does the job just fine, as far as creating object instances goes. But sometimes, creating an object in a valid state would require a Create
method with many parameters, and that gets annoying.
There’s something rather elegant about chained member calls that build an object. The methods of a FooBuilder
class return Me
, so the calling code can chain the member calls and build the object in a single, expressive statement:
Set pizza = builder _ .OfSize(Medium) _ .CrustType = Classic _ .WithPepperoni _ .WithCheese(Mozza) _ .WithPeppers _ .WithMushrooms _ .Build
The Build
method returns the product, i.e. the resulting object.
So a basic (and rather flawed) builder class might look like this:
Private result As Pizza Private Sub Class_Initialize() Set result = New Pizza End Sub Public Function OfSize(ByVal sz As PizzaSize) As PizzaBuilder If result.Size = Unspecified Then result.Size = sz Else Err.Raise 5, TypeName(Me), "Size was already specified" End If Set OfSize = Me End Function Public Function WithPepperoni() As PizzaBuilder result.Toppings.Add(Pepperoni) Set WithPepperoni = Me End Function '... Public Function Build() As IPizza Set Build = result End Function
Every “builder method” is a Function
that returns Me
, and may or may not include a bit of logic to keep the result
valid. Then the Build
function returns the encapsulated and incrementally initialized result
object.
If the return type of the Build
function is an interface (that the result
object implements), then the calling code can treat all pizzas equally (assuming, say, ClassicCrustPizza
, PanPizza
, ThinCrustPizza
are different acceptable implementations of the IPizza
interface… this is where the pizza example really crumbles), and the interface can very well not expose any Property Let
members.
Considerations
The builder pattern is fun and very good to know, but it’s very rarely something that’s needed. But for these times when you do need it, there are a number of things to keep in mind:
- No temporal coupling: the order in which the calling code calls the builder methods should make no difference.
- Builder methods may not be invoked: if a pizza without a
Size
isn’t a validPizza
object, then there shouldn’t be a builder method for it; either provide sensible defaults, or make a parameterized factory that creates the builder with all the non-optional values initialized. - Repeated invocations: the calling code might, intentionally or not, invoke a builder method more than once. This should be handled gracefully.
- Readability: if the fluent API of a builder isn’t making the code any easier to read, then it’s probably not worth it.
You’ll think of using a builder pattern when a factory method starts having so many parameters that the call sites are getting hard to follow: a builder can make these call sites easier to read/digest.
This SoftwareEngineering.SE answer describes the actual GoF Builder Pattern (see Design Patterns: Elements of Reusable Object-Oriented Software), which takes it a notch further and makes the builder itself abstract, using a much better example than pizza. I warmly encourage you to read it; even though the code isn’t VBA, the principles are the very same regardless.
[…] mastered, polymorphism will come much more easily. Factories being creational patterns (there are others), they make a perfect introduction to every single one of these […]
LikeLike
[…] 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 […]
LikeLike