【问题标题】:Builder Pattern in Effective Java有效 Java 中的构建器模式
【发布时间】:2011-02-15 17:48:24
【问题描述】:

我最近开始阅读 Joshua Bloch 的 Effective Java。我发现 Builder 模式的想法 [书中的第 2 项] 非常有趣。我试图在我的项目中实现它,但出现编译错误。以下本质上是我想要做的:

具有多个属性的类及其构建器类:

public class NutritionalFacts {
    private int sodium;
    private int fat;
    private int carbo;

    public class Builder {
        private int sodium;
        private int fat;
        private int carbo;

        public Builder(int s) {
            this.sodium = s;
        }

        public Builder fat(int f) {
            this.fat = f;
            return this;
        }

        public Builder carbo(int c) {
            this.carbo = c;
            return this;
        }

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

    private NutritionalFacts(Builder b) {
        this.sodium = b.sodium;
        this.fat = b.fat;
        this.carbo = b.carbo;
    }
}

我尝试使用上述类的类:

public class Main {
    public static void main(String args[]) {
        NutritionalFacts n = 
            new NutritionalFacts.Builder(10).carbo(23).fat(1).build();
    }
}

我收到以下编译器错误:

包含的封闭实例 有效的java.BuilderPattern.NutritionalFacts.Builder 是必须的 NutritionalFacts n = 新 NutritionalFacts.Builder(10).carbo(23).fat(1).build();

我不明白消息的含义。请解释。上面的代码和 Bloch 在他的书中建议的例子很相似。

【问题讨论】:

标签: java design-patterns builder-pattern


【解决方案1】:

使构建器成为static 类。然后它将起作用。如果它是非静态的,则需要其所属类的实例 - 关键是不要拥有它的实例,甚至禁止在没有构建器的情况下创建实例。

public class NutritionFacts {
    public static class Builder {
    }
}

参考:Nested classes

【讨论】:

  • 而且,事实上,Builder 在书中的例子中是static(第 14 页,第 2 版第 10 行)。
【解决方案2】:

您应该将 Builder 类设为静态,并且您应该将字段设为 final 并使用 getter 来获取这些值。不要为这些值提供设置器。这样,您的类将是完全不可变的。

public class NutritionalFacts {
    private final int sodium;
    private final int fat;
    private final int carbo;

    public int getSodium(){
        return sodium;
    }

    public int getFat(){
        return fat;
    }

    public int getCarbo(){
        return carbo;
    }

    public static class Builder {
        private int sodium;
        private int fat;
        private int carbo;

        public Builder sodium(int s) {
            this.sodium = s;
            return this;
        }

        public Builder fat(int f) {
            this.fat = f;
            return this;
        }

        public Builder carbo(int c) {
            this.carbo = c;
            return this;
        }

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

    private NutritionalFacts(Builder b) {
        this.sodium = b.sodium;
        this.fat = b.fat;
        this.carbo = b.carbo;
    }
}

现在您可以按如下方式设置属性:

NutritionalFacts n = new NutritionalFacts.Builder().sodium(10).carbo(15).
fat(5).build();

【讨论】:

  • 为什么不公开 NutritionalFacts 字段?它们已经是最终的,它仍然是不可变的。
  • final 字段仅在初始化期间始终需要字段时才有意义。如果不是,则字段不应为 final
【解决方案3】:

要在 Intellij IDEA 中生成内部构建器,请查看此插件:https://github.com/analytically/innerbuilder

【讨论】:

  • 这与提出的问题无关,但非常有帮助!很好的发现!
【解决方案4】:

您正在尝试以静态方式访问非静态类。将Builder 更改为static class Builder,它应该可以工作。

您给出的示例用法失败,因为不存在 Builder 的实例。用于所有实际目的的静态类总是被实例化。如果你不让它静态,你需要说:

Widget = new Widget.Builder(10).setparm1(1).setparm2(3).build();

因为您每次都需要构造一个新的Builder

【讨论】:

