Question

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.

Was it helpful?

Solution

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()

OTHER TIPS

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.

"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.

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.

Licensed under: CC-BY-SA with attribution
scroll top