Я знаю, что были похожие вопросы. Однако я не видел ответа на свой вопрос.Generic fluent Builder в Java
Я представлю то, что хочу, с помощью некоторого упрощенного кода. Скажем, у меня есть сложный объект, некоторые из его значений являются универсальными:
public static class SomeObject<T, S> {
public int number;
public T singleGeneric;
public List<S> listGeneric;
public SomeObject(int number, T singleGeneric, List<S> listGeneric) {
this.number = number;
this.singleGeneric = singleGeneric;
this.listGeneric = listGeneric;
}
}
Я хотел бы построить его с беглым синтаксиса Builder. Я бы хотел сделать это элегантно. Я хочу это работало так:
SomeObject<String, Integer> works = new Builder() // not generic yet!
.withNumber(4)
// and only here we get "lifted";
// since now it's set on the Integer type for the list
.withList(new ArrayList<Integer>())
// and the decision to go with String type for the single value
// is made here:
.withTyped("something")
// we've gathered all the type info along the way
.create();
Нет небезопасного предупреждения литого и нет необходимости заранее указать общие типы (в верхней части, где строятся Builder).
Вместо этого мы предоставляем информацию о типе в явном виде, далее вниз по цепочке - вместе с вызовами withList
и withTyped
.
Теперь, что было бы самым элегантным способом его достижения?
Мне известны самые распространенные трюки, такие как использование recursive generics, но я немного поиграл с ним и не мог понять, как это относится к этому прецеденту.
Ниже мирское многословное решение, которое работает в смысле удовлетворения всех требований, но за счет большого многословия - это вводит четыре строителей (не связанные с точкой зрения наследования), представляющее четыре возможных комбинациями T
и S
типов, являющимися определяется или нет.
Это действительно работает, но это вряд ли версия, которой можно гордиться, и недостижимо, если бы мы ожидали более общих параметров, чем два.
public static class Builder {
private int number;
public Builder withNumber(int number) {
this.number = number;
return this;
}
public <T> TypedBuilder<T> withTyped(T t) {
return new TypedBuilder<T>()
.withNumber(this.number)
.withTyped(t);
}
public <S> TypedListBuilder<S> withList(List<S> list) {
return new TypedListBuilder<S>()
.withNumber(number)
.withList(list);
}
}
public static class TypedListBuilder<S> {
private int number;
private List<S> list;
public TypedListBuilder<S> withList(List<S> list) {
this.list = list;
return this;
}
public <T> TypedBothBuilder<T, S> withTyped(T t) {
return new TypedBothBuilder<T, S>()
.withList(list)
.withNumber(number)
.withTyped(t);
}
public TypedListBuilder<S> withNumber(int number) {
this.number = number;
return this;
}
}
public static class TypedBothBuilder<T, S> {
private int number;
private List<S> list;
private T typed;
public TypedBothBuilder<T, S> withList(List<S> list) {
this.list = list;
return this;
}
public TypedBothBuilder<T, S> withTyped(T t) {
this.typed = t;
return this;
}
public TypedBothBuilder<T, S> withNumber(int number) {
this.number = number;
return this;
}
public SomeObject<T, S> create() {
return new SomeObject<>(number, typed, list);
}
}
public static class TypedBuilder<T> {
private int number;
private T typed;
private Builder builder = new Builder();
public TypedBuilder<T> withNumber(int value) {
this.number = value;
return this;
}
public TypedBuilder<T> withTyped(T t) {
typed = t;
return this;
}
public <S> TypedBothBuilder<T, S> withList(List<S> list) {
return new TypedBothBuilder<T, S>()
.withNumber(number)
.withTyped(typed)
.withList(list);
}
}
Есть ли более умная техника, которую я мог бы применить?
* «недопустимый, если бы мы ожидали более общих параметров, чем только два». * Если вы хотите сохранить произвольный порядок (в вашем примере вы можете делать как с помощью typed (...). WithList (...) '* и * 'withList (...). withTyped (...)'), тогда проблема становится очень трудной, потому что вы получаете что-то вроде классов 'n!', где 'n' - количество параметров типа. Если вы примете более традиционный подход к созданию шага, то это немного проще. – Radiodef
@Radiodef Я думал, что 2^n классов: в любой точке каждый родовой тип может находиться в одном из двух состояний: уже определен или еще не определен. Но да, это серьезный недостаток этой «ручной» реализации. Вот почему мне интересно, существует ли лучшее решение; возможно, используя общие ограничения каким-то умным способом. –