构建器模式和大量必需参数

时间:2011-09-05 00:04:22

标签: java parameters design-patterns builder

到目前为止,我使用构建器模式的following实现(而不是描述为here的实现):

public class Widget {
    public static class Builder {
        public Builder(String name, double price) { ... }
        public Widget build() { ... }
        public Builder manufacturer(String value) { ... }
        public Builder serialNumber(String value) { ... }
        public Builder model(String value) { ... }
    }

    private Widget(Builder builder) { ... }
}

这适用于我遇到的大多数情况,我需要使用各种必需/强制和可选参数来构建复杂对象。但是,当你的所有参数都是强制性的(或者至少是绝大多数参数)时,我最近一直在努力理解这种模式是如何有益的。

解决这个问题的一种方法是将传入的参数逻辑分组到它们自己的类中,以减少传递给构建器构造函数的参数数量。

例如,而不是:

Widget example = new Widget.Builder(req1, req2, req3,req4,req5,req6,req7,req8)
                           .addOptional(opt9)
                           .build();

按如下方式分组:

Object1 group1 = new Object1(req1, req2, req3, req4);
Object2 group2 = new Object2(req5, req6);

Widget example2 = new Widget.Builder(group1, group2, req7, req8)
                            .addOptional(opt9)
                            .build();

虽然拥有单独的对象可以简化一些事情,但如果不熟悉代码,也会使事情变得有点难以理解。我考虑的一件事是将所有参数移动到他们自己的addParam(param)方法中,然后在build()方法中对所需参数进行验证。

什么是最佳做法,是否有更好的方法可以解决这个问题?

8 个答案:

答案 0 :(得分:31)

如果您有许多必需参数,则可以使用Step Builder。简而言之:为每个必需参数定义一个接口,构建器方法返回下一个必需的构建器接口或构建器本身的可选方法。构建器仍然是一个实现所有接口的类。

Kotlin和Scala等语言在这里更方便,因为它们提供了带默认值的命名参数。

答案 1 :(得分:26)

  

然而,最近我一直在努力了解当所有参数都是强制性的(或者至少是绝大多数参数)时,模式是如何有益的。

流畅的构建模式仍然有益:

  1. 它更具可读性 - 它有效地允许命名参数,这样调用不仅仅是一长串未命名的参数

  2. 它是无序的 - 这允许您将参数组合成逻辑组,作为单个构建器设置器调用的一部分,或者只是让您使用自然顺序来调用最有意义的构建器setter方法特别实例化。


  3. Widget example = new Widget.Builder(req1, req2, req3,req4,req5,req6,req7,req8)
                                   .addOptional(opt9)
                                   .build();
    
         

    按如下方式分组:

    Object1 group1  = new Object1(req1, req2, req3, req4);
    Object2 group2  = new Object2(req5, req6);
    Widget example2 = new Widget.Builder(group1, group2, req7, req8)
                                .addOptional(opt9)
                                .build();
    
         

    虽然拥有单独的对象可以简化一些事情,但如果不熟悉代码,也会使事情变得有点难以理解。我考虑的一件事是将所有参数移动到他们自己的addParam(param)方法中,然后在build()方法中对所需参数进行验证。

    在合适或自然的时候,我会赞成混合动力。它不必全部在构造函数中,每个param都有自己的addParam方法。 Builder可以灵活地执行其中一个,中间或组合:

    Widget.Builder builder = new Widget.Builder(Widget.BUTTON);
    
    builder.withWidgetBackingService(url, resource, id);
    builder.withWidgetStyle(bgColor, lineWidth, fontStyle);
    builder.withMouseover("Not required");
    
    Widget example = builder.build();
    

答案 2 :(得分:3)

我很少(如果曾经)看到提升的构建器模式的一个优点是它也可以用于有条件地构造对象,例如,仅当所有必需参数都正确或者其他所需资源可用时。在这方面,他们为static factory method提供了类似的好处。

答案 3 :(得分:2)

  

我最近一直在努力去理解这种模式是怎样的   当所有参数都是必需的时候会受益

该模式简化了不可变类的创建并促进了可读代码。考虑下面的Person类(使用传统的构造函数和构建器)。

public static class Person {

    private static final class Builder {
        private int height, weight, age, income, rank;
        public Builder setHeight(final int height) { this.height = height; return this; }
        public Builder setWeight(final int weight) { this.weight = weight; return this; }
        public Builder setAge(final int age) { this.age = age; return this; }
        public Builder setIncome(final int income) {    this.income = income; return this; }
        public Builder setRank(final int rank) { this.rank = rank; return this; }
        public Person build() { return new Person(this); }
    }

    private final int height;
    private final int weight;
    private final int age;
    private final int income;
    private final int rank;

    public Person(final int height, final int weight, final int age, final int income, final int rank) {
        this.height = height; this.weight = weight; this.age = age; this.income = income; this.rank = rank;
    }

    private Person(final Builder builder) {
        height = builder.height; weight = builder.weight; age = builder.age; income = builder.income; rank = builder.rank;
        // Perform validation
    }

    public int getHeight() { return height; }
    public int getWeight() { return weight; }
    public int getAge() { return age; }
    public int getIncome() { return income; }
    public int getRank() {  return rank; }

}

哪种构造方法更容易理解?

final Person p1 = new Person(163, 184, 48, 15000, 23);
final Person p2 = new Person.Builder().setHeight(163).setWeight(184).setAge(48).
    setIncome(15000).setRank(23).build();
  

