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.

Mathieu Guindon
  • 1,720
  • 16
  • 33
  • 12
    And this "question" reeks of just being a rant... – Philip Kendall May 04 '16 at 16:56
  • 3
    @PhilipKendall maybe a little. But I'm genuinely interested in understanding why every Java builder implementation has that tight coupling. – Mathieu Guindon May 04 '16 at 16:58
  • 20
    @PhilipKendall It reeks of curiosity to me. – Mast May 04 '16 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 04 '16 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. – Robert Harvey May 05 '16 at 01:47
  • The coupling seems almost of secondary importance. – Robert Harvey May 05 '16 at 02:39

5 Answers5

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.

Jeff Bowman
  • 1,880
  • 1
  • 12
  • 10
  • I think I see your point about treating the constructor as an implementation detail. It just doesn't seem like *Effective Java* properly conveys this message then (I don't have / didn't read that book), leaving a ton of junior (?) Java devs assuming "that's how it's done" regardless of common sense. I agree that the Wikipedia examples are bad for comparison.. but hey it's Wikipedia.. and FWIW I don't actually care for the downvote; the deleted answer is sitting with a positive net score anyway (and there goes my chance to finally score a [badge:peer-pressure]). – Mathieu Guindon May 04 '16 at 18: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. – Mathieu Guindon May 04 '16 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](https://httpd.apache.org/docs/2.4/logs.html#combined), for instance. – Jeff Bowman May 04 '16 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... – Mathieu Guindon May 04 '16 at 18:40
  • @Mat'sMug _Within_ the package, either String consumes StringBuilder, StringBuilder consumes String, or they're colocated in the same top-level class. _Outside_ the package, it doesn't really matter; they're both integral parts of the same API surface. And, for a lot of relevant commentary, see [this API design talk by Josh Bloch](https://youtu.be/aAb7hSCtvGw?t=36m41s) where he explains his advice and (like in _Effective Java 2nd Edition_) he critiques the bad API design choices in the Java libraries. Link goes to 36:41, but the whole talk is worth watching. – Jeff Bowman May 04 '16 at 19:07
  • I'm pretty sure that in real life, several `Person`s can build other `Person`s. Is this like builder-ception? – enderland May 04 '16 at 23:11
  • In Java, for the case you describe, it would typically be more idiomatic to have a `Person.Builder` and perhaps a private constructor on `Person`. – chrylis -cautiouslyoptimistic- May 05 '16 at 05:55
  • 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 05 '16 at 07:47
  • @Luaan It's unclear what exactly about .NET's approach you prefer, since the Java API is identical there. – chrylis -cautiouslyoptimistic- May 05 '16 at 11:14
  • @chrylis .NET doesn't have a `string` constructor that takes a `StringBuilder` argument. You have to take it in context with Mat's comment before :D – Luaan May 05 '16 at 11:22
  • 3
    @Luaan Odd. I've literally never seen it used and didn't know it existed; `toString()` is universal. – chrylis -cautiouslyoptimistic- May 05 '16 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.

JimmyJames
  • 24,682
  • 2
  • 50
  • 92
  • 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). – Mathieu Guindon May 04 '16 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 04 '16 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](https://github.com/rubberduck-vba/Rubberduck/blob/next/RubberduckTests/Mocks/MockVbeBuilder.cs), at least without stabbing myself in the eye every time I look at them. – Mathieu Guindon May 04 '16 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 04 '16 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.

Old Fat Ned
  • 147
  • 3
  • Odd, the only times I *needed* a builder was when I had a type that doesn't have a definitive shape, like this [MockVbeBuilder](https://github.com/rubberduck-vba/Rubberduck/blob/next/RubberduckTests/Mocks/MockVbeBuilder.cs), 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. – Mathieu Guindon May 05 '16 at 00: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. – Old Fat Ned May 05 '16 at 00: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.

null
  • 3,546
  • 1
  • 11
  • 21
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.

DeadMG
  • 36,794
  • 8
  • 70
  • 139
  • 4
    Thanks... I'm sure this answer would collect more votes if it expanded a bit more, it looks like an unfinished answer =) – Mathieu Guindon May 04 '16 at 18:19
  • Yes,I'd +1, an alternative approach to the builder that provides the same safeguards and guarantees without the coupling – matt freake May 04 '16 at 18:22
  • 3
    For a counterpoint to this, see the [accepted answer](http://programmers.stackexchange.com/a/317565/16247), 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 04 '16 at 19:54