Und noch ein Fluent Builder

Nachdem wir schon zwei Ansätze für einen Fluent Builder angeschaut haben, wollen wir uns diesmal den Einsatz einer Chain in einem Builder anschauen.

Häufig möchte man einen Builder wiederverwenden, statt ihn jedes Mal für eine Instanz neu zu erzeugen, zu konfigurieren und dann wegzuwerfen.

In unserem bisherigen Beispiel erzeugen wir Personen Objekte

Person mother = Person.female().with(new LastName("Banks")).with(new FirstName("Winifred")); 
Person daughter = Person.female().with(new LastName("Banks")).with(new FirstName("Jane"));
Person father = Person.male().with(new LastName("Banks")).with(new FirstName("George")); 
Person son =Person.male().with(new LastName("Banks")).with(new FirstName("Michael"));

und müssen jedesmal die gesamte gemeinsame Konfiguration wiederholen. Schöner wäre es einen vorkonfigurierten Builder wiederzuverwenden.

PersonBuilder familyBanksBuilder = Person.create().with(LastName("Banks");
Person mother = familyBanksBuilder.withFirstName("Winifred").get();
Person daughter = familyBanksBuilder.withFirstName("Jane").female(MS).get();
PersonBuilder maleMembersBuilder = familyBanksBuilder .male(); 
Person father = maleMembersBuilder.withFirstName("George").get(); 
Person son = maleMembersBuilder.withFirstName("Michael).get();

Damit wir unseren Builder in dieser Form verwenden können, müssen wir entweder mit einem Prototypen arbeiten oder die Erzeugung unserer Instanzen verzögern, bis die get() Methode aufgerufen wird. Das Herzstück unserer Lösung ist ein FunctionalInterface mit dem wir eine Instanz modifizieren können

@FunctionalInterface
public interface ChainBuilder<T> extends Function<T, T> {
   default ChainBuilder<T> chain(ChainBuilder<T> before) {
      Objects.requireNonNull(before);
      return v -> apply(before.apply(v));
   }
}

Die Default Methode chain() im Interface erlaubt es, zwei ChainBuilder miteinander zu verketten, indem das Ergebnis des einen Funktionsaufrufes als Eingabe für den anderen verwendet wird. Unser PersonBuilder verändert sich durch den Einsatz dieses FunctionalInterface

public class PersonBuilder  {
  private final ChainBuilder<Person> chain;

  private PersonBuilder(ChainBuilder<Person> chain) {
    this.chain = chain;
  }

  public static PersonBuilder create() {
    return new PersonBuilder(x -> x);
  }

  public PersonBuilder with(FirstName firstName) {
    Objects.requireNonNull(firstName);
    ChainBuilder<Person> builder = x -> { x.firstName = firstName; };
    return new PersonBuilder(builder.chain(chain));
  }

  public Person get() {
    return chain.apply(new Person());
  }
}

In den with() Methoden werden neue ChainBilder an den Anfang der Kette gehängt und in einem neuen PersonBuilder zurückgegeben. Dadurch erhält man mit jedem with() Aufruf eine unabhängige Erzeugungskette für Person Objekte, die durch den Aufruf der get() Methode gestartet wird.

In der get() Methode wird das Person Objekt erzeugt und durch die ChainBuilder Instanzen nach und nach modifiziert. Wurde keine with() Methode zuvor auf dem Builder aufgerufen, dann greift die Identitätsfunktion x -> x aus der create() Methode.

Der Einsatz einer Chain zur Erzeugung komplexer Objektkonfigurationen ist durch den Einsatz von Lamda Funktionen in Java kein großer Aufwand mehr und hilft komplexen Code elegant zu reduzieren.