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

发布于 2024-12-02 19:25:30 字数 1415 浏览 1 评论 0原文

迄今为止,我使用 以下 构建器模式的实现(而不是到此处描述的实现):

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() 方法中对所需参数进行验证。

什么是最佳实践?是否有我没有考虑过的更好的方法?

To date I use the following implementation of the builder pattern (as opposed to the implementation described 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) { ... }
}

This works well for most situations I've encountered where I need to build up a complex object with a variety of required/mandatory and optional parameters. However, I've been struggling lately to understand how the pattern is of any benefit when all your parameters are mandatory (or at least the vast majority are).

One means of getting around this has been to logically group the parameters being passed in to their own classes to reduce the number of parameters being passed to the builder constructor.

For example, instead of:

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

becomes grouped as follows:

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

While having separate objects simplifies things quite a bit, it also makes things a little difficult to follow if one is not familiar with the code. One thing I considered was moving all parameters into their own addParam(param) methods and then performing validation on required parameters in the build() method.

What is best practice and is there perhaps a better approach to this that I haven't considered?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

万水千山粽是情ミ 2024-12-09 19:25:30

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

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 等语言在这里更方便,因为它们提供带有默认值的命名参数。

You can use a Step Builder if you have many mandatory parameters. In short: you define an interface for every single mandatory parameter and a builder method returns the next mandatory builder interface or the builder itself for optional methods. The builder remains a single class which implements all the interfaces.

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

Usage:

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

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

Keep in mind that I named the class StepBuilder only to make it clear in my explanation. It would be better to give it a name reflecting some aspect of the respective domain. Example:

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

Languages like Kotlin and Scala are more convenient here, since they offer named parameters with default values.

时光瘦了 2024-12-09 19:25:30

但是,我最近一直在努力理解当所有参数都是强制性的(或者至少绝大多数参数都是强制性的)时,该模式有何好处。

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

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

  2. 它是无序的 - 这使您可以将参数分组到逻辑组中,既可以作为单个构建器 setter 调用的一部分,也可以简单地让您使用自然顺序来调用构建器 setter方法最能理解这个特定的实例化。


Widget 示例 = new Widget.Builder(req1, req2, req3,req4,req5,req6,req7,req8)
                               .addOptional(opt9)
                               。建造();

分组如下:

Object1 group1 = new Object1(req1, req2, req3, req4);
Object2 group2 = new Object2(req5, req6);
小部件示例2 = new Widget.Builder(group1, group2, req7, req8)
                            .addOptional(opt9)
                            。建造();

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

在适当或自然的情况下,我更喜欢混合动力。它不必全部在构造函数中每个参数都有自己的 addParam 方法。 Builder 使您可以灵活地执行其中一项、另一项、中间项或组合:

Widget.Builder builder = new Widget.Builder(Widget.BUTTON);

builder.withWidgetBackingService(url, 资源, id);
builder.withWidgetStyle(bgColor, lineWidth, fontStyle);
builder.withMouseover("不需要");

小部件示例 = builder.build();

However, I've been struggling lately to understand how the pattern is of any benefit when all your parameters are mandatory (or at least the vast majority are).

The fluent builder pattern is still beneficial:

  1. Its more readable - it effectively allows named parameters so that the call isn't just a long list of unnamed arguments

  2. Its unordered - this lets you group arguments together into logical groups, either as part of a single builder setter call or simply by letting you use a natural order to calling the builder setter methods that make the most sense of this particular instantiation.


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

becomes grouped as follows:

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

While having separate objects simplifies things quite a bit, it also makes things a little difficult to follow if one is not familiar with the code. One thing I considered was moving all parameters into their own addParam(param) methods and then performing validation on required parameters in the build() method.

I would favor a hybrid when appropriate or natural. It doesn't have to be all in constructor or each param has its own addParam method. Builder gives you flexibility to do one, the other, in-between, or a combo:

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();
海螺姑娘 2024-12-09 19:25:30

