java 构建器模式和大量的强制参数

ifsvaxew  于 2023-02-28  发布在  Java
关注(0)|答案(8)|浏览(110)

到目前为止,我使用构建器模式的following实现(与这里描述的实现相反):

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()方法中对所需参数执行验证。
什么是最佳实践?是否有我尚未考虑过的更好的方法?

qybjjes1

qybjjes11#

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

interface StepB {
    StepBuilder b(String b);
}

interface StepA {
    StepB a(String a);
}

final class StepBuilder implements StepA, StepB {
    private String a;
    private String b;
    private String c = "";

    private StepBuilder() {
    }

    static StepA with() {
      return new StepBuilder();
    }

    // mandatory, from StepA
    @Override
    StepB a(String a) {
        this.a = a;
        return this;
    }

    // mandatory, from StepB
    @Override
    StepBuilder b(String b) {
        this.b = b;
        return this;
    }

    // optional
    StepBuilder c(String c) {
        this.c = c;
        return this;
    }

    Product build() {
        return new Product(a, b, c);
    }
}

用法:

StepBuilder.with().a("hello").b("world").build();

// or with the optional parameter c
StepBuilder.with().a("hello").b("world").c("!").build();

请记住,我将类命名为StepBuilder只是为了在我的解释中使它更清楚,最好给它一个反映相应域的某些方面的名称。

Url.with().host("example.com").port(81).path("some/where").query("status=1").build()

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

rnmwe5a2

rnmwe5a22#

然而,我最近一直在努力理解,当所有参数都是强制性的(或者至少绝大多数参数都是强制性的)时,这种模式有什么好处。
fluent builder模式仍然是有益的:
1.它的可读性更强--它有效地允许命名参数,这样调用就不仅仅是一长串未命名参数
1.它是无序的--这让你可以将参数分组到逻辑组中,或者作为单个构建器setter调用的一部分,或者简单地让你使用自然的顺序来调用构建器setter方法,使这个特定的示例化最有意义。

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();
rvpgvaaj

rvpgvaaj3#

最近我一直在努力理解,当所有参数都是强制性的时,模式有什么好处
这种模式简化了不可变类的创建,提高了代码的可读性。考虑下面的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的原则,无论对象构造语义如何,都应该采用。

zbdgwd5y

zbdgwd5y4#

构建器模式的一个优点是我很少看到它被推广,它也可以用于有条件地构造对象,例如只有当所有强制参数都正确或者其他所需资源可用时,在这方面,它们提供了与static factory method类似的好处。

ogsagwnx

ogsagwnx5#

我认为这将是适当的情况下,你有大的强制值,虽然接口的数量会增加,但代码会干净

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

n6lpvg4x

n6lpvg4x6#

构建器/工厂仍然允许您将接口与实现类型解耦(或者允许您插入适配器等),假设Widget成为一个接口,并且您有办法注入或隐藏new Widget.Builder
如果您不关心解耦,并且您的实现是一次性的,那么您是对的:builder模式并不比普通构造函数有用多少(它仍然用attribute-per-builder-method样式标记其参数)。
如果你重复地创建参数变化不大的对象,那么它可能仍然是有用的。你可以传递、缓存等插入几个属性后获得的中间构建器:

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();

这假设您的构建器是不可变的:builder setter不会设置一个属性并返回this,而是返回自身的一个新副本,其中包含修改。正如您在上面指出的,parameter对象是另一种避开重复参数样板的方法。在那里,您甚至不需要builder模式:只需将参数对象传递给实现构造函数。

zysjyyx4

zysjyyx47#

我的匿名类解决方案。这里是familyName是必需参数,givenName是可选的。如果这个解决方案的主要目标是强制创建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();
  }
}
t8e9dugd

t8e9dugd8#

我刚刚发布了一个免费的Intellij插件来解决这类问题。本质上,你可以为构建器中的每个参数定义一个方法,你可以注解哪些是强制的,哪些不是,IntelliJ完成会突出显示哪些是强制的和可选的参数。请随意尝试一下:
https://github.com/banterly91/Java-Builder-Guided-Completion-Intellij-Plugin

相关问题