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

迄今为止,我使用了以下构建器模式的实现(与此处描述的实现相反):

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

最好的做法是什么,也许还有更好的方法,我没有考虑到这一点?

然而,最近我一直在努力去理解,当所有的参数都是强制性的(或者至少绝大多数参数是强制性的)时,这种模式是有什么好处的。

流利的建筑模式仍然是有益的:

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

2)它是无序的 – 这可以让你将参数一起分组到逻辑组中,或者作为单个构建器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()方法中对所需参数进行验证。

在适当或自然的情况下,我倾向于采用混合动力 它不一定都是构造函数, 或者每个参数都有自己的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(); 

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

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

我很少(如果有的话)看到提升的构建模式的一个优点是它也可以用来有条件地构造对象,例如只有当所有的必需参数是正确的或者其他所需的资源可用时。 在这方面,他们提供类似的好处静态工厂方法 。

最近我一直在努力去理解,当所有的参数都是强制性的时,这个模式是如何得到好处的

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

解决这个问题的一个方法是将传入的参数逻辑分组到自己的类中

当然,这是凝聚力的原则,不管对象构造的语义如何,都应该被采用。

如果Widget成为一个接口,并且您可以注入或隐藏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(); 

这假设你的构建者是不可变的:构建器设置者不会设置一个属性并返回this属性,而是用这个变化返回一个新的副本。 正如你在上面指出的,参数对象是解决重复的参数样板的另一种方法。 在那里,你甚至不需要构建器模式:只需将参数对象传递给你的实现构造函数。