构build器模式和大量的强制性参数
迄今为止,我使用了以下构build器模式的实现(与此处描述的实现相反):
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) { ... } }
这适用于我遇到的大多数情况,我需要build立一个复杂的对象与各种必需/强制性和可选参数。 然而,最近我一直在努力去理解,当所有的参数都是强制性的(或者至less绝大多数参数是强制性的)时,这种模式是有什么好处的。
解决这个问题的一种方法是将传入的参数逻辑分组到自己的类中,以减less传递给构build器构造函数的参数数量。
例如,而不是:
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()
方法中对所需参数进行validation。
最好的做法是什么,也许还有更好的方法,我没有考虑到这一点?
然而,最近我一直在努力去理解,当所有的参数都是强制性的(或者至less绝大多数参数是强制性的)时,这种模式是有什么好处的。
stream利的build筑师模式仍然是有益的:
1)它的可读性更高 – 它有效地允许命名参数,以便调用不只是一长串未命名的参数
2)它的无序 – 这可以让你把参数一起分组到逻辑组中,或者作为单个构build器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()方法中对所需参数进行validation。
在适当或自然的情况下,我倾向于采用混合动力 它不一定都是构造函数, 或者每个参数都有自己的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();
如果您有许多必需参数,则可以使用“ 步骤生成器” 。 简而言之:为每个必需的参数定义一个接口,并且构build器方法返回下一个必需的构build器接口,或者为可选方法返回构build器本身。 构build者仍然是一个实现所有接口的类。
像Kotlin和Scala这样的语言在这里更方便,因为它们提供具有默认值的命名参数。
我很less(如果有的话)看到提升的构build模式的一个优点是它也可以用来有条件地构build对象,例如只有当所有的必需参数是正确的或者其他所需的资源可用时。 在这方面,他们提供类似的好处静态工厂方法 。
最近我一直在努力去理解,当所有的参数都是强制性的时,这个模式是如何得到好处的
该模式简化了不可变类的创build并提高了可读代码的可读性。 考虑下面的Person类(使用传统的构造函数和构build器)。
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; } }
哪种构build方法更容易理解?
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();
解决这个问题的一个方法是将传入的参数逻辑分组到自己的类中
当然,这是凝聚力的原则,不pipe对象构造的语义如何,都应该被采用。
如果Widget
成为一个接口,并且您可以注入或隐藏new Widget.Builder
,那么构build器/工厂仍然允许您将接口从实现types中分离出来(或者让您插入适配器等)。
如果你不关心解耦,而你的实现是一次性的,那么你是正确的:构build器模式比普通的构造器没有什么用处(它仍然用每个构build器的属性来标记它的参数 – 方法风格。)
如果您反复创build参数variables不大的对象,那么它可能仍然有帮助。 插入几个属性后,你可以传入,caching等获得的中间构build器:
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();
这假设你的构build者是不可变的:构build器设置者不会设置一个属性并返回this
属性,而是用这个变化返回一个新的副本。 正如你在上面指出的,参数对象是解决重复的参数样板的另一种方法。 在那里,你甚至不需要生成器模式:只要将参数对象传递给你的实现构造函数。