好的,更传统的步骤构建器方法是这样的。
不幸的是,因为我们混合了泛型和非泛型方法,所以我们必须重新声明很多方法。我不think有一个很好的方法可以解决这个问题。
基本思想就是:在接口上定义每个步骤,然后在私有类上实现它们。我们可以通过继承原始类型来使用通用接口来做到这一点。它很丑,但很有效。
public interface NumberStep {
NumberStep withNumber(int number);
}
public interface NeitherDoneStep extends NumberStep {
@Override NeitherDoneStep withNumber(int number);
<T> TypeDoneStep<T> withTyped(T type);
<S> ListDoneStep<S> withList(List<S> list);
}
public interface TypeDoneStep<T> extends NumberStep {
@Override TypeDoneStep<T> withNumber(int number);
TypeDoneStep<T> withTyped(T type);
<S> BothDoneStep<T, S> withList(List<S> list);
}
public interface ListDoneStep<S> extends NumberStep {
@Override ListDoneStep<S> withNumber(int number);
<T> BothDoneStep<T, S> withTyped(T type);
ListDoneStep<S> withList(List<S> list);
}
public interface BothDoneStep<T, S> extends NumberStep {
@Override BothDoneStep<T, S> withNumber(int number);
BothDoneStep<T, S> withTyped(T type);
BothDoneStep<T, S> withList(List<S> list);
SomeObject<T, S> create();
}
@SuppressWarnings({"rawtypes","unchecked"})
private static final class BuilderImpl implements NeitherDoneStep, TypeDoneStep, ListDoneStep, BothDoneStep {
private final int number;
private final Object typed;
private final List list;
private BuilderImpl(int number, Object typed, List list) {
this.number = number;
this.typed = typed;
this.list = list;
}
@Override
public BuilderImpl withNumber(int number) {
return new BuilderImpl(number, this.typed, this.list);
}
@Override
public BuilderImpl withTyped(Object typed) {
// we could return 'this' at the risk of heap pollution
return new BuilderImpl(this.number, typed, this.list);
}
@Override
public BuilderImpl withList(List list) {
// we could return 'this' at the risk of heap pollution
return new BuilderImpl(this.number, this.typed, list);
}
@Override
public SomeObject create() {
return new SomeObject(number, typed, list);
}
}
// static factory
public static NeitherDoneStep builder() {
return new BuilderImpl(0, null, null);
}
由于我们不希望人们访问丑陋的实现,因此我们将其设为私有,并让每个人都经历一个static
method.
否则它的工作原理与您自己的想法几乎相同:
SomeObject<String, Integer> works =
SomeObject.builder()
.withNumber(4)
.withList(new ArrayList<Integer>())
.withTyped("something")
.create();
// we could return 'this' at the risk of heap pollution
这是关于什么的?好吧,这里有一个一般性的问题,如下所示:
NeitherDoneStep step = SomeObject.builder();
BothDoneStep<String, Integer> both =
step.withTyped("abc")
.withList(Arrays.asList(123));
// setting 'typed' to an Integer when
// we already set it to a String
step.withTyped(123);
SomeObject<String, Integer> oops = both.create();
如果我们不创建副本,我们现在就会有123
伪装成String
.
(如果您仅使用构建器作为流畅的调用集,则不会发生这种情况。)
虽然我们不需要复制withNumber
,我只是采取了额外的步骤并使构建器不可变。我们创建的对象超出了我们需要的数量,但确实没有其他好的解决方案。如果每个人都将以正确的方式使用构建器,那么我们可以使其可变并且return this
.
由于我们对新颖的通用解决方案感兴趣,因此这里是单个类中的构建器实现。
这里的区别在于我们不保留类型typed
and list
如果我们第二次调用它们中的任何一个设置器。这本身并不是一个真正的缺点,我想只是有所不同。这意味着我们可以这样做:
SomeObject<Long, String> =
SomeObject.builder()
.withType( new Integer(1) )
.withList( Arrays.asList("abc","def") )
.withType( new Long(1L) ) // <-- changing T here
.create();
public static class OneBuilder<T, S> {
private final int number;
private final T typed;
private final List<S> list;
private OneBuilder(int number, T typed, List<S> list) {
this.number = number;
this.typed = typed;
this.list = list;
}
public OneBuilder<T, S> withNumber(int number) {
return new OneBuilder<T, S>(number, this.typed, this.list);
}
public <TR> OneBuilder<TR, S> withTyped(TR typed) {
// we could return 'this' at the risk of heap pollution
return new OneBuilder<TR, S>(this.number, typed, this.list);
}
public <SR> OneBuilder<T, SR> withList(List<SR> list) {
// we could return 'this' at the risk of heap pollution
return new OneBuilder<T, SR>(this.number, this.typed, list);
}
public SomeObject<T, S> create() {
return new SomeObject<T, S>(number, typed, list);
}
}
// As a side note,
// we could return e.g. <?, ?> here if we wanted to restrict
// the return type of create() in the case that somebody
// calls it immediately.
// The type arguments we specify here are just whatever
// we want create() to return before withTyped(...) and
// withList(...) are each called at least once.
public static OneBuilder<Object, Object> builder() {
return new OneBuilder<Object, Object>(0, null, null);
}
创建副本和堆污染也是如此。
现在我们得到really小说。这里的想法是,我们可以通过引起捕获转换错误来“禁用”每个方法。
解释起来有点复杂,但基本思想是:
- 每个方法在某种程度上都依赖于在类上声明的类型变量。
- 通过将其返回类型将该类型变量设置为“禁用”该方法
?
.
- 如果我们尝试调用该返回值的方法,这会导致捕获转换错误。
此示例与上一个示例的区别在于,如果我们尝试第二次调用 setter,我们将收到编译器错误:
SomeObject<Long, String> =
SomeObject.builder()
.withType( new Integer(1) )
.withList( Arrays.asList("abc","def") )
.withType( new Long(1L) ) // <-- compiler error here
.create();
因此,我们只能调用每个 setter 一次。
这里的两个主要缺点是:
- 不能再次调用 setter合法的 reasons
- and can第二次调用设置者
null
文字。
我认为这是一个非常有趣的概念验证,即使它有点不切实际。
public static class OneBuilder<T, S, TCAP, SCAP> {
private final int number;
private final T typed;
private final List<S> list;
private OneBuilder(int number, T typed, List<S> list) {
this.number = number;
this.typed = typed;
this.list = list;
}
public OneBuilder<T, S, TCAP, SCAP> withNumber(int number) {
return new OneBuilder<T, S, TCAP, SCAP>(number, this.typed, this.list);
}
public <TR extends TCAP> OneBuilder<TR, S, ?, SCAP> withTyped(TR typed) {
// we could return 'this' at the risk of heap pollution
return new OneBuilder<TR, S, TCAP, SCAP>(this.number, typed, this.list);
}
public <SR extends SCAP> OneBuilder<T, SR, TCAP, ?> withList(List<SR> list) {
// we could return 'this' at the risk of heap pollution
return new OneBuilder<T, SR, TCAP, SCAP>(this.number, this.typed, list);
}
public SomeObject<T, S> create() {
return new SomeObject<T, S>(number, typed, list);
}
}
// Same thing as the previous example,
// we could return <?, ?, Object, Object> if we wanted
// to restrict the return type of create() in the case
// that someone called it immediately.
// (The type arguments to TCAP and SCAP should stay
// Object because they are the initial bound of TR and SR.)
public static OneBuilder<Object, Object, Object, Object> builder() {
return new OneBuilder<Object, Object, Object, Object>(0, null, null);
}
同样,创建副本和堆污染也是如此。
不管怎样,我希望这能给你一些想法,让你认真思考。 :)
如果你对这类事情普遍感兴趣,我建议学习带注释处理的代码生成 https://deors.wordpress.com/2011/10/08/annotation-processors/,因为你可以比手工编写更容易生成这样的东西。正如我们在评论中谈到的,手动编写这样的东西很快就会变得不切实际。