Re: dysfunctional Builder pattern?
I have a question about a convention I have seen in a project I am
working on. The convention is that when a class has a constructor with
a lengthy argument list, a second class called a 'builder', is used to
Note that this is not the same as the builder pattern in the GoF. The
GoF pattern has an abstract builder interface and a concrete builder
for a start. And there's a director. And the point is to employ a
common construction process to create different representations.
Having said that, let us use some of the GoF terminology. Let us refer
to the type of the object being built as the Product.
The so-called builder that I have seen is used to avoid calling a ctor
that has a long argument list. For the sake of the argument we must
assume that there are some valid cases where it is legitimate to have
a long ctor arg list. Please, let's not get into the "if your arg list
is very long then the design is already bad" argument.
The Builder has a private data member for every private data member
that the Product has. The Builder sets all these private members to
default values. There are setter for each private data member. There
is a build() method that invokes the ctor of Product, supplying all
the private data members of the Builder. Thus, for any members whose
setter has been invoked, the set value will be employed, but for any
values that have not been set, the builder-defined default value will
be used. The setter methods by convention begin with 'with'. They also
return a reference to the Builder so that setters can be chained
together. Here is an example of a Builder being used:
someWidget = SomeWidgetBuilder.withFirstAttribute(firstVal).
Here, someWidget has at least 10 attributes but only 3 of them have
been set explicitly. You can very clearly see what they are, but what
is not so clear is what all the attributes are that someWidget has.
The problem I have seen, and why I do not like this 'builder', is that
I have seen Product classes where you HAVE to define valid values for
certain private members and there is no suitable default value.
The common approach to this problem is that the Builder's constructor
takes all required parameters, so that you must specify them.
Builder hides this and allows you to construct an object that is
invalid. For example, consider the someWidget object. It might be that
secondAttribute needs to be set. The code shown does not set it
explicitly so the default value employed by the Builder will be used.
This could make the system fail only when the someWidget object has
cause to use its secondAttribute member.
That is a flaw in the SomeWidget constructor which should throw an
IllegalArgumentException stating what about its construction is invalid.
At that point it is
discovered to be invalid and the code blows up. Eventually the
developer figures out that the error is in the call to the builder
where withSecondAttribute has not been called.
My preference is to use the ctor to construct an object, even if the
ctor list is long. One can always comment it. Thus I would say:
someWidget = new SomeWidget(startTime, endTime, partNumber, sharpness,
0, false, "widgetName");
I would comment the literal constants 0 and false, to make it clear
that there are the metric size and homeMade flag respectively.
someWidget can now never be invalid.
I think that there is a happy medium. Make your Builder's constructor
take all the required values (throwing exceptions if they aren't valid).
Make your "SomeWidget" constructor throw exceptions as well, just in
case someone does bypass the builder.
Hope this helps,
Daniel Pitts' Tech Blog: <http://virtualinfinity.net/wordpress/>