3

I have some classes I've created that are immutable and follow the builder pattern. I'm now creating a composite class that will include those classes as fields. Should the builder for that composite class call through to the sub-builders and then build them in the outer builder's build() method, or should the composite builder have methods that accept already constructed instances of the contained classes, or should I allow both options?

2 Answers 2

4

I'm going to assume your classes are called Composite, Foo, Bar and Baz.

The simplest option is to let CompositeBuilder take pre-built Foos/Bars/Bazs, so you get new CompositeBuilder().setFoo(...).setBar(...).setBaz(...).build(). The big advantage here is that the API to your Composite class is dead simple, both for you and your users. KISS and YAGNI apply here. Plus, you still get the option of subclassing Foo/Bar/Baz, and this might make it easier to change Foo/Bar/Baz themselves since Composite doesn't have to know much about them.

The alternative of having CompositeBuilder provide it's own interface has other potential advantages, assuming that you provide a much smaller, narrower interface than the one you get "for free" from the three subbuilders. That can potentially make Composite much easier to work with, and opens the possibility of someday changing Composite to use Oof/Rab/Zab instead of Foo/Bar/Baz without affecting client code, in case subclassing isn't enough.

I would avoid providing both, unless you're very sure you have a lot of users that want a simplified interface and a lot of other users who need the full power of a raw FooBuilder. Trying to do too many things at once always runs the risk of doing none of them well (and making it hard for anyone to figure out how they're supposed to use Composite).

Which one you should pick depends on a lot of factors you haven't described. How complicated are the three subbuilders? How much customization does a Composite need? Will users of CompositeBuilder already be familiar with the Foo/Bar/BazBuilders? Do they despise the current Foo/Bar/BazBuilder interfaces? Are there separate "consumers" and "producers" of these objects, or are they usually used in the same place where they're built? Would it ever make sense to change the classes that Composite is implemented with? And so on and so forth.

2
  • You gave me some very good points to chew on, which led me to my ultimate conclusion: I realized that while I was not likely to replace Foo with Oof, I am likely to subclass Foo or directly extend Foo with additional fields. If I take pre-built Foos, then my Composite class doesn't need to know about the change, which is preferable. I don't want this point to get lost in the comments. If you add it to your answer I will accept it.
    – TBridges42
    Jul 10, 2015 at 19:33
  • @TBridges42 Very good point. Added it.
    – Ixrec
    Jul 10, 2015 at 19:36
4

I tend to prefer a builder for each object that accepts fully built sub-objects.

The primary reason is this path provides simplicity of implementation, without loss of flexibility of expression. Also, I find it more readable.

Compare two completely facetious examples:

CompositeClass.builder()
              .withArgument(some_arg)

              .withSubclass1Argument1(some_arg)
              .withSubclass1Argument2(some_arg)

              .withSubclass2Argument1(some_arg)
              .withSubclass2Argument2(some_arg)
              .build();

vs

CompositeClass.builder()
              .withArgument(some_arg)
              .withSubclass1(Subclass1.builder()
                                      .withArgument1(some_arg)
                                      .withArgument2(some_arg)
                                      .build())
              .withSubclass2(Subclass2.builder()
                                      .withArgument1(some_arg)
                                      .withArgument2(some_arg)
                                      .build())
              .build()

It comes down to personal preference, but I find the second easier to read and work with.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.