Recursive fluent builder












0














Problem statement



I have entities whose relations form a graph. As an example, let's imagine Users and Groups. Each user has a Set of groups s*he belongs to; each Group has a Set of Users, representing its members. When constructing objects of those types, there is no clear hierachy. One could start by constructing a Group and add Users to the group, as well as start with constructing an User and add Groups 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 has OrderItems


  • OrderItems have an OrderDescription

  • ....


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 a User build()-method,

  • One UserBuilderNested, if a User is constructed within the construction of another object, thus providing an and()-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 additional Set<Post> posts of written posts), one additional generic parameter has to be introduced.

  • As of now, UserImpl and GroupImpl 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 some UserBuilderNested in GroupImplBuilder.

  • 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- and GroupBuilder-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());
}
}
}









share|improve this question









New contributor




Turing85 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

























    0














    Problem statement



    I have entities whose relations form a graph. As an example, let's imagine Users and Groups. Each user has a Set of groups s*he belongs to; each Group has a Set of Users, representing its members. When constructing objects of those types, there is no clear hierachy. One could start by constructing a Group and add Users to the group, as well as start with constructing an User and add Groups 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 has OrderItems


    • OrderItems have an OrderDescription

    • ....


    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 a User build()-method,

    • One UserBuilderNested, if a User is constructed within the construction of another object, thus providing an and()-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 additional Set<Post> posts of written posts), one additional generic parameter has to be introduced.

    • As of now, UserImpl and GroupImpl 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 some UserBuilderNested in GroupImplBuilder.

    • 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- and GroupBuilder-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());
    }
    }
    }









    share|improve this question









    New contributor




    Turing85 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
    Check out our Code of Conduct.























      0












      0








      0







      Problem statement



      I have entities whose relations form a graph. As an example, let's imagine Users and Groups. Each user has a Set of groups s*he belongs to; each Group has a Set of Users, representing its members. When constructing objects of those types, there is no clear hierachy. One could start by constructing a Group and add Users to the group, as well as start with constructing an User and add Groups 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 has OrderItems


      • OrderItems have an OrderDescription

      • ....


      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 a User build()-method,

      • One UserBuilderNested, if a User is constructed within the construction of another object, thus providing an and()-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 additional Set<Post> posts of written posts), one additional generic parameter has to be introduced.

      • As of now, UserImpl and GroupImpl 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 some UserBuilderNested in GroupImplBuilder.

      • 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- and GroupBuilder-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());
      }
      }
      }









      share|improve this question









      New contributor




      Turing85 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.











      Problem statement



      I have entities whose relations form a graph. As an example, let's imagine Users and Groups. Each user has a Set of groups s*he belongs to; each Group has a Set of Users, representing its members. When constructing objects of those types, there is no clear hierachy. One could start by constructing a Group and add Users to the group, as well as start with constructing an User and add Groups 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 has OrderItems


      • OrderItems have an OrderDescription

      • ....


      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 a User build()-method,

      • One UserBuilderNested, if a User is constructed within the construction of another object, thus providing an and()-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 additional Set<Post> posts of written posts), one additional generic parameter has to be introduced.

      • As of now, UserImpl and GroupImpl 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 some UserBuilderNested in GroupImplBuilder.

      • 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- and GroupBuilder-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






      share|improve this question









      New contributor




      Turing85 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.











      share|improve this question









      New contributor




      Turing85 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      share|improve this question




      share|improve this question








      edited Dec 24 at 22:09





















      New contributor




      Turing85 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      asked Dec 23 at 21:47









      Turing85

      1043




      1043




      New contributor




      Turing85 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.





      New contributor





      Turing85 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






      Turing85 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






















          1 Answer
          1






          active

          oldest

          votes


















          0














          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.






          share|improve this answer





















          • I am aware of this solution and it is already implemented in my solution.
            – Turing85
            Dec 23 at 23:28











          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.










          draft saved

          draft discarded


















          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









          0














          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.






          share|improve this answer





















          • I am aware of this solution and it is already implemented in my solution.
            – Turing85
            Dec 23 at 23:28
















          0














          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.






          share|improve this answer





















          • I am aware of this solution and it is already implemented in my solution.
            – Turing85
            Dec 23 at 23:28














          0












          0








          0






          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.






          share|improve this answer












          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.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          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


















          • 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










          Turing85 is a new contributor. Be nice, and check out our Code of Conduct.










          draft saved

          draft discarded


















          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.




          draft saved


          draft discarded














          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





















































          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







          Popular posts from this blog

          Сан-Квентин

          8-я гвардейская общевойсковая армия

          Алькесар