消费者建设者的陷阱

时间:2018-04-03 19:18:59

标签: java constructor java-8

请查看以下代码:

class Person {

    String name;
    int age;

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

}

正如您所看到的,我正在使用&#34;消费者构造函数&#34;,所以我可以创建一个这样的人:

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

似乎这种方法比构建器模式或所有参数构造函数要好得多。

自Java 8发布以来已经过去了4年,但没有人使用消费者构建器(至少我以前没见过它)。

我不明白为什么?这种方法有一些缺陷或局限吗?

我找到了一个,但我认为这不重要:

class AdvancedPerson extends Person {

    String surname;

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

}

当然,我们可以在Person中创建一个无参数构造函数,并在AdvancedPerson使用者构造函数中调用它。但这是一个解决方案吗?

那你觉得怎么样? 使用消费者构造者是否安全? 这是建筑商和所有参数构造者的替代品吗?为什么呢?

3 个答案:

答案 0 :(得分:12)

从我的角度来看,这既不安全也不优雅。反对这种方法有几个原因:最糟糕的是它不仅允许而且还迫使你在对象未初始化时让这个引用逃脱。这有几个严重的影响:

  • 消费者将引用处于中间构造函数调用中的对象。然后,消费者可以做各种邪恶的事情,例如调用被另一个子类重写的方法。
  • 此外,消费者也能够看到处于部分状态的物体,例如一些成员初始化而另一些成员没有。当涉及多线程时,这会变得更糟。
  • 使用这种方法无法创建不可变对象,因为您必须允许使用者干涉正在构建的对象的内部。
  • 最后但并非最不重要的是,你负责保证班级的不变量对消费者有责任。这绝对是一个班级应该对自己负责的事情。

有关创建对象的最佳实践,请参阅有效java的第二章。

答案 1 :(得分:7)

似乎我根本无法控制初始化过程。

我不知道已经设置了多少字段,它们的值是多少。我不知道消费者打过哪种方法(以及有多少种方法)。看起来我打破了封装规则。

我还公开了外部世界的this参考 调用者可以随心所欲地使用this。而且我没有得到通知。多线程有一个称为“不正当出版物”的术语。我会在这里使用它。

答案 2 :(得分:4)

消费者构造函数似乎在Java中提供了一种简洁的方式来编写代码,在其他语言中这些代码将被称为&#34;属性&#34;。只需传入初始化对象状态的Consumer

但这不适用于正确的类设计,包括封装。在这里,这意味着nameage应为private。这意味着Consumer不再能够访问实例变量并且不再编译。

封装和构建器模式都具有检查和拒绝对象的无效状态的优势。

如果Java具有属性的概念,那么现实中的obj.prop1 = value;variable = obj.prop2;等代码称为方法,那么这不是一个问题。但它没有这个概念。

您找到的陷阱可以通过为每个超类以及相关类传递Consumer来解决,例如:

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

但是,这意味着用户必须为每个超类传递不同的Consumer,这没有多大意义。

此外,从构造函数传递this不是一个好主意;这是一个实例泄漏,其中外部代码可以访问尚未完全构建的对象。

封装,this泄漏以及类层次结构中多个Consumer的复杂化都是使用消费者构造函数的好理由。