Java中的Builder模式用法

3

最近我看到一些开发人员使用嵌套的构建器类来编写VO。

public class User {

    private String firstName;
    private String lastName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public static class UserBuilder {

        private String firstName;
        private String lastName;

        public User build() {
            User user = new User();
            user.firstName = firstName;
            user.lastName = lastName;
            return user;
        }

        public UserBuilder withFirstName(String firstName) {
            this.firstName = firstName;
            return this;
        }

        public UserBuilder withLastName(String lastName) {
            this.firstName = firstName;
            return this;
        }       
    }

}

现在他们声称这样做可以让代码更易读。但我的观点是,这样做有以下不利之处:
  1. 我不能简单地添加字段并期望我的IDE为我完成代码,因为现在我还需要更新这个内部类。

  2. 简单的POJO承载的是与VO不相关的代码。

如果我漏掉了什么,请给予任何建议。欢迎分享您对此的想法。
修改后的示例代码如下:
User user = new User.UserBuilder()
                .withFirstName("Name")
                .withLastName("surName")
                .build();

1
通常情况下,当您的构造函数有许多不同的可能性且其中许多都是相同类型时,您会使用构建器。因此,构建器使一切更易读和可重用。 - LordAnomander
9
开发者避免使用“伸缩构造函数”反模式,否则你将得到所有可能的可选构造参数的组合爆炸。在《Effective Java 2nd Ed Item 2》中有详细讨论。 - Andy Turner
2
这个链接提供了关于使用建造者模式的所有信息,你应该知道。 - LordAnomander
6
我认为构建者最适合用于构建不可变对象,否则你将不得不将所有参数传入构造函数中。构建者模式使构建过程更易读和灵活。但在你的例子中,User是可变的,因此使用构建者与使用 setter 方法相比没有太大区别。 - shmosel
1
这个简单的例子将帮助你清楚地了解为什么、什么以及如何做到这一点。 https://dzone.com/articles/factories-builders-and-fluent- - Lokesh
显示剩余6条评论
3个回答

1
这是Joshua Bloch的一篇文章,他很好地解释了为什么、何时以及如何使用构建器:http://www.informit.com/articles/article.aspx?p=1216151&seqNum=2 这是他的一本名为Effective Java的书中的其中一项内容。如果你有一点Java经验,我强烈建议你阅读这本书。
主要观点:
当您获得一个具有许多属性的类时,有几种方法可以创建对象并对其进行初始化。
如果您逐个设置每个属性,则可能会很冗长,并且在创建后可能会更改对象。使用此方法,无法使类不可变,并且您无法确定对象处于一致状态。
来自文章的示例:
public class NutritionFacts {
    // Parameters initialized to default values (if any)
    private int servingSize  = -1; // Required; no default value
    private int servings     = -1;  //     "     "      "      "
    private int calories     = 0;
    private int fat          = 0;
    private int sodium       = 0;
    private int carbohydrate = 0;

    public NutritionFacts() { }
    // Setters
    public void setServingSize(int val)  { servingSize = val; }
    public void setServings(int val)     { servings = val; }
    public void setCalories(int val)     { calories = val; }
    public void setFat(int val)          { fat = val; }
    public void setSodium(int val)       { sodium = val; }
    public void setCarbohydrate(int val) { carbohydrate = val; }
}

你可以使用可伸缩构造函数。它可以使你的对象不可变。但是,如果你有很多属性,编写和阅读代码可能会很困难。此外,当你只想创建一个已设置属性的对象时,不幸的是这个属性是构造函数的最后一个参数,你仍然必须设置所有参数。
文章中的示例:
public class NutritionFacts {
    private final int servingSize;  // (mL)            required
    private final int servings;     // (per container) required
    private final int calories;     //                 optional
    private final int fat;          // (g)             optional
    private final int sodium;       // (mg)            optional
    private final int carbohydrate; // (g)             optional

    public NutritionFacts(int servingSize, int servings) {
        this(servingSize, servings, 0);
    }

    public NutritionFacts(int servingSize, int servings,
            int calories) {
        this(servingSize, servings, calories, 0);
    }

    public NutritionFacts(int servingSize, int servings,
            int calories, int fat) {
        this(servingSize, servings, calories, fat, 0);
    }

    public NutritionFacts(int servingSize, int servings,
            int calories, int fat, int sodium) {
        this(servingSize, servings, calories, fat, sodium, 0);
    }

    public NutritionFacts(int servingSize, int servings,
           int calories, int fat, int sodium, int carbohydrate) {
        this.servingSize  = servingSize;
        this.servings     = servings;
        this.calories     = calories;
        this.fat          = fat;
        this.sodium       = sodium;
        this.carbohydrate = carbohydrate;
    }
}

建造者模式可以使您的代码更易读和编写。它还使您能够使您的类不可变。
来自文章的示例:
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // Required parameters
        private final int servingSize;
        private final int servings;

        // Optional parameters - initialized to default values
        private int calories      = 0;
        private int fat           = 0;
        private int carbohydrate  = 0;
        private int sodium        = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings    = servings;
        }

        public Builder calories(int val)
            { calories = val;      return this; }
        public Builder fat(int val)
            { fat = val;           return this; }
        public Builder carbohydrate(int val)
            { carbohydrate = val;  return this; }
        public Builder sodium(int val)
            { sodium = val;        return this; }

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

    private NutritionFacts(Builder builder) {
        servingSize  = builder.servingSize;
        servings     = builder.servings;
        calories     = builder.calories;
        fat          = builder.fat;
        sodium       = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

在您的示例中,我不确定为仅具有两个属性的类建立构建器是否非常有用。
我希望这可以帮助您。

请在您的回答中至少添加最重要的要点。 - Markus Kull
完成了,希望没问题。 - Thomas Betous

0

从小的不可变对象开始

如果您的所有属性都是必需的,则应仅使用构造函数。通过这样做,您可以创建漂亮的小型不可变对象。

当您有多个可选字段时,建造者很有帮助

如果有多个可选字段和创建对象的不同方式,则需要多个构造函数。

public User (int requiredParameter) { ... }
public User (int reqiredParameter, int optionalParameter) { ... }
public User (int reqiredParameter, int optionalParameter, String optionalParameter2) { ... }
public User (int reqiredParameter, String optionalParameter2) { ... }

它会创建混乱的代码。很难确定应该使用哪个构造函数。 在这种情况下,您可以使用嵌套构建器来直观地创建对象。


0

在给定的示例中,使用构建器模式不会为您带来任何价值。您可以通过所有setter创建User对象而无需使用Builder。在这种特殊情况下,Builder唯一提供的是Fluent Interface

当有多种组合方式创建有效对象时,应使用Builder模式。这样,您就不必实现许多构造函数或工厂方法。

当创建有效对象需要许多参数时,Builder也很有用。

Builder应负责仅构建有效对象。在您的情况下,如果User需要名字和姓氏,则Builder不应允许创建没有设置这些属性的User实例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接