This is motivated by this answer to a separate question.
The builder pattern is used to simplify complex initialization, especially with optional initialization parameters). But I don't know how to properly manage mutually exclusive configurations.
Here's an Image
class. Image
can be initialized from a file or from a size, but not both. Using constructors to enforce this mutual exclusion is obvious when the class is simple enough:
public class Image
{
public Image(Size size, Thing stuff, int range)
{
// ... initialize empty with size
}
public Image(string filename, Thing stuff, int range)
{
// ... initialize from file
}
}
Now assume Image
is actually configurable enough for the builder pattern to be useful, suddenly this might be possible:
Image image = new ImageBuilder()
.setStuff(stuff)
.setRange(range)
.setSize(size) // <---------- NOT
.setFilename(filename) // <---------- COMPATIBLE
.build();
These problems must be caught at run time rather than at compile time, which isn't the worst thing. The problem is that consistently and comprehensively detecting these problems within the ImageBuilder
class could get complex, especially in terms of maintenance.
How should I deal with incompatible configurations in the builder pattern?
setFilename("file.txt")
and then looking at the completion to be onlybuild()
would be surprising to me. – Jul 11 '15 at 18:33SizeBuilder
andFileBuilder
mirror each other's interface? That repetition sounds like an issue for maintenance. – kdbanman Jul 11 '15 at 18:51SizeBuilder
andFileBuilder
, one can take the two methodssetRange
andsetStuff
into another parent interface, e.g. theRangeAndStuffBuilder
, and then haveSizeBuilder
andFileBuilder
inherit from it. (The meaning ofinterface B extends interface A
is that any object known to implement the child interface B is guaranteed to implement the parent interface A also, which the compiler will help enforce.) – rwong Jul 11 '15 at 19:28Range
andStuff
must be initialized at first, not at arbitrary times. – rwong Jul 11 '15 at 19:36RangeAndStuffBuilder
) can be called on actual type. Further restrictions can be implemented by returning more basal types for some methods (though this will cause an exponential increase in types), effectively removing operations. As long as method results don't go back down the hierarchy, you won't get type errors. ThesetHeight
/setWidth
scenario could be implemented with a sibling hierarchy that doesn't have abuild
method. – outis Jul 11 '15 at 22:43