0

For complex object creation, I am quite fond of Builder Pattern. My 1st question is "Do I need the Builder to be inside my complex class blueprint, or Could I actually have it outside?"

e.g. Are the following two classes as 2 different compilation units okay?

public class MyComplexObj {
   // Have loads of props

}

public class MyComplexObjBuilder {

// builder methods e.g. a(), b(), c() returning this

// finally, call build() and return MyComplexObj(this)
}

My 2nd Question - "Does my complex obj really need to be immutable e.g. no setters, or could I really use Builders for also a mean of "Refactoring" of my object creation?

1
  • 1
    I don't know what a "blueprint" is in this context. Let's use OO terminology. Are you asking if the builder methods must be members of the class being built? (The answer is no, not at all). Or are you referring to something more conceptual?
    – John Wu
    Dec 10 '19 at 2:52
4

Do I need the Builder to be inside my complex class blueprint, or Could I actually have it outside?

No, you don't need it to be inside. I personally dislike using classes as namespaces and always put a builder in its own file. But others have a different view on this to me. There's no right answer here, it's very much a case of personal style and taste.

As 9000 points out in their answer, putting it inside the class can have the advantage that it can access private members of the class to be built. Again I don't see this as an advantage but others clearly do.

Does my complex obj really need to be immutable e.g. no setters, or could I really use Builders for also a mean of "Refactoring" of my object creation?

These aren't actually contradictory requirements. Using the "wither" pattern, you can use a builder class to take an immutable object and construct a new immutable object from it with slightly different values.

As to whether to make the complex object immutable, that's a balancing act between idealism and pragmatism. Ideally, all classes should be immutable. Immutable objects have big advantages in terms of code reliability and are generally easier to reason about and debug. But in practice, a 100% immutable app would be pretty useless in all but edge cases. And immutability can have performance implications. So using pragmatism by allowing some mutability is essential. If you can make the complex object immutable, then great. If you need it to be mutable though, then don't sweat it: make it mutable.

4
  • 1
    I guess the "Balance" here should be my first concern. Perhaps once I can refactor this class to have a builder, I can slowly convert it to "Immutable"?
    – hagubear
    Dec 10 '19 at 9:39
  • @hagubear: The builder pattern is a great way to produce non-trivial immutable objects in an efficient way, bu mutating the builder (think of an immutable map). But immutable objects tend to be smaller than typical mutable objects, so the need of builders is reduced quite a bit, and often you just need a good set of constructors.
    – 9000
    Dec 10 '19 at 16:53
  • @david-arno: As a side note: surely a program consisting of only immutable objects can be useful; any program in Haskell is an example. As long as you can produce effects (like I/O) and construct new objects, you can write normal-looking programs. But immutable structures sometimes incur performance costs, so for certain critical parts you want mutable objects. Modern languages, e.g. Rust, tend to have everything immutable unless declared mutable, giving you the best of both worlds when applied right.
    – 9000
    Dec 10 '19 at 16:58
  • Ideally, all classes should be immutable. - Immutability is fine for value objects, not so much for entities. If I'm modeling an entity that has some inherently mutable properties, I'd argue that a mutable class is actually easier to reason about than an immutable one. I'd much rather write person.changeName("foo") than person = person.withName("foo") - the first version makes it clear that I'm trying to change that person's name and not trying to create a new person with a different name.
    – casablanca
    Dec 11 '19 at 4:26
2

Since the class's builder does not make sense in separation from the class it builds, there is a widespread practice to make the builder a nested static class within the target class:

class Foo {
  // ...
  public static Builder builder() { return new Builder(); }
  static class Builder {
    // ...
    public Builder withX(X x) {...}
    public Builder withY(Y y) {...}
    Foo build() { ... }
  }
}

Foo foo = Foo.builder().withX(10).withY(20).build();

Effectively the builder is just namespaced inside its target class. (Often a whole family of builders live within, allowing through the type system to build a class with various subsets of properties but not allowing it to remain incomplete.)

Of course, a builder can live elsewhere, but putting it inside the target class may allow for easier access to non-public methods between them, without exposing such methods to to the outside (at least in Java).

I find such an approach quite clean.

1
  • Thanks @9000 .... I am currently dealing with a big system where object creation doesn't have any pattern in particular. I identified a few candidates for builder pattern-based refactoring. But to keep the impact minimum, I was hoping that I could ignore the "immutability" aspect of my class and refactor with a builder. I know it doesn't stop the base class being exposed with it's setters etc. but I was hoping that a "Balance" is acceptable in this case?
    – hagubear
    Dec 10 '19 at 9:37
1

Let me first answer:

Must the built object be immutable?

I personally prefer yes, but let's look at functional languages where everything is immutable. Often there is an exception for object creation. Starting with a large matrix, it would be nice when the data is mutable on construction, and then have a life time as immutable.

And this may hold for an immutable-object builder too: only mutability prior to construction.

I have seen a build having a mutable copy of every object's field under construction, and finally create the actual immutable object. With a huge private constructor.

One could also create an object with non-final fields and provide no setters. Somehow this feels faked, suboptimal. But it saves on having the same fields in the builder, and a private default constructor suffices. Building can happen on an internal object, yet not returned.

Can the builder class be outside?

Indeed an embedded builder class can easily be hard to read, confusing. Having a separate class also obeys the principle separation of concerns.

So I would prefer it for a class with some amount of fields.

The builder method is public, hence its class should be public, the constructor package private.

public class Foo {
    private Foo() { ... }
    public static FooBuilder withBar(Bar bara) { ... }
}
public class FooBuilder {
    FooBuilder() { ... }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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