Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Consumer Constructor Pitfalls

Look at the following code:

class Person {

    String name;
    int age;

    Person(Consumer<Person> consumer) {
        consumer.accept(this);
    }

}

As you can see, I'm using "consumer constructor", so I can create a person like this:

var person = new Person(p -> {
    p.name = "John";
    p.age = 30;
})

Seems like this approach is much better than builder pattern or all-arguments constructor.

4 years have passed since Java 8 was released but nobody uses consumer constructors (at least I haven't seen it before).

I do not understand why? Is this approach has some pitfalls or limitations?

I have found one, but I don't think it is critical:

class AdvancedPerson extends Person {

    String surname;

    AdvancedPerson(Consumer<AdvancedPerson> consumer) {
        super(); // <-- what to pass?
        consumer.accept(this);
    }

}

Sure we can create a no-arguments constructor in Person and just call it in AdvancedPerson consumer constructor. But is this a solution?

So what do you think about it?
Is it safe to use consumer constructors?
Is this a substitute for builders and all-argument constructors? Why?

like image 591
Feeco Avatar asked Apr 03 '18 19:04

Feeco


1 Answers

It's neither safe nor elegant from my point of view. There are several reasons against this approach: The worst thing about it is that it not only allows but also forces you to let the this reference escape while the object is not initialized yet. This has several severely bad implications:

  • The consumer will have a reference to an object who is in a mid constructor call. The consumer can then do all kind of evil things like e.g. call methods overridden by another child class.
  • Also the consumer may be able to see the object in a partial state, e.g. some member initialized while other aren't. This can get even worse when multi-threading is involved.
  • Immutable objects are impossible to create with this approach as you have to allow the consumer to meddle with the internals of the object under construction.
  • Last but not least you put the burden of being responsible for guaranteeing for the class' invariants to the consumer. This is definitely something a class should be responsible for itself.

See the second chapter of effective java for best practices concerning creating objects.

like image 141
Björn Zurmaar Avatar answered Sep 18 '22 17:09

Björn Zurmaar