Problem statement:
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:
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, and GroupBuilder; 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.
User.java:
// 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
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
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
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());
}
}
}