23

I've recently deleted a answer of mine on Code Review, that started like this:

private Person(PersonBuilder builder) {

Stop. Red flag. A PersonBuilder would build a Person; it knows about a Person. The Person class shouldn't know anything about a PersonBuilder - it's just an immutable type. You've created a circular coupling here, where A depends on B which depends on A.

The Person should just intake its parameters; a client that's willing to create a Person without building it should be able to do that.

I was slapped with a downvote, and told that (quoting) Red flag, why? The implementation here has the same shape that which Joshua Bloch's demonstrated in his "Effective Java" book(item #2).

So, it appears that the One Right Way of implementing a Builder Pattern in Java is to make the builder a nested type (this isn't what this question is about though), and then make the product (the class of the object being built) take a dependency on the builder, like this:

private StreetMap(Builder builder) {
    // Required parameters
    origin      = builder.origin;
    destination = builder.destination;

    // Optional parameters
    waterColor         = builder.waterColor;
    landColor          = builder.landColor;
    highTrafficColor   = builder.highTrafficColor;
    mediumTrafficColor = builder.mediumTrafficColor;
    lowTrafficColor    = builder.lowTrafficColor;
}

https://en.wikipedia.org/wiki/Builder_pattern#Java_example

The same Wikipedia page for the same Builder pattern has a wildly different (and much more flexible) implementation for C#:

//Represents a product created by the builder
public class Car
{
    public Car()
    {
    }

    public int Wheels { get; set; }

    public string Colour { get; set; }
}

As you can see, the product here does not know anything about a Builder class, and for all it cares it could be instantiated by a direct constructor call, an abstract factory, ...or a builder - as far as I understand it, the product of a creational pattern never needs to know anything about what's creating it.

I've been served the counter-argument (which is apparently explicitly defended in Bloch's book) that a builder pattern could be used to rework a type that would have a constructor bloated with dozens of optional arguments. So instead of sticking to what I thought I knew I researched a bit on this site, and found that as I suspected, this argument does not hold water.

So what's the deal? Why come up with over-engineered solutions to a problem that shouldn't even exist in the first place? If we take Joshua Bloch off his pedestal for a minute, can we come up with one single good, valid reason for coupling two concrete types and calling it a best practice?

This all reeks of cargo-cult programming to me.

6
  • 12
    And this "question" reeks of just being a rant... May 4, 2016 at 16:56
  • 3
    @PhilipKendall maybe a little. But I'm genuinely interested in understanding why every Java builder implementation has that tight coupling. May 4, 2016 at 16:58
  • 20
    @PhilipKendall It reeks of curiosity to me.
    – Mast
    May 4, 2016 at 16:58
  • 8
    @PhilipKendall It reads like a rant, yes, but at its core there is a valid on-topic question. Maybe the rant could be edited out?
    – Andres F.
    May 4, 2016 at 19:50
  • I'm not impressed by the "too many constructor arguments" argument. But I'm even less impressed by both of these builder examples. Perhaps its because the examples are too simple to demonstrate non-trivial behavior, but the Java example reads like a convoluted fluent interface, and the C# example is just a roundabout way to implement properties. Neither example does any form of parameter validation; a constructor would at least validate the type and number of arguments provided, which means the constructor with too many arguments wins. May 5, 2016 at 1:47

5 Answers 5

23

I disagree with your assertion that it is a red flag. I also disagree with your representation of the counterpoint, that there is One Right Way of representing a Builder pattern.

To me there's one question: Is the Builder a necessary part of the type's API? Here, is the PersonBuilder a necessary part of the Person API?

It is entirely reasonable that a validity-checked, immutable, and/or tightly-encapsulated Person class will necessarily be created through a Builder it provides (regardless of whether that builder is nested or adjacent). In doing so, the Person can keep all of its fields private or package-private and final, and also leave the class open for modification if it is a work in progress. (It may end up closed for modification and open for extension, but that's not important right now, and is especially controversial for immutable data objects.)

If it is the case that a Person is necessarily created through a supplied PersonBuilder as part of a single package-level API design, then a circular coupling is just fine and a red flag isn't warranted here. Notably, you stated it as an incontrovertible fact and not an API design opinion or choice, so that may have contributed to a bad response. I wouldn't have downvoted you, but I don't blame someone else for doing so, and I'll leave the rest of the discussion of "what warrants a downvote" to the Code Review help center and meta. Downvotes happen; it's no "slap".

Of course, if you want to leave a lot of ways open to build a Person object, then the PersonBuilder could become a consumer or utility of the Person class, on which the Person should not directly depend. This choice seems more flexible—who wouldn't want more object creation options?—but in order to hold to guarantees (like immutability or serializability) suddenly the Person has to expose a different kind of public creation technique, such as a very long constructor. If the Builder is meant to represent or replace a constructor with a lot of optional fields or a constructor open to modification, then the class's long constructor may be an implementation detail better left hidden. (Also bear in mind that an official and necessary Builder doesn't preclude you from writing your own construction utility, just that your construction utility may consume the Builder as an API as in my original question above.)


p.s.: I note that the code review sample you listed has an immutable Person, but the counterexample from Wikipedia lists a mutable Car with getters and setters. It may be easier to see the necessary machinery omitted from the Car if you keep the language and invariants consistent between them.

11
  • 1
    Yet, I can't seem to shake off the idea that a type with too many constructor arguments is a design issue (smells of breaking SRP), that a builder pattern swiftly shoves under a carpet. May 4, 2016 at 18:22
  • 3
    @Mat'sMug My post above takes it as a given that the class needs that many constructor arguments. I'd likewise treat it as a design smell if we were talking about an arbitrary business logic component, but for a data object, it's not of the question for a type to have ten or twenty direct attributes. It's no violation of SRP for an object to represent a single strongly-typed record in a log, for instance. May 4, 2016 at 18:34
  • 1
    Fair enough. I still don't get the String(StringBuilder) constructor though, which seems to obey to that "principle" where it's normal for a type to have a dependency on its builder. I was flabbergasted when I saw that constructor overload. It's not like a String has 42 constructor parameters... May 4, 2016 at 18:40
  • 2
    @Mat'sMug StringBuilder is a performance optimization of a mutable string, it's not a builder of the String type (not the least because there's nothing to build on a string - ignoring implementation complexities, string is just "one value"). Just because something is called XXXBuilder doesn't mean it implements the Builder pattern for type XXX. That said, I still like .NET's approach better - StringBuilder.ToString() gives you the string "built" by the StringBuilder (in .NET, the two are exactly the same, except that string is immutable; it's just a runtime hack, really).
    – Luaan
    May 5, 2016 at 7:47
  • 3
    @Luaan Odd. I've literally never seen it used and didn't know it existed; toString() is universal. May 5, 2016 at 11:27
8

I understand how annoying it can be when 'appeal to authority' is used in a debate. Arguments should stand on their own IMO and while it's not wrong to point to the advice of such a well-respected person, it really can't be considered a full argument in itself ala we know the Sun travels around the earth because Aristotle said so.

Having said that, I think the premise of your argument kind of the same. We should never coupling two concrete types and call it a best practice because... Someone said so?

In order for the argument that coupling is problematic to be valid, there need to be specific problems that it creates. If this approach is not subject to those issues, then you can't logically argue that this form of coupling is problematic. So if you want to make your point here, I think you need to show how this approach is subject to those issues and/or how decoupling the builder will improve a design.

Offhand, I'm struggling to see how the coupled approach will create issues. The classes are completely coupled, for sure but the builder exists only to create instances of the class. Another way to look at it would be as an extension of the constructor.

4
  • So... higher coupling, lower cohesion => happy ending? hmm... I see the point about the builder "extending the constructor", but to bring up another example, I fail to see what String is doing with a constructor overload taking a StringBuilder parameter (although it's debatable whether a StringBuilder is a builder, but that's another story). May 4, 2016 at 18:45
  • 5
    @Mat'sMug To be clear, I don't really have a strong feeling about this either way. I don't tend to use the builder pattern because I don't really create classes that have lots of state input. I would generally decompose such a class for reasons you allude to elsewhere. All I'm saying is that if you can show how this approach leads to a problem, it won't matter if God came down and handed that builder pattern to Moses on a stone tablet. You 'win'. On the other hand if you can't...
    – JimmyJames
    May 4, 2016 at 18:55
  • One legit use of the builder pattern I've got in my own code base, is for mocking the VBIDE API; basically you supply the string content of a code module, and the builder gives you a VBE mock, complete with windows, active code pane, a project and a component with a module that contains the string you provided. Without it, I don't know how I'd be able to write 80% of the tests in Rubberduck, at least without stabbing myself in the eye every time I look at them. May 4, 2016 at 19:02
  • @Mat'sMug It can be really useful for unmarshalling data into immutable objects. These kinds of objects tend to be bags of properties i.e. not really OO. That whole space of problem is such a PITA that anything that helps get done is going to get used whether they follow good practices or not.
    – JimmyJames
    May 4, 2016 at 19:07
4

To answer why a type would be coupled with a builder, it is necessary to understand why anyone would use a builder in the first place. Specifically: Bloch recommends using a builder when you have a large number of constructor parameters. That isn't the only reason to use a builder, but I speculate it is the most common. In short the builder is a replacement for those constructor parameters and often the builder is declared in the same class it is building. Thus, the enclosing class already has knowledge of the builder and passing it as constructor argument doesn't change that. That is why a type would be coupled with it's builder.

Why does having a large number of parameters imply you should use a builder? Well, having a large number of parameters is not uncommon in the real world, nor does it violate the single responsibility principle. It also sucks when you have ten String parameters and are trying to remember which ones are which and whether or not it's the fifth or the sixth String that should be null. A builder makes it easier to manage the parameters and it can also do other cool things that you should read the book to find out about.

When do you use a builder that isn't coupled with it's type? Generally when the components of an object are not immediately available and the objects that have those pieces don't need to know about the type you are trying to build or you're adding a builder for a class that you don't have control over.

2
  • Odd, the only times I needed a builder was when I had a type that doesn't have a definitive shape, like this MockVbeBuilder, which builds a mock of an IDE's API; one test might just need a simple code module, another test could need two forms and a class module - IMO that is what GoF's builder pattern is for. That said, I might start using it for another class with a crazy constructor... but the coupling just doesn't feel right at all. May 5, 2016 at 0:47
  • 1
    @Mat's Mug The class you've presented seems more representative of the Builder design pattern (from Design Patterns by Gamma, et al.), not necessarily a simple builder class. They both build things, but they are not the same thing. Also, you never "need" a builder, it just makes other things easier. May 5, 2016 at 0:56
1

the product of a creational pattern never needs to know anything about what's creating it.

The Person class doesn't know what's creating it. It only has a constructor with a parameter, so what? The name of the type of the parameter ends with ...Builder which doesn't really force anything. It's just a name after all.

The builder is a glorified1 configuration object. And a configuration object is really just grouping properties together that belong to each other. If they only belong to each other for the purpose of being passed to the constructor of a class, so be it! Both origin and destination of the StreetMap example are of type Point. Would it be possible to pass the individual coordinates of each point to the map? Sure, but the properties of a Point belong together, plus you can have all kinds of methods on them that help with their construction:

1The difference is that the builder is not just a plain data object, but allows for these chained setter calls. The Builder is mostly concerned with how to build itself.

Now let's add a little bit of command pattern to the mix, because if you look closely, the builder is actually a command:

  1. It encapsulates an action: calling a method of another class
  2. It holds the necessary information to perform that action: the values for the parameters of the method

The special part for the builder is that:

  1. That method to be called is actually the constructor of a class. Better call it a creational pattern now.
  2. The value for the parameter is the builder itself

What is being passed to the Person constructor can build it, but it doesn't necessarily have to. It might be a plain old configuration object or a builder.

0

No, they're completely wrong and you're absolutely right. That builder model is just silly. It's none of the Person's business where the constructor arguments come from. The builder is just a convenience for users and nothing more.

3
  • 4
    Thanks... I'm sure this answer would collect more votes if it expanded a bit more, it looks like an unfinished answer =) May 4, 2016 at 18:19
  • Yes,I'd +1, an alternative approach to the builder that provides the same safeguards and guarantees without the coupling May 4, 2016 at 18:22
  • 3
    For a counterpoint to this, see the accepted answer, which mentions cases where PersonBuilder is in fact an essential part of the Person API. As it stands, this answer doesn't argue its case.
    – Andres F.
    May 4, 2016 at 19:54

Your Answer

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

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