A language agnostic approach since I see this problem in both compiled and interpreted languages with the builder pattern.
Let's say I have a Model that has 10 required fields and 5 optional fields. Of course, adding all these fields to the constructor would be a mess, but the constructor would allow us to easily check for failure because it can verify the types of the fields and that all the fields are provided.
Using the Builder pattern, we can make this code much cleaner to read and write, but as far as I see, it'd be hard for the compiler or IDE to know that a required field hasn't been provided.
For instance, let's say email
is required:
instance = new Model(firstName, lastName, phoneNumber);
The compiler, or other forms of checks, can see email
is not provided so it can fail since the constructor defines email
as a required parameter.
instance = new ModelBuilder()
->withName(firstName, lastName)
->withPhoneNumber(phoneNumber)
->build();
Here, the compiler, as far as I know, cannot tell that withEmail()
should have been called in order to define the email which can lead to a runtime exception if you have one instance of the Builder that is missing a required field.
Is this unavoidable? Is there some pattern that can be used to solve this problem?
Beyond making sure every instance that uses Builder has test coverage, I haven't been able to come up with a solution to the runtime exceptions. This problem seems to present itself more when the model has a new required field added after the builder instances have been implemented across the application.
4 Answers 4
Yes
In the builder, you are free to define the return types of the methods. It is often the builder itself, but it doesn't have to be. You can define additional interfaces.
E.g
IBuilderMail WithName()
IBuilderPhoneNumber WithMail()
IFinalBuilder WithPhoneNumber()
Here we define like a linked list the next allowed operation. You can also be less strict about if you like and maybe have a group of optional parameters in IOptional.
If you have many interfaces you can consider making you builder implement them all and do a downcast when returning for indicating the next operation in order to keep things simple.
It is also useful when configurering a service for a specific implementation.
new Builder()
.ConfigureForPostGreSql() // returns PostGreSql specific type
.PostGrespecificMethod()
-
4After more research, it looks like this pattern is called Step Builder, which seems to be the best form of the builder pattern for this problem.Devon– Devon2019年01月16日 19:27:03 +00:00Commented Jan 16, 2019 at 19:27
If you actually need all 10 parameters, put them all in the constructor. 10 parameters in the constructors is a little weird and annoying, but far less annoying than weird designs or chasing down bugs. Using another pattern to do the same thing is not any easier to read or write, or debug.
Instead, think about whether your class is doing too many things, or if some of those parameters can be grouped into their own DTO objects, etc.
Your question is basically "how can I put ten things in the constructor, but use different syntax?" When phrased like that, it should be clear how futile an effort that is.
-
2I think you're right and this is the best answer to my specific concerns, but the Step Builder answer is a solution to the builder pattern concerns. I was too concerned about having too many parameters in the constructor, but I think many language features like named parameters and extracting to data objects makes this less concerning.Devon– Devon2019年01月16日 19:29:47 +00:00Commented Jan 16, 2019 at 19:29
"Before runtime" means "at compile time" in most languages (and "compile time" can be triggered by the developer, or by a run time environment right before executing the code).
If the language does not explicitly provide a code structure or syntax to check this sort of thing, then you cannot verify before runtime.
This is where code review comes in to play. A human needs to check this work before it gets included in your main line branch in source control.
-
Do any languages provide a way to check the builder pattern with required fields? I guess that is my problem with understanding and what my question is based on. I haven't seen anything that would be able to check for this so I while I saw builder pattern was cleaner, I saw it as a pattern that would defer errors to runtime.Devon– Devon2019年01月16日 15:53:52 +00:00Commented Jan 16, 2019 at 15:53
-
1@Devon: To my knowledge there are not any languages that provide this feature, as the builder pattern is exactly that: a pattern. It is not a language construct. It is a way of writing and organizing code.Greg Burghardt– Greg Burghardt2019年01月16日 16:28:47 +00:00Commented Jan 16, 2019 at 16:28
One possibility is through the aid of your IDE. For example, I recently developed an IntelliJ plugin that allows you to mark builder methods as mandatory or optional. Feel free to check it out.
NameModelBuilder
with has the methodPhoneNumberModelBuilder withName(String name)
, thenPhoneNumberModelBuilder
has the methodEmailModelBuilder withPhoneNumber(String phoneNumber)
, etc. until you reach a step where you have all the required field. This has the disadvantage of forcing the order of fields to set, but it would achieve what you want. That being said, this is certainly way too over-engineered, and if you get to this point, I believe you may want to reconsider your design.