9

In Item 2 of Effective Java (2nd Edition), the author mentions the following about imposing invariants on parameters while using Builders:

It is critical that they be checked after copying the parameters from the builder to the object, and that they be checked on the object fields rather than the builder fields (Item 39). If any invariants are violated, the build method should throw an IllegalStateException (Item 60).

Does this imply that after the build method has created the target object, it should be passed to a validation routine for any required validations?

Also, could someone please explain the reasoning behind this?

Abhigyan Mehra
  • 215
  • 1
  • 4
  • 7
  • 1
    I suppose because the builder fields could be mutated (e.g. by another thread) after they have been copied to the object, meaning that the builder could be put into a state where it would create a "valid" object, whereas previously it would have created an "invalid" object. – Andy Turner Jul 03 '16 at 19:02
  • I'm sorry, but I'm not sure if I understand this properly. A builder would be created as new .Builder.setter1().setter2.().build(). How could any another thread get reference for this particular builder object? – Abhigyan Mehra Jul 03 '16 at 19:28
  • In that case, it couldn't. But there's nothing to stop a builder instance being shared between threads, in general: it's just a reference like any other. – Andy Turner Jul 03 '16 at 19:30

2 Answers2

19

Object validation is integral part of object creation using builders. Although you can have a separate routine doing the validation, such separation is not required: validation code could be part of the function doing the build. In other words, you can do this

TargetObject build() {
    TargetObject res = new TargetObject();
    res.setProperty1();
    res.setProperty2();
    validate(res); // This call may throw an exception
    return res;
}

void validate(TargetObject obj) {
    if (...) {
        throw new IllegalStateException();
    }
}

or this:

TargetObject build() {
    TargetObject res = new TargetObject();
    res.setProperty1();
    res.setProperty2();
    if (...) {
        throw new IllegalStateException();
    }
    return res;
}

The important thing is that the validation takes place after, not before, the construction of the target object. In other words, you need to validate the state of the object, not the state of the builder.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 1
    The other important thing is that it is the object you validate, not the builder, because it's the object you're going to do "useful stuff" with, so that's the one that has to be valid. – Andy Turner Jul 03 '16 at 19:05
16

The builder's build() method typically calls the private constructor of the class it's building. That's why builders are usually implemented as static nested classes, so they have access to the private constructor. The constructor is where the validation occurs. Even when you're not using the builder pattern, constructors are responsible for ensuring that the object is in a valid state when it is created. And the constructor should create defensive copies (EJ item 39) and validate the new object's fields, not the builder's fields, because the builder could be mutated while the fields are being copied.

Kevin Krumwiede
  • 9,868
  • 4
  • 34
  • 82