1

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.

  • 5
    You use Builder when you have complicated and varied construction of an object. You don't use builder when there are required fields, only to have "nicer" syntax. – Euphoric Jan 16 at 15:16
  • So if you had a model, representing a db table, that had 10 non-nullable fields, you would put all 10 fields in the constructor, or would you suggest a different pattern? – Devon Jan 16 at 15:17
  • 2
    I would find it weird that all 10 of them are required. And that there are not specific value objects for things like names or addresses, that can minimize amount of those fields in single object. – Euphoric Jan 16 at 15:19
  • So, a few fields could be extracted into objects, but most in this case are singular values with no defaults: Names (object), Role, Date of Birth, Gender, Email, Phone Number (object), Address (object), Office ID, Manager ID. – Devon Jan 16 at 15:24
  • You could essentially chain builders to do this. You have a NameModelBuilder with has the method PhoneNumberModelBuilder withName(String name), then PhoneNumberModelBuilder has the method EmailModelBuilder 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. – Vincent Savard Jan 16 at 16:48
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()
  • 1
    After 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 Jan 16 at 19:27
7

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.

  • I 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 Jan 16 at 19:29
1

"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 Jan 16 at 15:53
  • @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 Jan 16 at 16:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.