Erzeugungsmuster

Builder

Ein Konstruktor mit acht Parametern, von denen fünf optional sind und drei nur in Kombination Sinn ergeben, ist am Aufrufort nicht mehr lesbar: new Report(true, false, null, 3, …​). Der Builder sammelt die Angaben benannt und einzeln ein und erzeugt das Objekt erst am Ende — als Ganzes, gültig, unveränderlich.

Struktur

Der Builder hält die Zwischenwerte, jede Setter-Methode gibt ihn selbst zurück (damit sich Aufrufe verketten lassen), build() prüft und erzeugt.

final class ReportBuilder
{
    private ?DateRange $zeitraum = null;
    private array $spalten = [];

    public function zeitraum(DateRange $r): self { $this->zeitraum = $r; return $this; }
    public function spalte(string $name): self  { $this->spalten[] = $name; return $this; }

    public function build(): Report
    {
        if ($this->zeitraum === null || $this->spalten === []) {
            throw new LogicException('Report braucht Zeitraum und mindestens eine Spalte.');
        }
        return new Report($this->zeitraum, $this->spalten);
    }
}

Entscheidend ist die Prüfung in build(). Ein Builder, der jeden Zustand durchwinkt, verschiebt das Problem nur vom Konstruktor in die Laufzeit.

Praxisnotiz

Der Builder ist eines der wenigen Muster, die ich fast ohne Einschränkung mag — weil er ein echtes Lesbarkeitsproblem löst und nicht bloß eine Struktur hinzufügt. Am deutlichsten wird das in Tests: Ein KundeBuilder mit sinnvollen Vorbelegungen macht aus zwanzig Zeilen Aufbau-Rauschen drei Zeilen, in denen nur noch das steht, was für diesen Test relevant ist. Das ist meist der Ort, an dem sich der erste Builder in einem Projekt lohnt.

Zwei Dinge gehen regelmäßig schief. Erstens: Builder ohne Validierung. Dann entsteht ein halbfertiges Objekt, und der Fehler taucht drei Schichten später auf, wo niemand mehr weiß, welcher Aufruf ihn erzeugt hat. Zweitens: Builder für Objekte mit drei Feldern. Der Aufwand ist dann höher als der Nutzen — benannte Argumente leisten dasselbe ohne zusätzliche Klasse.

Wenn die Sprache benannte Argumente kennt (PHP 8, Kotlin, Python), verschiebt sich die Schwelle deutlich nach oben. Der Builder rechnet sich dort erst, wenn Regeln zwischen den Schritten gelten oder das Ergebnis unveränderlich sein soll.

Wann es nicht passt

  • Bei wenigen, gleichrangigen Pflichtfeldern. Der Konstruktor ist dann klarer.

  • Wenn die vielen Parameter selbst das Problem sind: Ein Objekt mit zwölf Feldern hat meist mehr als eine Verantwortung. Der Builder macht den Aufruf hübsch und den Entwurfsfehler unsichtbar.

  • Wenn das Objekt nach dem Bauen ohnehin veränderbar bleibt — dann bringt der Umweg nichts.

Alle Design-Patterns