Recursive fluent builder
Problem statement
I have entities whose relations form a graph. As an example, let's imagine User
s and Group
s. Each user has a Set
of groups s*he belongs to; each Group
has a Set
of User
s, representing its members. When constructing objects of those types, there is no clear hierachy. One could start by constructing a Group
and add User
s to the group, as well as start with constructing an User
and add Group
s to that user.
To ease construction, I want to have a fluent API for constructing those objects. Looking online, the most advanced solutions I have found assume a hieracical (i.e. tree-like) relation model, e.g.:
- An
Order
hasOrderItem
s
OrderItems
have anOrderDescription
- ....
Implementing this kind of fluent builder is easy enough, since an OrderItemBuilder
always needs some kind of callback to an Order
, and an Order always needs to have a build()
method.
With the User
-Group
-example, the separation is not as clear. When starting construction a User
-objects, a build()
-method must be present. Starting a (recursive) Group
-construction, the builder must not provide build()
, but and()
(or some other callback-method) instead. Here is an example usage:
Group root = groupBuilder // may be injected through some means
.withName("root")
.withUser()
.name("John Doe")
.email("john@doe.com")
// must not compile:
// .build()
.and()
// must not compile:
//.and()
.build();
User jane = userBuilder // may be injected thorugh some means
.withName("Jane Doe")
.withGroup()
.name("default")
// must not compile:
// .build()
.and()
.withEmail("jane@doe.com")
// must not compile:
// .and()
.build()
My solution
follows the following concept:
- One
UserBuilder
for "root"-consturction having aUser build()
-method, - One
UserBuilderNested
, if aUser
is constructed within the construction of another object, thus providing anand()
-method, which internally calls some callback-function, passing the constructed user to the enclosing builder - both versions for the
Group
-object.
To avoid WET-programming, I created two interfaces UserBuilderCore
and GroupBuilderCore
, containing only the with...(...)
methods for the respective builder. UserBuilder
and UserBuilderNested
implement UserBuilderCore
; GroupBuilder
and GroupBuilderNested
implement GroupBuilderCore
. The code for User
and its builders is shown at the end of the question, the code for Group
and its builders is analogue. The whole code can be found on bitbucket.
Pros
- The interfaces are fully functional and type-safe.
Cons
- For every relation in a class (e.g., lets assume each
User
gets an additionalSet<Post> posts
of written posts), one additional generic parameter has to be introduced. - As of now,
UserImpl
andGroupImpl
are tightly coupled. I have not yet found a possibility to modify the generic parameters in such a way that I could e.g. inject someUserBuilderNested
inGroupImplBuilder
. - Furhtermore, adding a new
User
-implementation requires the correct generic wiring, as shown in the example implementation.
Request
I am looking for ways to improve the code in the following way:
- Avoid adding additional generic parameters for new relationships (if possible).
- Simplify the current design to allow easier extensability.
- Decouple the
UserBuilder
- andGroupBuilder
-implementations. - Any other remarks are of course welcome, but I know that the question is already pretty comprehensive. The points above are my main concern.
Remarks on the provided code
The code is only a mockup, with no validation or referencial integrity measurements (e.g., when constructing a Group
within a User
, the User
is not added to the constructed Group
). This is on purpose.
Source code
User.java
:
package com.turing.builder;
import java.util.Set;
public interface User {
String getName();
String getEmail();
Set<Group> getGroups();
void addGroup(Group group);
}
// UserBuilderCore.java
package com.turing.builder;
public interface UserBuilderCore< // @formatter:off
S extends UserBuilderCore<S, C>,
C extends GroupBuilderNested<C, ?, S>> { // @formatter:on
S withName(String name);
S withEmail(String email);
S withGroup(Group group);
C withGroup();
}
UserBuilder.java
:
package com.turing.builder;
public interface UserBuilder< // @formatter:off
S extends UserBuilder<S, C>,
C extends GroupBuilderNested<C, ?, S>> // @formatter:on
extends UserBuilderCore<S, C> {
User build();
}
UserBuilderNested.java
:
package com.turing.builder;
public interface UserBuilderNested< // @formatter:off
S extends UserBuilderNested<S, C, P>,
C extends GroupBuilderNested<C, ?, S>,
P extends GroupBuilderCore<P, ?>> // @formatter:on
extends UserBuilderCore<S, C> {
P and();
}
UserImpl.java
:
package com.turing.builder.impl;
import com.turing.builder.Group;
import com.turing.builder.User;
import com.turing.builder.UserBuilder;
import com.turing.builder.UserBuilderCore;
import com.turing.builder.UserBuilderNested;
import com.turing.builder.impl.GroupImpl.GroupImplBuilderCore;
import com.turing.builder.impl.GroupImpl.GroupImplBuilderNested;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
public class UserImpl implements User {
private String name;
private String email;
private Set<Group> groups = new HashSet<>();
@Override
public String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
@Override
public String getEmail() {
return email;
}
private void setEmail(String email) {
this.email = email;
}
@Override
public Set<Group> getGroups() {
return groups;
}
private void setGroups(Set<Group> groups) {
this.groups = groups;
}
@Override
public void addGroup(Group group) {
this.groups.add(group);
}
@Override
public String toString() {
return String.format("User %s=%s",
getName(),
getGroups().stream()
.map(Group::getName)
.collect(Collectors.toList()));
}
protected abstract static class UserImplBuilderCore< // @formatter:off
S extends UserImplBuilderCore<S>>
implements UserBuilderCore<
/* S = */ S,
/* C = */ GroupImplBuilderNested<S>> { // @formatter:on
private UserImpl managed = new UserImpl();
@Override
@SuppressWarnings("unchecked")
public S withName(String name) {
managed.setName(name);
return ((S) this);
}
@Override
@SuppressWarnings("unchecked")
public S withEmail(String email) {
managed.setEmail(email);
return ((S) this);
}
@Override
@SuppressWarnings("unchecked")
public S withGroup(Group group) {
managed.addGroup(group);
return ((S) this);
}
@Override
public GroupImplBuilderNested<S> withGroup() {
return new GroupImplBuilderNested<>(this::withGroup);
}
protected User construct() {
User constructed = this.managed;
this.managed = new UserImpl();
return constructed;
}
}
public static class UserImplBuilder extends UserImplBuilderCore<UserImplBuilder>
implements UserBuilder< // @formatter:off
/* S = */ UserImplBuilder,
/* C = */ GroupImplBuilderNested<UserImplBuilder>> { // @formatter:on
public User build() {
return construct();
}
}
public static class UserImplBuilderNested<T extends GroupImplBuilderCore<T>>
extends UserImplBuilderCore<UserImplBuilderNested<T>>
implements UserBuilderNested< // @formatter:off
/* S = */ UserImplBuilderNested<T>,
/* C = */ GroupImplBuilderNested<UserImplBuilderNested<T>>,
/* P = */ T> { // @formatter:on
private final Function<User, T> callback;
public UserImplBuilderNested(Function<User, T> callback) {
this.callback = callback;
}
public T and() {
return callback.apply(construct());
}
}
}
java generics fluent-interface
New contributor
add a comment |
Problem statement
I have entities whose relations form a graph. As an example, let's imagine User
s and Group
s. Each user has a Set
of groups s*he belongs to; each Group
has a Set
of User
s, representing its members. When constructing objects of those types, there is no clear hierachy. One could start by constructing a Group
and add User
s to the group, as well as start with constructing an User
and add Group
s to that user.
To ease construction, I want to have a fluent API for constructing those objects. Looking online, the most advanced solutions I have found assume a hieracical (i.e. tree-like) relation model, e.g.:
- An
Order
hasOrderItem
s
OrderItems
have anOrderDescription
- ....
Implementing this kind of fluent builder is easy enough, since an OrderItemBuilder
always needs some kind of callback to an Order
, and an Order always needs to have a build()
method.
With the User
-Group
-example, the separation is not as clear. When starting construction a User
-objects, a build()
-method must be present. Starting a (recursive) Group
-construction, the builder must not provide build()
, but and()
(or some other callback-method) instead. Here is an example usage:
Group root = groupBuilder // may be injected through some means
.withName("root")
.withUser()
.name("John Doe")
.email("john@doe.com")
// must not compile:
// .build()
.and()
// must not compile:
//.and()
.build();
User jane = userBuilder // may be injected thorugh some means
.withName("Jane Doe")
.withGroup()
.name("default")
// must not compile:
// .build()
.and()
.withEmail("jane@doe.com")
// must not compile:
// .and()
.build()
My solution
follows the following concept:
- One
UserBuilder
for "root"-consturction having aUser build()
-method, - One
UserBuilderNested
, if aUser
is constructed within the construction of another object, thus providing anand()
-method, which internally calls some callback-function, passing the constructed user to the enclosing builder - both versions for the
Group
-object.
To avoid WET-programming, I created two interfaces UserBuilderCore
and GroupBuilderCore
, containing only the with...(...)
methods for the respective builder. UserBuilder
and UserBuilderNested
implement UserBuilderCore
; GroupBuilder
and GroupBuilderNested
implement GroupBuilderCore
. The code for User
and its builders is shown at the end of the question, the code for Group
and its builders is analogue. The whole code can be found on bitbucket.
Pros
- The interfaces are fully functional and type-safe.
Cons
- For every relation in a class (e.g., lets assume each
User
gets an additionalSet<Post> posts
of written posts), one additional generic parameter has to be introduced. - As of now,
UserImpl
andGroupImpl
are tightly coupled. I have not yet found a possibility to modify the generic parameters in such a way that I could e.g. inject someUserBuilderNested
inGroupImplBuilder
. - Furhtermore, adding a new
User
-implementation requires the correct generic wiring, as shown in the example implementation.
Request
I am looking for ways to improve the code in the following way:
- Avoid adding additional generic parameters for new relationships (if possible).
- Simplify the current design to allow easier extensability.
- Decouple the
UserBuilder
- andGroupBuilder
-implementations. - Any other remarks are of course welcome, but I know that the question is already pretty comprehensive. The points above are my main concern.
Remarks on the provided code
The code is only a mockup, with no validation or referencial integrity measurements (e.g., when constructing a Group
within a User
, the User
is not added to the constructed Group
). This is on purpose.
Source code
User.java
:
package com.turing.builder;
import java.util.Set;
public interface User {
String getName();
String getEmail();
Set<Group> getGroups();
void addGroup(Group group);
}
// UserBuilderCore.java
package com.turing.builder;
public interface UserBuilderCore< // @formatter:off
S extends UserBuilderCore<S, C>,
C extends GroupBuilderNested<C, ?, S>> { // @formatter:on
S withName(String name);
S withEmail(String email);
S withGroup(Group group);
C withGroup();
}
UserBuilder.java
:
package com.turing.builder;
public interface UserBuilder< // @formatter:off
S extends UserBuilder<S, C>,
C extends GroupBuilderNested<C, ?, S>> // @formatter:on
extends UserBuilderCore<S, C> {
User build();
}
UserBuilderNested.java
:
package com.turing.builder;
public interface UserBuilderNested< // @formatter:off
S extends UserBuilderNested<S, C, P>,
C extends GroupBuilderNested<C, ?, S>,
P extends GroupBuilderCore<P, ?>> // @formatter:on
extends UserBuilderCore<S, C> {
P and();
}
UserImpl.java
:
package com.turing.builder.impl;
import com.turing.builder.Group;
import com.turing.builder.User;
import com.turing.builder.UserBuilder;
import com.turing.builder.UserBuilderCore;
import com.turing.builder.UserBuilderNested;
import com.turing.builder.impl.GroupImpl.GroupImplBuilderCore;
import com.turing.builder.impl.GroupImpl.GroupImplBuilderNested;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
public class UserImpl implements User {
private String name;
private String email;
private Set<Group> groups = new HashSet<>();
@Override
public String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
@Override
public String getEmail() {
return email;
}
private void setEmail(String email) {
this.email = email;
}
@Override
public Set<Group> getGroups() {
return groups;
}
private void setGroups(Set<Group> groups) {
this.groups = groups;
}
@Override
public void addGroup(Group group) {
this.groups.add(group);
}
@Override
public String toString() {
return String.format("User %s=%s",
getName(),
getGroups().stream()
.map(Group::getName)
.collect(Collectors.toList()));
}
protected abstract static class UserImplBuilderCore< // @formatter:off
S extends UserImplBuilderCore<S>>
implements UserBuilderCore<
/* S = */ S,
/* C = */ GroupImplBuilderNested<S>> { // @formatter:on
private UserImpl managed = new UserImpl();
@Override
@SuppressWarnings("unchecked")
public S withName(String name) {
managed.setName(name);
return ((S) this);
}
@Override
@SuppressWarnings("unchecked")
public S withEmail(String email) {
managed.setEmail(email);
return ((S) this);
}
@Override
@SuppressWarnings("unchecked")
public S withGroup(Group group) {
managed.addGroup(group);
return ((S) this);
}
@Override
public GroupImplBuilderNested<S> withGroup() {
return new GroupImplBuilderNested<>(this::withGroup);
}
protected User construct() {
User constructed = this.managed;
this.managed = new UserImpl();
return constructed;
}
}
public static class UserImplBuilder extends UserImplBuilderCore<UserImplBuilder>
implements UserBuilder< // @formatter:off
/* S = */ UserImplBuilder,
/* C = */ GroupImplBuilderNested<UserImplBuilder>> { // @formatter:on
public User build() {
return construct();
}
}
public static class UserImplBuilderNested<T extends GroupImplBuilderCore<T>>
extends UserImplBuilderCore<UserImplBuilderNested<T>>
implements UserBuilderNested< // @formatter:off
/* S = */ UserImplBuilderNested<T>,
/* C = */ GroupImplBuilderNested<UserImplBuilderNested<T>>,
/* P = */ T> { // @formatter:on
private final Function<User, T> callback;
public UserImplBuilderNested(Function<User, T> callback) {
this.callback = callback;
}
public T and() {
return callback.apply(construct());
}
}
}
java generics fluent-interface
New contributor
add a comment |
Problem statement
I have entities whose relations form a graph. As an example, let's imagine User
s and Group
s. Each user has a Set
of groups s*he belongs to; each Group
has a Set
of User
s, representing its members. When constructing objects of those types, there is no clear hierachy. One could start by constructing a Group
and add User
s to the group, as well as start with constructing an User
and add Group
s to that user.
To ease construction, I want to have a fluent API for constructing those objects. Looking online, the most advanced solutions I have found assume a hieracical (i.e. tree-like) relation model, e.g.:
- An
Order
hasOrderItem
s
OrderItems
have anOrderDescription
- ....
Implementing this kind of fluent builder is easy enough, since an OrderItemBuilder
always needs some kind of callback to an Order
, and an Order always needs to have a build()
method.
With the User
-Group
-example, the separation is not as clear. When starting construction a User
-objects, a build()
-method must be present. Starting a (recursive) Group
-construction, the builder must not provide build()
, but and()
(or some other callback-method) instead. Here is an example usage:
Group root = groupBuilder // may be injected through some means
.withName("root")
.withUser()
.name("John Doe")
.email("john@doe.com")
// must not compile:
// .build()
.and()
// must not compile:
//.and()
.build();
User jane = userBuilder // may be injected thorugh some means
.withName("Jane Doe")
.withGroup()
.name("default")
// must not compile:
// .build()
.and()
.withEmail("jane@doe.com")
// must not compile:
// .and()
.build()
My solution
follows the following concept:
- One
UserBuilder
for "root"-consturction having aUser build()
-method, - One
UserBuilderNested
, if aUser
is constructed within the construction of another object, thus providing anand()
-method, which internally calls some callback-function, passing the constructed user to the enclosing builder - both versions for the
Group
-object.
To avoid WET-programming, I created two interfaces UserBuilderCore
and GroupBuilderCore
, containing only the with...(...)
methods for the respective builder. UserBuilder
and UserBuilderNested
implement UserBuilderCore
; GroupBuilder
and GroupBuilderNested
implement GroupBuilderCore
. The code for User
and its builders is shown at the end of the question, the code for Group
and its builders is analogue. The whole code can be found on bitbucket.
Pros
- The interfaces are fully functional and type-safe.
Cons
- For every relation in a class (e.g., lets assume each
User
gets an additionalSet<Post> posts
of written posts), one additional generic parameter has to be introduced. - As of now,
UserImpl
andGroupImpl
are tightly coupled. I have not yet found a possibility to modify the generic parameters in such a way that I could e.g. inject someUserBuilderNested
inGroupImplBuilder
. - Furhtermore, adding a new
User
-implementation requires the correct generic wiring, as shown in the example implementation.
Request
I am looking for ways to improve the code in the following way:
- Avoid adding additional generic parameters for new relationships (if possible).
- Simplify the current design to allow easier extensability.
- Decouple the
UserBuilder
- andGroupBuilder
-implementations. - Any other remarks are of course welcome, but I know that the question is already pretty comprehensive. The points above are my main concern.
Remarks on the provided code
The code is only a mockup, with no validation or referencial integrity measurements (e.g., when constructing a Group
within a User
, the User
is not added to the constructed Group
). This is on purpose.
Source code
User.java
:
package com.turing.builder;
import java.util.Set;
public interface User {
String getName();
String getEmail();
Set<Group> getGroups();
void addGroup(Group group);
}
// UserBuilderCore.java
package com.turing.builder;
public interface UserBuilderCore< // @formatter:off
S extends UserBuilderCore<S, C>,
C extends GroupBuilderNested<C, ?, S>> { // @formatter:on
S withName(String name);
S withEmail(String email);
S withGroup(Group group);
C withGroup();
}
UserBuilder.java
:
package com.turing.builder;
public interface UserBuilder< // @formatter:off
S extends UserBuilder<S, C>,
C extends GroupBuilderNested<C, ?, S>> // @formatter:on
extends UserBuilderCore<S, C> {
User build();
}
UserBuilderNested.java
:
package com.turing.builder;
public interface UserBuilderNested< // @formatter:off
S extends UserBuilderNested<S, C, P>,
C extends GroupBuilderNested<C, ?, S>,
P extends GroupBuilderCore<P, ?>> // @formatter:on
extends UserBuilderCore<S, C> {
P and();
}
UserImpl.java
:
package com.turing.builder.impl;
import com.turing.builder.Group;
import com.turing.builder.User;
import com.turing.builder.UserBuilder;
import com.turing.builder.UserBuilderCore;
import com.turing.builder.UserBuilderNested;
import com.turing.builder.impl.GroupImpl.GroupImplBuilderCore;
import com.turing.builder.impl.GroupImpl.GroupImplBuilderNested;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
public class UserImpl implements User {
private String name;
private String email;
private Set<Group> groups = new HashSet<>();
@Override
public String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
@Override
public String getEmail() {
return email;
}
private void setEmail(String email) {
this.email = email;
}
@Override
public Set<Group> getGroups() {
return groups;
}
private void setGroups(Set<Group> groups) {
this.groups = groups;
}
@Override
public void addGroup(Group group) {
this.groups.add(group);
}
@Override
public String toString() {
return String.format("User %s=%s",
getName(),
getGroups().stream()
.map(Group::getName)
.collect(Collectors.toList()));
}
protected abstract static class UserImplBuilderCore< // @formatter:off
S extends UserImplBuilderCore<S>>
implements UserBuilderCore<
/* S = */ S,
/* C = */ GroupImplBuilderNested<S>> { // @formatter:on
private UserImpl managed = new UserImpl();
@Override
@SuppressWarnings("unchecked")
public S withName(String name) {
managed.setName(name);
return ((S) this);
}
@Override
@SuppressWarnings("unchecked")
public S withEmail(String email) {
managed.setEmail(email);
return ((S) this);
}
@Override
@SuppressWarnings("unchecked")
public S withGroup(Group group) {
managed.addGroup(group);
return ((S) this);
}
@Override
public GroupImplBuilderNested<S> withGroup() {
return new GroupImplBuilderNested<>(this::withGroup);
}
protected User construct() {
User constructed = this.managed;
this.managed = new UserImpl();
return constructed;
}
}
public static class UserImplBuilder extends UserImplBuilderCore<UserImplBuilder>
implements UserBuilder< // @formatter:off
/* S = */ UserImplBuilder,
/* C = */ GroupImplBuilderNested<UserImplBuilder>> { // @formatter:on
public User build() {
return construct();
}
}
public static class UserImplBuilderNested<T extends GroupImplBuilderCore<T>>
extends UserImplBuilderCore<UserImplBuilderNested<T>>
implements UserBuilderNested< // @formatter:off
/* S = */ UserImplBuilderNested<T>,
/* C = */ GroupImplBuilderNested<UserImplBuilderNested<T>>,
/* P = */ T> { // @formatter:on
private final Function<User, T> callback;
public UserImplBuilderNested(Function<User, T> callback) {
this.callback = callback;
}
public T and() {
return callback.apply(construct());
}
}
}
java generics fluent-interface
New contributor
Problem statement
I have entities whose relations form a graph. As an example, let's imagine User
s and Group
s. Each user has a Set
of groups s*he belongs to; each Group
has a Set
of User
s, representing its members. When constructing objects of those types, there is no clear hierachy. One could start by constructing a Group
and add User
s to the group, as well as start with constructing an User
and add Group
s to that user.
To ease construction, I want to have a fluent API for constructing those objects. Looking online, the most advanced solutions I have found assume a hieracical (i.e. tree-like) relation model, e.g.:
- An
Order
hasOrderItem
s
OrderItems
have anOrderDescription
- ....
Implementing this kind of fluent builder is easy enough, since an OrderItemBuilder
always needs some kind of callback to an Order
, and an Order always needs to have a build()
method.
With the User
-Group
-example, the separation is not as clear. When starting construction a User
-objects, a build()
-method must be present. Starting a (recursive) Group
-construction, the builder must not provide build()
, but and()
(or some other callback-method) instead. Here is an example usage:
Group root = groupBuilder // may be injected through some means
.withName("root")
.withUser()
.name("John Doe")
.email("john@doe.com")
// must not compile:
// .build()
.and()
// must not compile:
//.and()
.build();
User jane = userBuilder // may be injected thorugh some means
.withName("Jane Doe")
.withGroup()
.name("default")
// must not compile:
// .build()
.and()
.withEmail("jane@doe.com")
// must not compile:
// .and()
.build()
My solution
follows the following concept:
- One
UserBuilder
for "root"-consturction having aUser build()
-method, - One
UserBuilderNested
, if aUser
is constructed within the construction of another object, thus providing anand()
-method, which internally calls some callback-function, passing the constructed user to the enclosing builder - both versions for the
Group
-object.
To avoid WET-programming, I created two interfaces UserBuilderCore
and GroupBuilderCore
, containing only the with...(...)
methods for the respective builder. UserBuilder
and UserBuilderNested
implement UserBuilderCore
; GroupBuilder
and GroupBuilderNested
implement GroupBuilderCore
. The code for User
and its builders is shown at the end of the question, the code for Group
and its builders is analogue. The whole code can be found on bitbucket.
Pros
- The interfaces are fully functional and type-safe.
Cons
- For every relation in a class (e.g., lets assume each
User
gets an additionalSet<Post> posts
of written posts), one additional generic parameter has to be introduced. - As of now,
UserImpl
andGroupImpl
are tightly coupled. I have not yet found a possibility to modify the generic parameters in such a way that I could e.g. inject someUserBuilderNested
inGroupImplBuilder
. - Furhtermore, adding a new
User
-implementation requires the correct generic wiring, as shown in the example implementation.
Request
I am looking for ways to improve the code in the following way:
- Avoid adding additional generic parameters for new relationships (if possible).
- Simplify the current design to allow easier extensability.
- Decouple the
UserBuilder
- andGroupBuilder
-implementations. - Any other remarks are of course welcome, but I know that the question is already pretty comprehensive. The points above are my main concern.
Remarks on the provided code
The code is only a mockup, with no validation or referencial integrity measurements (e.g., when constructing a Group
within a User
, the User
is not added to the constructed Group
). This is on purpose.
Source code
User.java
:
package com.turing.builder;
import java.util.Set;
public interface User {
String getName();
String getEmail();
Set<Group> getGroups();
void addGroup(Group group);
}
// UserBuilderCore.java
package com.turing.builder;
public interface UserBuilderCore< // @formatter:off
S extends UserBuilderCore<S, C>,
C extends GroupBuilderNested<C, ?, S>> { // @formatter:on
S withName(String name);
S withEmail(String email);
S withGroup(Group group);
C withGroup();
}
UserBuilder.java
:
package com.turing.builder;
public interface UserBuilder< // @formatter:off
S extends UserBuilder<S, C>,
C extends GroupBuilderNested<C, ?, S>> // @formatter:on
extends UserBuilderCore<S, C> {
User build();
}
UserBuilderNested.java
:
package com.turing.builder;
public interface UserBuilderNested< // @formatter:off
S extends UserBuilderNested<S, C, P>,
C extends GroupBuilderNested<C, ?, S>,
P extends GroupBuilderCore<P, ?>> // @formatter:on
extends UserBuilderCore<S, C> {
P and();
}
UserImpl.java
:
package com.turing.builder.impl;
import com.turing.builder.Group;
import com.turing.builder.User;
import com.turing.builder.UserBuilder;
import com.turing.builder.UserBuilderCore;
import com.turing.builder.UserBuilderNested;
import com.turing.builder.impl.GroupImpl.GroupImplBuilderCore;
import com.turing.builder.impl.GroupImpl.GroupImplBuilderNested;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
public class UserImpl implements User {
private String name;
private String email;
private Set<Group> groups = new HashSet<>();
@Override
public String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
@Override
public String getEmail() {
return email;
}
private void setEmail(String email) {
this.email = email;
}
@Override
public Set<Group> getGroups() {
return groups;
}
private void setGroups(Set<Group> groups) {
this.groups = groups;
}
@Override
public void addGroup(Group group) {
this.groups.add(group);
}
@Override
public String toString() {
return String.format("User %s=%s",
getName(),
getGroups().stream()
.map(Group::getName)
.collect(Collectors.toList()));
}
protected abstract static class UserImplBuilderCore< // @formatter:off
S extends UserImplBuilderCore<S>>
implements UserBuilderCore<
/* S = */ S,
/* C = */ GroupImplBuilderNested<S>> { // @formatter:on
private UserImpl managed = new UserImpl();
@Override
@SuppressWarnings("unchecked")
public S withName(String name) {
managed.setName(name);
return ((S) this);
}
@Override
@SuppressWarnings("unchecked")
public S withEmail(String email) {
managed.setEmail(email);
return ((S) this);
}
@Override
@SuppressWarnings("unchecked")
public S withGroup(Group group) {
managed.addGroup(group);
return ((S) this);
}
@Override
public GroupImplBuilderNested<S> withGroup() {
return new GroupImplBuilderNested<>(this::withGroup);
}
protected User construct() {
User constructed = this.managed;
this.managed = new UserImpl();
return constructed;
}
}
public static class UserImplBuilder extends UserImplBuilderCore<UserImplBuilder>
implements UserBuilder< // @formatter:off
/* S = */ UserImplBuilder,
/* C = */ GroupImplBuilderNested<UserImplBuilder>> { // @formatter:on
public User build() {
return construct();
}
}
public static class UserImplBuilderNested<T extends GroupImplBuilderCore<T>>
extends UserImplBuilderCore<UserImplBuilderNested<T>>
implements UserBuilderNested< // @formatter:off
/* S = */ UserImplBuilderNested<T>,
/* C = */ GroupImplBuilderNested<UserImplBuilderNested<T>>,
/* P = */ T> { // @formatter:on
private final Function<User, T> callback;
public UserImplBuilderNested(Function<User, T> callback) {
this.callback = callback;
}
public T and() {
return callback.apply(construct());
}
}
}
java generics fluent-interface
java generics fluent-interface
New contributor
New contributor
edited Dec 24 at 22:09
New contributor
asked Dec 23 at 21:47
Turing85
1043
1043
New contributor
New contributor
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
Drop nested builders and use separate builders for each type of object to be created (GroupBuilder
, UserBuilder
, WhateverBuilder
).
Then you may use the builders like so:
Group root = GroupBuilder
.withName("root")
.withUser(UserBuilder.
.withName("John Doe")
.withEmail("john@doe.com")
// ...
.build())
.withUser(UserBuilder.
.withName("Jane Doe")
.withEmail("jane@doe.com")
.build())
.build();
This is easier to implement and probably easier to understand for users.
I am aware of this solution and it is already implemented in my solution.
– Turing85
Dec 23 at 23:28
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Turing85 is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210244%2frecursive-fluent-builder%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
Drop nested builders and use separate builders for each type of object to be created (GroupBuilder
, UserBuilder
, WhateverBuilder
).
Then you may use the builders like so:
Group root = GroupBuilder
.withName("root")
.withUser(UserBuilder.
.withName("John Doe")
.withEmail("john@doe.com")
// ...
.build())
.withUser(UserBuilder.
.withName("Jane Doe")
.withEmail("jane@doe.com")
.build())
.build();
This is easier to implement and probably easier to understand for users.
I am aware of this solution and it is already implemented in my solution.
– Turing85
Dec 23 at 23:28
add a comment |
Drop nested builders and use separate builders for each type of object to be created (GroupBuilder
, UserBuilder
, WhateverBuilder
).
Then you may use the builders like so:
Group root = GroupBuilder
.withName("root")
.withUser(UserBuilder.
.withName("John Doe")
.withEmail("john@doe.com")
// ...
.build())
.withUser(UserBuilder.
.withName("Jane Doe")
.withEmail("jane@doe.com")
.build())
.build();
This is easier to implement and probably easier to understand for users.
I am aware of this solution and it is already implemented in my solution.
– Turing85
Dec 23 at 23:28
add a comment |
Drop nested builders and use separate builders for each type of object to be created (GroupBuilder
, UserBuilder
, WhateverBuilder
).
Then you may use the builders like so:
Group root = GroupBuilder
.withName("root")
.withUser(UserBuilder.
.withName("John Doe")
.withEmail("john@doe.com")
// ...
.build())
.withUser(UserBuilder.
.withName("Jane Doe")
.withEmail("jane@doe.com")
.build())
.build();
This is easier to implement and probably easier to understand for users.
Drop nested builders and use separate builders for each type of object to be created (GroupBuilder
, UserBuilder
, WhateverBuilder
).
Then you may use the builders like so:
Group root = GroupBuilder
.withName("root")
.withUser(UserBuilder.
.withName("John Doe")
.withEmail("john@doe.com")
// ...
.build())
.withUser(UserBuilder.
.withName("Jane Doe")
.withEmail("jane@doe.com")
.build())
.build();
This is easier to implement and probably easier to understand for users.
answered Dec 23 at 22:50
aventurin
470119
470119
I am aware of this solution and it is already implemented in my solution.
– Turing85
Dec 23 at 23:28
add a comment |
I am aware of this solution and it is already implemented in my solution.
– Turing85
Dec 23 at 23:28
I am aware of this solution and it is already implemented in my solution.
– Turing85
Dec 23 at 23:28
I am aware of this solution and it is already implemented in my solution.
– Turing85
Dec 23 at 23:28
add a comment |
Turing85 is a new contributor. Be nice, and check out our Code of Conduct.
Turing85 is a new contributor. Be nice, and check out our Code of Conduct.
Turing85 is a new contributor. Be nice, and check out our Code of Conduct.
Turing85 is a new contributor. Be nice, and check out our Code of Conduct.
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210244%2frecursive-fluent-builder%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown