Builders are most useful when your object needs a lot of arguments/dependencies to be useful, or you want to allow many different ways of constructing the object.
Off the top of my head, I can imagine someone might want to "build" objects in a 3D game like this:
// Just ignore the fact that this hypothetical god class is coupled to everything ever
new ObjectBuilder(x, y, z).importBlenderMesh("./meshes/foo")
.syncWithOtherPlayers(serverIP)
.compileShaders("./shaders/foo.vert", "./shaders/foo.frag")
.makeDestructibleRigidBody(health, weight)
...
I would argue this example is more readable with the builder methods I made up just now than it would be with optional parameters:
new Object(x, y, z, meshType: MESH.BLENDER,
meshPath: "./meshes/foo",
serverToSyncWith: serverIP,
vertexShader: "./shaders/foo.vert",
physicsType: PHYSICS_ENGINE.RIGID_DESTRUCTIBLE,
health: health,
weight: weight)
...
In particular, the information implied by the builder method names has to get replaced by yet more parameters, and it's much easier to forget about one parameter in a group of closely related parameters. In fact, the fragment shader is missing, but you wouldn't notice that unless you knew to look for it.
Of course, if your object only takes one to five arguments to construct, there's no need to get the builder pattern involved, whether or not you have named/optional parameters.
And, besides what Ross Patterson suggested, consider this position which is the exact opposite:
Take maxims such as "Thou Shalt Not Do Any Real Work In Thy Constructors" with a grain of salt.
A constructor is, really, nothing but a static method. So, structurally, there is really not much difference between:
a) a simple constructor and a bunch of complex static factory methods, and
b) a simple constructor and a bunch of more complex constructors.
A considerable part of the negative sentiment towards doing any real work in constructors comes from a certain period of the history of C++ when there was debate as to precisely what state the object will be left in if an exception is thrown within the constructor, and whether the destructor should be invoked in such an event. That part of the history of C++ is over, and the issue has been settled, while in languages like Java there never was any issue of this kind to begin with.
My opinion is that if you simply avoid using new
in the constructor, (as your intention to employ Dependency Injection indicates,) you should be fine. I laugh at statements like "conditional or looping logic in a constructor is a warning sign of a flaw".
Besides all that, personally, I would take the XML parsing logic out of the constructor, not because it is evil to have complex logic in a constructor, but because it is good to follow the "separation of concerns" principle. So, I would move the XML parsing logic into some separate class altogether, not into some static methods that belong to your ModelDef
class.
Amendment
I suppose that if you have a method outside of ModelDef
which creates a ModelDef
from XML, you will need to instantiate some dynamic temporary tree data structure, populate it by parsing your XML, and then create your new ModelDef
passing that structure as a constructor parameter. So, that could perhaps be thought of as an application of the "Builder" pattern. There is a very close analogy between what you want to do and the String
& StringBuilder
pair. However, I have found this Q&A which seems to disagree, for reasons which are not clear to me: Stackoverflow - StringBuilder and Builder Pattern. So, to avoid a lengthy debate over here as to whether the StringBuilder
does or does not implement the "builder" pattern, I would say feel free to be inspired by how StrungBuilder
works in coming up with a solution that suits your needs, and postpone calling it an application of the "Builder" pattern until that little detail has been settled.
See this brand new question: Programmers SE: Is “StringBuilder” an application of the Builder Design Pattern?
Best Answer
A key to the understanding of the GoF design patterns is their intent. For the builder it is:
Simplified variant
When looking at the solution proposed by GoF, what strikes most is the separate building and assembly of parts: the structure distinguishes the
Director
and theBuilder
and enables a step by step construction:While being very useful, I think however that these examples focus only on one aspect of the pattern and miss the real intent. Thus their structure appears simplified by merging either
Director
andBuilder
orBuilder
andConcreteBuilder
.The missing intent
For GoF, it's not only about the separation between
Director
andBuilder
to build by step, but also the separation betweenBuilder
andConcreteBuilded
:Builder
provides the polymorphic/virtualbuildPart()
methods. ButgetResult()
to obtain the final product is a method ofConcreteBuilder
that is not included in the abstractBuilder
's interface. So it can use different parameters or return different types.Client
calls theDirector
for constructing the object and trigger the building of parts (perhaps using temporary objects), but that it's up to the client to callgetResult()
directly from ConcreteBuilder.Director
by referfencing it.This makes clear that this pattern is not so for building the parts and assemble the whole (which after all is nothing more than a helper to simplify providing all the elements at once), but more for returning very different "representations" (classes) using the same procedure for constructing the parts.
Real life use of the complete pattern
The typical use in real life is to build composite objects having different types of implementation/representation, such as for example different graphical representations/views (e.g. Entity/Relationship or UML model), different input or output formats (e.g. JSON or XML document) or encodings (e.g ASCII vs UTF16/32).
A concrete use could for example be ADO.net with
DbCommandBuilder
that is implemented by concreteSqlCommandBuilder
,OracleCommandBuilder
,ODBCCommandBuilder
to generate db specific commands.