我最近一直在努力理解这个模式是如何的
当所有参数都是强制性的时会受益

该模式简化了不可变类的创建并提高了可读代码。考虑下面的 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();

解决这个问题的一种方法是将
参数被传递给自己的类

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

I've been struggling lately to understand how the pattern is of any
benefit when all your parameters are mandatory

The pattern eases creation of immutable classes and promotes readable code. Consider the Person class below (with a conventional constructor and a builder).

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; }

}

Which method of construction is easier to comprehend?

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

One means of getting around this has been to logically group the
parameters being passed in to their own classes

Sure, this is the principle of cohesion and should be adopted irrespective of object construction semantics.

山人契 2024-12-09 19:25:30

我很少(如果有的话)看到推广的构建器模式的一个优点是它还可以用于有条件地构造对象,例如仅当所有强制参数都正确或其他所需资源可用时。在这方面,它们提供了与静态工厂方法类似的好处。

One advantage of the Builder Pattern that I rarely (if ever) see promoted is that it can also be used to conditionally construct the object, for instance only if all mandatory parameters are correct or if other required resources are available. In that respect they offer similar benefits to a static factory method.

执笔绘流年 2024-12-09 19:25:30

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

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

查看此参考:
带有 Twist 的构建器模式

I think this would be appropriate in case you have large mandatory values, though number of interfaces would increase but code would be clean

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

This will force the user to set all mandatory values and also force the order that the values are set. So to construct a person this will be the resulting code:

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

Checkout this Reference:
Builder Pattern with Twist

心如荒岛 2024-12-09 19:25:30

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

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

I just released a free Intellij plugin to tackle this kind of problem. Essentially you define a method for each parameter in your builder and you can annotate which ones are mandatory and which ones are not and the IntelliJ completion will highlight what are mandatory and optional parameters. Feel free to give it a try:

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

飘过的浮云 2024-12-09 19:25:30

构建器/工厂仍然允许您将接口与实现类型解耦(或者让您插入适配器等),假设 Widget 成为一个接口,并且您有办法注入或隐藏 new Widget.Builder。

如果您不关心解耦,并且您的实现是一次性的,那么您是对的:构建器模式并不比普通构造函数有用多少(它仍然使用 attribute-per-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();

这假设您的构建器是不可变的:构建器设置器不会设置属性并返回 this,而是返回一个新副本相反,他们自己也做出了改变。正如您上面指出的,参数对象是绕过重复参数样板的另一种方法。在那里,您甚至不需要构建器模式:只需将参数对象传递给实现构造函数即可。

A builder/factory still lets you decouple interface from implementation type (or lets you plug in an adapter, etc), assuming Widget becomes an interface and you have a way to inject or hide the new Widget.Builder.

If you don't care about decoupling, and your implementation is a one-off, then you're right: the builder pattern isn't much more useful than a normal constructor (it still labels its arguments with the attribute-per-builder-method style.)

If you are repeatedly creating objects with little variation in the arguments, then it may still be helpful. You could pass in, cache, etc the intermediate builder acquired after plugging in several attributes:

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

This assumes that your builders are immutable: builder setters don't set an attribute and returning this, but return a new copy of themselves with the change instead. As you pointed out above, parameter objects are another way to get around repeated argument boilerplate. There, you don't even need the builder pattern: just pass the parameter object to your implementation constructor.

热鲨 2024-12-09 19:25:30

我的解决方案是匿名类。这里的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();
  }
}

My solution with anonymous class. Here is familyName is required parameter, and givenName is Optional. The main target if this solution is force the programmer who creating Person to set requires parameters (if he will not do it, Java will not compile).

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

Actually, the target is not reached completely: because I couldn't to force the programmer to write this.familyName = familyName;, but he must to implement the setFamilyName. If programmer is not imbecile, he knows what he must to do in this method, but he can forget it because of fatigue.

Implementation:

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();
  }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文