    【解决方案5】:

    您需要将Builder 内部类声明为static

    查阅non-static inner classesstatic inner classes 的一些文档。

    如果没有附加的外部类实例,基本上非静态内部类实例就不能存在。

    【讨论】:

      【解决方案6】:

      一旦你有了想法,在实践中,你可能会发现 lombok 的@Builder 更方便。

      @Builder 可让您自动生成所需的代码,以使您的类可以使用以下代码进行实例化:

      Person.builder()
        .name("Adam Savage")
        .city("San Francisco")
        .job("Mythbusters")
        .job("Unchained Reaction")
       .build(); 
      

      官方文档:https://www.projectlombok.org/features/Builder

      【讨论】:

        【解决方案7】:

        这意味着你不能创建封闭类型。这意味着首先你必须创建一个“父”类的实例,然后从这个实例中你可以创建嵌套的类实例。

        NutritionalFacts n = new NutritionalFacts()
        
        Builder b = new n.Builder(10).carbo(23).fat(1).build();
        

        Nested Classes

        【讨论】:

        • 这没有多大意义,因为他需要构建者来构建“事实”,而不是相反。
        • 如果我们专注于构建器模式,我只专注于“我不明白消息的含义”,并提出了两种解决方案之一。
        【解决方案8】:

        Builder 类应该是静态的。我现在没有时间实际测试除此之外的代码,但如果它不起作用,请告诉我,我会再看看。

        【讨论】:

          【解决方案9】:

          当您有 2 个不同的类时,我个人更喜欢使用另一种方法。所以你不需要任何静态类。这基本上是为了避免在必须创建新实例时写入Class.Builder

          public class Person {
              private String attr1;
              private String attr2;
              private String attr3;
          
              // package access
              Person(PersonBuilder builder) {
                  this.attr1 = builder.getAttr1();
                  // ...
              }
          
              // ...
              // getters and setters 
          }
          
          public class PersonBuilder (
              private String attr1;
              private String attr2;
              private String attr3;
          
              // constructor with required attribute
              public PersonBuilder(String attr1) {
                  this.attr1 = attr1;
              }
          
              public PersonBuilder setAttr2(String attr2) {
                  this.attr2 = attr2;
                  return this;
              }
          
              public PersonBuilder setAttr3(String attr3) {
                  this.attr3 = attr3;
                  return this;
              }
          
              public Person build() {
                  return new Person(this);
              }
              // ....
          }
          

          所以,您可以像这样使用您的构建器:

          Person person = new PersonBuilder("attr1")
                                      .setAttr2("attr2")
                                      .build();
          

          【讨论】:

            【解决方案10】:

            正如许多人已经说过的那样,您需要创建课程static。 只是一个小小的补充——如果你愿意,没有静态的有一点不同的方法。

            考虑一下。通过在类中声明 withProperty(value) 类型设置器之类的东西来实现构建器,并使它们返回对自身的引用。在这种方法中,您有一个单一且优雅的类,它是线程安全且简洁的。

            考虑一下:

            public class DataObject {
            
                private String first;
                private String second;
                private String third;
            
                public String getFirst(){
                   return first; 
                }
            
                public void setFirst(String first){
                   this.first = first; 
                }
            
                ... 
            
                public DataObject withFirst(String first){
                   this.first = first;
                   return this; 
                }
            
                public DataObject withSecond(String second){
                   this.second = second;
                   return this; 
                }
            
                public DataObject withThird(String third){
                   this.third = third;
                   return this; 
                }
            }
            
            
            DataObject dataObject = new DataObject()
                 .withFirst("first data")
                 .withSecond("second data")
                 .withThird("third data");
            

            查看更多Java Builder 示例。

            【讨论】:

              【解决方案11】:

              您需要将 Builder 类更改为 静态类 Builder。然后就可以正常工作了。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2013-08-27
                • 1970-01-01
                • 2015-08-24
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多