I have two classes (A and B) that are both complex to construct, with multiple properties that must be validated at construction time. I want to use the Builder pattern to construct these objects, but among the constraints on construction of these objects are:
- An instance of A must contain multiple instances of B
- Each instance of B has a child relationship to a single instance of A
In addition, I need to be able to add new instances of B to A after A has been constructed.
It seems like this must be a pretty common scenario. Is there some pattern other than Builder or to augment Builder that handles this situation?
Option 1 - With a "placeholder" class
The best solution that I have come up with so far uses an impotent version of the Builder class, with the Build()
method removed (I call it a Placeholder) to specify the parameters for the child class.
class ClassA {
get propertyC
...
get list<A> children
restricted constructor() {}
BBuilder AddChild() {}
}
class ClassB {
get propertyD
...
get ClassA parent
restricted constructor(ClassA parent)
}
class ABuilder {
get / set propertyC
...
BPlaceholder AddChild() {}
ClassA Build() {
VALIDATE properties
BUILD ClassA
foreach child { BUILD child }
}
}
class BPlaceholder {
get / set propertyD
...
restricted constructor(ABuilder parent) {}
}
class BBuilder : BPlaceholder {
restricted constructor(ClassA parent) {}
ClassB Build() {
VALIDATE properties
BUILD child
}
}
Notice that, of the 5 classes in this example, the only one that can be simply constructed is ABuilder
. The other classes are all instantiated, either directly or indirectly, through this class.
Option 2 - Similar to Abstract Factory
Another possibility I have considered is to remove the Build()
method from both Builder classes (in which case they are no longer Builders) and pass both classes to a third Factory class to perform the construction, similar to Abstract Factory. This doesn't seem as nice to me for two reasons, though:
- The interface doesn't intuitively guide you to the proper use
- Construction of B after A has already been instantiated is substantially different from construction of B before A has been instantiated
Rationale for avoidance of simple construction
One possibility is to just use simple construction, something like this:
objA = new ClassA()
objA.Add(new ClassB(objA))
This is obviously a possibility, but it is missing a key element found in the Builder pattern. Builder allows you to ensure that there is no way to construct an "incomplete" instance of a class. My constraints state that a valid instance of ClassA
must have at least one child of type ClassB
. In the example above, objA
exists (for a short time) without any children, and is hence invalid.
-
2I guess design patterns are ontopic here, but huge chunks of source code are definitely now. Please use a small example (in pseudo code) to make your point. Also, make sure that your are not asking a code review question; for this site, you'd have to ask about a concept or for a reference.Raphael– Raphael2016年02月08日 21:19:13 +00:00Commented Feb 8, 2016 at 21:19
-
1@Raphael - Apologies. I'm not looking for a code review. I figured that actual code might make the question clearer. Thanks for your comment. I'm really looking for a pattern recommendation here. I can come up with several solutions, but one of the benefits of patterns is that they facilitate communication and understanding. With the solution I have come up with, it's difficult to clearly and concisely explain what I did and why. If I use a documented pattern, then the what and why already exist, and I just need to reference them. I haven't found a pattern that fits my requirements, though.danBhentschel– danBhentschel2016年02月09日 15:34:33 +00:00Commented Feb 9, 2016 at 15:34
-
@D.W. - I have added a section to address your question. Hopefully this makes what I'm looking for a bit clearer. Thanks for your comment.danBhentschel– danBhentschel2016年02月09日 15:35:42 +00:00Commented Feb 9, 2016 at 15:35
-
The answer here most likely depends a lot on how you are deciding what As and Bs to create. If you could give more context around what data and/or events lead to the creation of these object, it would help.JimmyJames– JimmyJames2016年09月08日 18:17:10 +00:00Commented Sep 8, 2016 at 18:17
-
Also, are these immutable objects or is there simply a minimum of 2 Bs and more can be added to A later?JimmyJames– JimmyJames2016年09月08日 18:18:15 +00:00Commented Sep 8, 2016 at 18:18
1 Answer 1
I would do something along the lines of...
public class A
{
public List<B> childern {get;private set;}
public A(DataReader dataForA, DataReader dataForBs)
{
//validate and set A properties from first datareader,
///validate we have at least one B
while(dataForBs.Read())
{
var parent = this;
var b = new B(dataForBs, parent) //validate b data in constructor
this.childern.Add(b);
}
}
}
Using datareaders here for simplicity, but obviously you could have any 'raw' data construct or just a list of parameters.
Since all the validation, child construction and linking is done in the constructor for A or B it should be impossible to have an instance of either A with no children or B with no parent.
You will need to create your own version of List which you cant remove everything from but I guess this is outside the scope of your problem