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
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
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,
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.
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
Sizeisn’t a valid
Pizzaobject, 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.