Typsicheres Builder-Pattern mit Java


Der folgende Code ist eine ziemlich direkte Übersetzung des typsicheren Builder-Patterns aus Scala nach Java. Es gibt mehrere Versionen, aber mir gefällt die Variante von Daniel Sobral am besten, weil damit auch bei der „Verkabelung“ im Builder weniger Fehler möglich sind. Etwas kürzer ist die ursprüngliche Version von Rafael Ferreira, aber dort ist die Zuordnung von Typparametern zu den Werten „willkürlich“, so dass man beim Builder-Schreiben eher etwas falsch machen kann.

Zuerst zu unserer zu bauenden Klasse und einem kleinen Anwendungsbeispiel:

public class Person {

  private String firstName;
  private String lastName;
  private int age;

  public Person(String firstName, String lastName, int age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  public String getFirstName() { return firstName; }

  public String getLastName() { return lastName; }

  public int getAge() { return age; }

  @Override
  public String toString() {
    return "Person{" +
      "firstName='" + firstName + '\'' +
      ", lastName='" + lastName + '\'' +
      ", age=" + age +'}';
  }
}

...

Person person = build(NEW.withLastName("Mustermann").withAge(62).withFirstName("Heinz"));

//das compiliert *nicht*
Person person = build(NEW.withLastName("Mustermann").withAge(62));

Zentral für Daniel Sobrals Idee ist ein Wrapper-Typ mit zwei Ausprägungen („da“ und „nicht da“) für die zu sammelnden Werte. Leider hat Optional in Java 8 keine Unterklassen wie Option in Scala, deshalb brauchen wir unsere eigene kleine Hierarchie. Und dann können wir auch gleich andere Namen verwenden, die unserem Zweck eher entsprechen:

public interface Value<T> {}

public final class Without<T> implements Value<T> {
  private final static Without<?> WITHOUT = new Without<>();

  private Without(){}

  @SuppressWarnings("unchecked") 
  public static <T> Without<T> without() { 
    return (Without<T>) WITHOUT; 
  }
}

public final class With<T> implements Value<T> {
  private final T t;

  public With(T t) { this.t = t; }

  public T get() { return t; }
}

Man könnte die Without-Klasse auch hübscher (also ohne Cast) schreiben, aber mit meiner Variante kommt man mit einem einzigen Objekt aus. Nun zur eigentlichen Magie, dem Builder. Ich bitte zu beachten, dass die Klasse mit ihrem Generic-Verhau zwar ziemlich länglich aussieht, der Nutzer davon aber nichts mitbekommt (siehe Anwendungsbeispiel oben).


import static builder.Without.without;

public class PersonBuilder<FirstName extends Value<String>,
          LastName extends Value<String>,
          Age extends Value<Integer>> {

  public static final PersonBuilder<Without<String>, Without<String>, Without<Integer>> NEW =
    new PersonBuilder<Without<String>, Without<String>, Without<Integer>>(without(), without(), without());

  private FirstName firstName;
  private LastName lastName;
  private Age age;

  private PersonBuilder(FirstName firstName, LastName lastName, Age age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  public PersonBuilder<With<String>, LastName, Age> withFirstName(String firstName) {
    return new PersonBuilder<>(new With<>(firstName), lastName, age);
  }

  public PersonBuilder<FirstName, With<String>, Age> withLastName(String lastName) {
    return new PersonBuilder<>(firstName, new With<>(lastName), age);
  }

  public PersonBuilder<FirstName, LastName, With<Integer>> withAge(int age) {
    return new PersonBuilder<>(firstName, lastName, new With<>(Integer.valueOf(age)));
  }

  public static Person build(PersonBuilder<With<String>, With<String>, With<Integer>> builder) {
    return new Person(builder.firstName.get(), builder.lastName.get(), builder.age.get());
  }
}

Das Prinzip ist trotz der Generic-Orgie recht einfach zu verstehen: Ausgehend von einem Builder mit lauter Withouts werden letztere schrittweise durch With-Typparameter mit den zugehörigen Werten ersetzt. Und nur ein Builder mit lauter With-Typparametern kann verwendet werden, um am Ende eine Person zu erzeugen (deshalb muss das auch eine statische Methode statt wie üblich eine Instanzmethode sein). Man sieht in der build-Methode, wie die Generics (im Gegensatz zu Rafael Ferreiras Version) wirklich mit den internen Werten gekoppelt sind: Nur mit With als Typparametern sind die get-Methoden verfügbar.

Natürlich erscheint der vorgestellte Code erst einmal ziemlich umfangreich. Dabei sollte man aber berücksichtigen, dass der Unterschied zu einem „normalen“ Builder ohne Compilezeit-Test von erforderlichen Argumenten nicht besonders groß ist: Da die Value-Hierarchie wiederverwendet werden kann, kommen im Prinzip nur die Generics im Builder hinzu. Ich denke, wenn man sich sowieso schon dazu entschieden hat, einen Builder zu verwenden, ist es naheliegend, gleich diese Version zu verwenden, wenn es in der zu erzeugenden Klasse „Pflichtfelder“ gibt.

Advertisements

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s