You should look at all of the common object creation patterns and make an assessment based upon the state of your logic, time you have to implement the change, and so forth. Also, rather than have the participating objects know how to construct each other, you should consider Inversion of Control.
Its hard to be specific without more info, but given that you are talking about a complex graph I would think that Abstract Factory or Builder would be more useful here than just a simple Factory depending on how different each of your scenarios is.
If you have some initial decisions to make that influence creation of the graph but the differences are nuances, look at Builder...particularly if there are some structural differences between the "products" (such as, some participating objects are optional or in some scenarios participating objects drag in another set of collaborators).
If the differences between the scenarios are more distinct / elaborate and take the form of "families" of objects that should be used together, look at Abstract Factory with a few different factory implementations.
If you actually just have a few specific flavors of this graph and want to pick and choose between them dynamically, you might be able to just go with Prototype; treat your current implementation as a prototype, code up your alternate implementation(s) as another prototype, and just clone the appropriate prototype as needed.
A StringBuilder
is similar to a the Builder Pattern, but does not share much with the GoF description of this design pattern. The original point of the design pattern was
Separate the construction of a complex object from its representation so that the same construction process can create different representations.
— from Design Patterns, by Gamma, Helm, Johnson, Vlissides.
(note: “complex” primarily means “composed of multiple parts”, not necessarily “complicated” or “difficult”)
The “different representations” is key here. E.g. assuming this construction process:
interface ArticleBuilder {
void addTitle(String title);
void addParagraph(String paragraph);
}
void createArticle(ArticeBuilder articleBuilder) {
articleBuilder.addTitle("Is String Builder an application of ...");
articleBuilder.addParagraph("Is the Builder Pattern restricted...");
articleBuilder.addParagraph("The StringBuilder class ...");
}
we might end up with a HtmlDocument
or a TexDocument
or a MarkdownDocument
depending on what concrete implementation is provided:
class HtmlDocumentBuilder implements ArticleBuilder {
...
HtmlDocument getResult();
}
HtmlDocumentBuilder b = new HtmlDocumentBuilder();
createArticle(b);
HtmlDocument dom = b.getResult();
So one central point of the Builder pattern is polymorphism. The Design Patterns book compares this pattern to the Abstract Factory:
Abstract Factory is similar to the Builder in that it too may construct complex objects. The primary difference is that the Builder pattern focuses on constructing a complex object step by step. […] Builder returns the product as a final step, but as far as the Abstract Factory is concerned, the product gets returned immediately.
— from Design Patterns, by Gamma, Helm, Johnson, Vlissides.
This step-by-step aspect has then become the more popular aspect of the Builder pattern, so that in common parlance the Builder pattern is understood like this:
Split construction of an object into multiple steps. This allows us to use named arguments or optional parameters even in languages that do not support these features.
Wikipedia defines the pattern like this:
The builder pattern is an object creation software design pattern. Unlike the abstract factory pattern and the factory method pattern whose intention is to enable polymorphism, the intention of the builder pattern is to find a solution to the telescoping constructor anti-pattern[citation needed]. […]
The builder pattern has another benefit. It can be used for objects that contain flat data (html code, SQL query, X.509 certificate...), that is to say, data that can't be easily edited. This type of data cannot be edited step by step and must be edited at once. The best way to construct such an object is to use a builder class.[citation needed]
— from Builder Pattern on Wikipedia, by various contributors.
So as we can see, there is no truly common understanding of which pattern this name refers to, and in some points different definitions even contradict one another (e.g. regarding the relevance of polymorphism for Builders).
The only common property of the StringBuilder
with various interpretations of the pattern is that the product is created step by step rather than in one go. It does not meet a strict reading of the GoF definition of the design pattern, but please note that design patterns are malleable concepts meant to facilitate communication. I would continue to call StringBuilder
an example of the Builder Pattern, albeit an atypical one – the main reason for that structure in Java is performant concatenation in the presence of immutable strings, but not some interesting object-oriented design.
Best Answer
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 aModelDef
from XML, you will need to instantiate some dynamic temporary tree data structure, populate it by parsing your XML, and then create your newModelDef
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 theString
&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 theStringBuilder
does or does not implement the "builder" pattern, I would say feel free to be inspired by howStrungBuilder
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?