Mehr zum Fluent Builder

Kaum erscheint der Beitrag  Fluent Builder statt komplexe Konstruktoren, da regt sich auch schon der Unmut bei den Lesern.

Denn der Verwechslungsgefahr bei den verschiedenen Parametern, kann auch durch eigene Typen für Vorname, Nachname und Anrede vorgebeugt werden.

public final class FirstName {
  private String name;
  public FirstName(String name) {
    this.name = Objects.requireNonNull(name);
  }
  public String value() {
    return name;
  }
}

Diese Klasse verwenden wir anstelle des String Typen und können so einer versehentlichen Parametervertauschung vorbeugen. Unser Beispiel mit Mary Poppins wird dann wie folgt geschrieben.

Person.female().with(new Title("Ms")).with(new FirstName("Mary")).with(new LastName("Poppins")).get();

Die Methodennamen reduzieren wir auf with, da der Parametertyp die Methode erklärt.

Momentan kennen wir für die Anrede nur die Werte Ms, Mr und Mrs, daher ersetzen wir den Typen Title durch ein Interface. Dieses Interface lassen wir von einer Enum StandardTitle implementieren. Damit greifen wir in unserem Code auf Konstanten zurück, berauben uns aber nicht der Möglichkeit eine andere Anrede zu verwenden.

public interface Title {
  String value();
}

public enum StandardTitle implements Title {
  MS, MR, MRS;

 @Override
 public String value() {
   return name();
 }
}

Unser Beispiel mit einem static import verkürzt sich auf

Person.female().with(MS).with(new FirstName("Mary")).with(new LastName("Poppins")).get();

Eine Alternative oder Ergänzung zum Fluent Builder ist der Einsatz eines Prototypen. Wir ergänzen die Klasse Person dazu um Methoden, die modifizierte Kopien der prototypischen Instanz liefern. Bei diesem Ansatz können alle erzeugten Kopien immutable sein. Die Werte der Kopien können wir über den Konstruktor setzen.  Wir benötigen somit keine Setter-Methoden mit denen die Instanzen versehentlich verändert werden können.

public Person with(FirstName firstName) {
  Person result = new Person(this);
  result.firstName = Objects.requireNonNull(firstName);
  return result;
}

Die erste Zeile liefert eine Kopie der aktuellen Instanz, deren firstName Attribute in der zweiten Zeile ersetzt wird.

Es gibt eine Menge Möglichkeiten, wie man die Zuständigkeiten zwischen Builder und Prototype aufteilt. Da wir in unserem Beispiel davon ausgehen, dass nur das Geschlecht in den Instanzen gesetzt sein muss, können wir unseren Builder auf die beiden Methoden male() und female() reduzieren, die ein Objekt vom Typ Person zurückliefern. Alle weiteren Modifikationen erfolgen dann über den erzeugten Prototypen und es entfällt die get() Methode des Builders.

Person.female().with(MS).with(new FirstName("Mary")).with(new LastName("Poppins"))

Der Einsatz spezifischer Klassen für die Parameter, vereinfacht nicht nur die Verwendung des Builders, sondern verbessert auch die Verständlichkeit der gesamten Codebasis. Die spezifischen Fachlichkeiten von Vorname, Nachname und Anrede werden in den entsprechenden Klassen formuliert, was im vorher verwendeten Typ String nicht möglich war.