Using the Builder pattern there is always the question whether the fields do have a default value or not? I cannot find a reliable source where that's clearly defined...
The problem is readability: What is Car.Builder().build()
returning? I always need to check the specific implementation of the Builder
to see which default values are used. Shouldn't Builder be used to create complex objects which do not have a simple default state by definition?
An alternative would be to check whether all mandatory fields are set inside of the build()
method:
fun build() : Car {
return if (doors != null && hp != null) Car(doors, hp, color) // color can be null
else throw IllegalArgumentException("Door count and HP is mandatory!")
}
...or is this considered bad practice?
You didn't find a general answer to your question, because there is none. It depends on the context the builder is used in or the object's details that the builder is constructing.
Sometimes it makes perfect sense to return a default object, where all of its members are initialized to default values. But then this default object must be valid. E.g. to build a logger object, it would be valid to return a logger that logs to the console with a default formatting and default log level. Someone could instantly use it.
But sometimes a pure default object doesn't make sense. Creating a completely configured default HTTP client is not sensible, because it won't deliver the minimum expected behavior, since the target URL might be something unexpected. In this case you could write a constructor for the builder class that takes an URL as parameter (maybe some data object as well) and then preconfigure the client object with default values (e.g. default request header, default timeout, default buffer size, etc). This object then would satisfy the minimum usage expectations. Every subsequent call of a setter will then overwrite the default, where each setter should check for validity of the arguments before accepting them.
But you should always, when possible, try to use defaults over exceptions. When an object's construction requires mandatory information, then make this public by putting them in a constructor. This way you are safe that your object is always in a valid and useful state. In case of the HTTP client builder you could throw an exception if the URL is malformed, to give the developer a hint that his code that constructs the URL might have flaws. Maybe read Best practices for exceptions.
With the solution in mind to write constructors in order to collect all required parameters (that cannot be set to useful default values), your build()
finalize method can and should always and at any time return a valid and useful object. This makes your builder convenient. (Otherwise the user of the builder would be forced to read documentation to know which setters to call. Everybody knows that you don't like to write documentations. Everybody knows that you don't like to read documentation before the use of some classes. Everybody feels the same).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With