Sono consapevole che ci sono state domande simili. Non ho visto una risposta alla mia domanda però.Generatore fluente generico in Java
Presenterò quello che voglio con un codice semplificato. Dire che ho un oggetto complesso, alcuni dei suoi valori sono generici:
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;
}
}
vorrei costruirlo con la sintassi Builder fluente. Mi piacerebbe renderlo elegante però. Vorrei che ha funzionato così:
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();
Nessun allarme del cast non sicuri, e non c'è bisogno di specificare i tipi generici in anticipo (in alto, in cui è costruito Builder).
Invece, lasciamo che le informazioni sul tipo scorrano in modo esplicito, più in basso lungo la catena - insieme alle chiamate withList
e withTyped
.
Ora, quale sarebbe il modo più elegante per raggiungerlo?
Sono a conoscenza dei trucchi più comuni, come l'uso di recursive generics, ma l'ho usato per un po 'e non riuscivo a capire come si applica a questo caso d'uso.
Qui di seguito è una soluzione verbose mondano che opera nel senso di soddisfare tutte le esigenze, ma a costo di grandi verbosità - introduce quattro costruttori (non correlato in termini di eredità), che rappresentano quattro possibili combinazioni di T
e S
tipo, in quanto definito o no.
Funziona, ma non è una versione di cui essere orgogliosi e non mantenibile se ci aspettassimo più parametri generici di due.
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);
}
}
Esiste una tecnica più intelligente che potrei applicare?
* "impossibile da mantenere se ci si aspettassero parametri più generici di solo due" * Se si desidera mantenere l'ordinamento arbitrario (nel proprio esempio, è possibile eseguire sia con withTyped (...). WithList (...) '* e * 'withList (...). withTyped (...)') allora il problema diventa davvero difficile perché si finisce con qualcosa come 'n!' classi, dove 'n' è il numero di parametri di tipo. Se si segue un approccio passo-passo più tradizionale, allora è un po 'più semplice. – Radiodef
@Radiodef Ho pensato alle classi 2^n: in qualsiasi momento, ogni tipo generico può essere in uno dei due stati: già definito o non ancora definito. Ma sì, questo è un grave inconveniente di quell'implementazione "manuale". Ecco perché mi chiedo se esista una soluzione migliore; forse sfruttando i vincoli generici in modo intelligente. –