解决这个问题的一种方法是对逻辑进行逻辑分组   参数被传递到他们自己的类

当然,这是cohesion的原则,无论对象构造语义如何都应该采用。

答案 4 :(得分:0)

构建器/工厂仍允许您将接口与实现类型分离(或允许您插入适配器等),假设Widget成为接口并且您有办法注入或隐藏{{1} }。

如果您不关心解耦,并且您的实现是一次性的,那么您是对的:构建器模式并不比普通构造函数更有用(它仍然使用属性标记其参数 - per-builder-method style。)

如果您反复创建参数变化很小的对象,那么它可能仍然有用。你可以传入,缓存等插入几个属性后获得的中间构建器:

new Widget.Builder

这假定您的构建器是不可变的:构建器设置器不设置属性并返回Widget.Builder base = new Widget.Builder(name, price).model("foo").manufacturer("baz"); // ... Widget w1 = base.serialNumber("bar").build(); Widget w2 = base.serialNumber("baz").build(); Widget w3 = base.serialNumber("quux").build(); ,而是返回自己的新副本,而不是更改。正如您在上面指出的那样,参数对象是绕过重复参数样板的另一种方法。在那里,您甚至不需要构建器模式:只需将参数对象传递给实现构造函数。

答案 5 :(得分:0)

我认为如果您的强制性值较大,尽管接口数量会增加,但代码会很干净,这将是适当的

public class PersonBuilder implements NamePersonBuilder, LastNamePersonBuilder, 
                                  BirthDatePersonBuilder, FinalPersonBuilder {

private String name;
private String lastName;
private Date birthDate;
private String phoneNumber;

/**
 * Private constructor to force the use of the factroy method
 */
private PersonBuilder() {
}

/**
 * Creates a new person builder
 */
public static NamePersonBuilder aPerson() {
    return new PersonBuilder();
}

public LastNamePersonBuilder withName(String aName) {
    name = aName;
    return this;
}

public BirthDatePersonBuilder withLastName(String aLastName) {
    lastName = aLastName;
    return this;
}

public FinalPersonBuilder withBirthDate(Date aBirthDate) {
    birthDate = aBirthDate;
    return this;
}

public FinalPersonBuilder andPhoneNumber(String aPhoneNumber) {
    phoneNumber = aPhoneNumber;
    return this;
}

public Person build() {
    // The constructor and setters for Person has default scope
    // and is located in the same package as the builder
    Person p = new Person();
    p.setName(name);
    p.setLastName(lastName);
    p.setBirthDate(birthDate);
    p.setPhoneNumber(phoneNumber);
    return p;
}

interface NamePersonBuilder {
    LastNamePersonBuilder withName(String aName);
}

interface LastNamePersonBuilder {
    BirthDatePersonBuilder withLastName(String aLastName);
}

interface BirthDatePersonBuilder {
    FinalPersonBuilder withBirthDate(Date aBirthDate);
}

interface FinalPersonBuilder {
    FinalPersonBuilder andPhoneNumber(String aPhoneNumber);
    Person build();
}}

这将强制用户设置所有必需值,并强制设置值的顺序。因此,要构建一个人,这将是结果代码:

PersonBuilder.aPerson()
    .withName("Name")
    .withLastName("LastName")
    .withBirthDate(new Date())
    .build();

查看此参考: Builder Pattern with Twist

答案 6 :(得分:0)

我的匿名类解决方案。这是familyName是必需参数,而givenName是可选参数。如果此解决方案是 force 的主要目标,则创建Person进行设置的程序员需要参数(如果他不这样做,Java将不会编译)。

new Person(
    Person.parametersObject(new Person.RequiredParameters() {
      @Override
      public void setFamilyName() {
        this.familyName = "Jonson";
      }
    })
    .setGivenName("John")
);

实际上,并没有完全达到目标:因为我不能强迫程序员编写this.familyName = familyName;,但他必须实现setFamilyName。如果程序员不是不太熟练,他就会知道用这种方法必须做什么,但是由于疲劳,他会忘记它。

实施:

public class Person {

  private String familyName;
  private String givenName;


  public Person(ParametersObject parametersObject) {
    parametersObject.initializeSpecifiedFields(this);
  }

  public static ParametersObject parametersObject(Person.RequiredParameters requiredParameters) {
    return new Person.ParametersObject(requiredParameters);
  }


  public String getFamilyName() {
    return familyName;
  }
  public Person setFamilyName(String familyName) {
    this.familyName = familyName;
    return this;
  }

  public String getGivenName() {
    return givenName;
  }
  public Person setGivenName(String givenName) {
    this.givenName = givenName;
    return this;
  }


  public static class ParametersObject {

    private String familyName;
    private String givenName;

    public ParametersObject(Person.RequiredParameters requiredParameters) {
      this.familyName = requiredParameters.familyName;
    }

    public void initializeSpecifiedFields(Person person) {
      person.familyName = this.familyName;
      person.givenName = this.givenName;
    }

    public ParametersObject setGivenName(String givenName) {
      this.givenName = givenName;
      return this;
    }
  }

  public static abstract class RequiredParameters {
    public String familyName;
    public abstract void setFamilyName();
  }
}

答案 7 :(得分:0)

我刚刚发布了一个免费的 Intellij 插件来解决此类问题。本质上,您为构建器中的每个参数定义一个方法,您可以注释哪些是必需的,哪些不是,并且 IntelliJ 完成将突出显示哪些是必需参数和可选参数。请随意尝试:

https://github.com/banterly91/Java-Builder-Guided-Completion-Intellij-Plugin