java builder模式用法[重复]

Posted

技术标签:

【中文标题】java builder模式用法[重复]【英文标题】:java builder pattern usage [duplicate] 【发布时间】:2016-05-19 04:33:32 【问题描述】:

最近我看到一些开发人员使用嵌套构建器类来编写他们的 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;
               
    


现在,他们声称这使代码更具可读性。我的观点是,这有以下缺点:

    我不能简单地添加字段并期望我的 IDE 为我完成代码,因为现在我也需要更新这个内部类。

    简单 POJO 携带的代码与 VO 无关。

如果我在这里遗漏了什么,我正在寻找任何建议。随意添加您的想法。

修改后的示例代码如下所示,

User user = new User.UserBuilder()
                .withFirstName("Name")
                .withLastName("surName")
                .build();

【问题讨论】:

当您的构造函数有许多不同的可能性并且其中许多属于同一类型时,您通常会使用构建器。因此,构建器使所有内容更具可读性和可重用性。 构造函数避免了“伸缩构造函数”反模式,在这种模式下,你最终会得到所有可能的可选构造函数参数的组合爆炸。 Effective Java 2nd Ed Item 2 中有详细讨论。 ***.com/questions/328496/… 此链接提供了您应该了解的有关构建器模式使用的所有信息。 我认为构建器对于构建不可变对象最有用,否则您必须将所有参数传递给构造函数。构建器模式使构建更具可读性和灵活性。但是在您的示例中,User 是可变的,因此您可以对构建器执行的操作是您无法对设置器执行的操作。 Josh bloch 解释用例 - informit.com/articles/article.aspx?p=1216151&seqNum=2 【参考方案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;
    

在您的示例中,我不确定为只有两个属性的类创建构建器是否非常有用。

希望对你有帮助。

【讨论】:

请在您的答案中至少添加最重要的几点。 完成了,希望没问题。【参考方案2】:

恕我直言,在给定的示例中,使用 Builder 模式没有任何价值。您可以在没有 Builder 的情况下创建 User 对象(因为所有设置器)。在这种特殊情况下,Builder 为您提供的唯一内容是 Fluent Interface。

当存在创建有效对象的各种组合时,您应该使用构建器模式。多亏了这一点,您不必实现许多构造函数或工厂方法。

在创建需要许多参数的有效对象时,Builder 也很有帮助。

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

【讨论】:

【参考方案3】:

从小的不可变对象开始

如果你的所有属性都是必需的,那么你应该只使用构造函数。通过这样做,您可能会创建不错的小型不可变对象。

当您有多个可选字段时,构建器会很有帮助

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

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

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

【讨论】:

以上是关于java builder模式用法[重复]的主要内容,如果未能解决你的问题,请参考以下文章

Java设计模式Builder建造者模式,Builder设计模式简单代码示例

Java开发中的23种设计模式详解----构建器模式(Builder)

Java开发中的23种设计模式详解----构建器模式(Builder)

设计模式-----Builder模式

还在手写 Builder 模式?试试 Lombok 中的 @Builder 用法,太强了!

lombok @Builder 注解的用法