2017-08-06 26 views
1

,我已经遇到网上通常有这样的事情嵌套生成器模式:Java的嵌套生成器模式重复字段

class Person{ 

    private int id; 
    private String name; 
    private int age; 
    ... so on 

    private Person(Builder builder){ 
     this.id = builder.id; 
     this.name = builder.name; 
     this.age = builder.age; 
    } 

    public static class Builder{ 

     private int id; 
     private String name; 
     private int age; 
     ... so on 

     public Builder id(int id){ 
      this.id = id; 
      return this; 
     } 
     public Builder name(String name){ 
      this.name = name; 
      return this; 
     } 
     .... so on 

     public Person build(){ 
      return new Person(this); 
     } 

    } 

} 

我的问题是,是否有必要在复制和Person领域Builder?它看起来像很多冗余代码。而我的第二个问题是,下面的代码是否会成为一个可行的替代品,为什么?为什么?

class Person{ 

    private int id; 
    private String name; 
    private int age; 
    ... so on 

    private Person(){} 

    public static class Builder{ 

     private Person person = new Person(); 

     public Builder id(int id){ 
      this.person.id = id; 
      return this; 
     } 
     public Builder name(String name){ 
      this.person.name = name; 
      return this; 
     } 
     .... so on 

     public Person build(){ 
      return person; 
     } 
     // UPDATED -- another build method 
     public Person build(){ 
      Person built = this.person; 
      this.person = new Person(); 
      return built; 
     } 

    } 

} 

注:我理解这个话题可能刚愎自用,有可能不是一个“正确”的答案,但我只是想听到不同的想法和意见。我不是在寻找ultimate truth

+2

在第二个例子中,您完全击败了构建器的要点。构建器构建_complete_,_valid_和_immutable_实例 - 现在您无法完成这些任务。更糟糕的是,构建器可以在调用'build'后修改构建的对象**。我没有看到“重复的字段”有任何问题 - 但是您提出的替代方案非常糟糕。 –

+0

我明白你的意思了,谢谢你的反馈 – user2914191

+0

什么样的第一个评论说是不对的恕我直言。在构建方法被调用之前,构建器可以做任何它需要做的事情。而且你的代码没有被证明是错误的或正确的(这取决于你没有显示的代码)。建设者可以在建立过程中随意修改对象。唯一的必须是构建器之外的任何人都不能修改它,并且一旦构建方法被称为noone(甚至不是构建器)都可以修改它。你展示的人的成员是私人的,不变的,所以一切都很好。这里的关键是如果你有一个公开的二传手。 –

回答

2

您的代码就可以了,只要:

  1. 你把你的个人成员变量私人(你这样做)
  2. 你不提供,让这些成员变量的修饰方法(在你显示的代码不会这样做,但是你省略了部分代码)
  3. 这些成员变量是不可变的,或者你确保getter提供它们的副本。通常更好的是成员已经是不可变的了(提示:甚至java集合)。否则您将在每个getX调用中创建实例。
  4. 一旦调用了Builder.build,没有人必须能够修改Person实例状态,甚至不需要Builder本身。 这不会发生在您发布的代码中
  5. 构建器不公开正在构建的“时态实例”(如果有的话)。没有一个实例必须暴露在构建方法的返回之外。

有关于哪种方式是首选方式或大多数时候不喜欢味道的意见。但就正确与否而言,这种方法在经过一些修改后会很好。最后,调用构建之前发生的事情纯粹是Builder的内部。这是一个实施问题。重要的是以前的规则得到满足。

要修复规则4:您的Builder.build方法应返回正在使用的临时实例的深度克隆(有多种方法可以无需指定每个字段)。或者,您应该在构建器中有一个标志,禁止在构建实例中调用任何其他方法,一旦构建被调用。

附注:我通常更喜欢Builder类也使用私有构造函数。我想有这样的Person类:

public static Builder builder() { 
    return new Builder(); 
} 

这可以给你初始化生成器的方式更灵活,甚至可以有几种生成器方法做的不完全一样的东西,在“预”的条款(并且因为它们是方法,所以在命名上比在构造函数上更灵活:))

+0

如上所述;代码不好。事实上,它具体违反了你的观点4. –

+0

遵循你的定义 - 你是如何发现第二种方法完美无缺的?在调用构建之后,可以调用id方法并修改返回的对象的ID。 这里的另一个奇怪的事情,虽然不是非常重要,但是你在调用构建方法之前创建Person对象。 – user265732

+1

你是对的,我没有注意到build是返回person instance的。它应该在构建器中返回一个克隆/副本或者至少有一个标志来禁止在调用构建之后调用任何其他构建器实例方法。将更新回答 –