有效Java中的构建器模式


137

我最近开始阅读Joshua Bloch撰写的Effective Java。我发现构建器模式[本书中的项目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 = new NutritionalFacts.Builder(10).carbo(23).fat(1).build();

我不明白该消息的含义。请解释。上面的代码类似于Bloch在他的书中建议的示例。


Answers:


171

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

public class NutritionFacts {
    public static class Builder {
    }
}

参考:嵌套类


34
而且,实际上Builder就是static本书的示例(第二版的第14页第10行)。
Powerlord

27

您应将Builder类设置为静态,还应将字段定型并使用吸气剂来获取这些值。不要为这些值提供设置器。这样,您的班级将是完全不变的。

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领域?他们已经是最终的,而且仍然是一成不变的。
skia.heliou

final仅当初始化期间始终需要这些字段时,这些字段才有意义。如果不是,则字段不应为final
Piotrek Hryciuk,

12

您正在尝试以静态方式访问非静态类。更改Builderstatic class Builder,它应该起作用。

您提供的示例用法失败,因为没有存在的实例Builder。出于所有实际目的,始终会实例化静态类。如果您不将其设为静态,则需要说:

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

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




5

一旦有了想法,在实践中,您可能会发现龙目岛的 @Builder更加方便。

@Builder 使您可以自动生成使您的类可实例化的代码,例如:

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

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


4

这意味着您不能创建封闭类型。这意味着您首先必须确定“父”类的实例,然后才能从该实例创建嵌套的类实例。

NutritionalFacts n = new NutritionalFacts()

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

嵌套类


3
那没有多大意义,因为他需要构建者来构建“事实”,而不是反过来。
2011年

5
如果我们专注于构建器模式,则为true,我仅关注“我不理解消息的含义”,并提出了两种解决方案之一。
DamianLeszczyński-Vash

3

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


1

当您有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();

0

正如这里已经提到的,您需要上课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示例。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.