Verhaltensmuster

Strategy

Für dieselbe Aufgabe gibt es mehrere Verfahren: Versandkosten je nach Land, Rabatt je nach Kundengruppe, Export in verschiedene Formate. Ohne Muster wächst dafür eine Verzweigung, die bei jeder neuen Variante angefasst wird. Strategy macht aus jedem Zweig eine eigene Klasse hinter einer gemeinsamen Schnittstelle.

Struktur

Der Kontext kennt nur die Schnittstelle und bekommt die konkrete Strategie von außen — im Konstruktor oder pro Aufruf.

interface Rabattregel
{
    public function rabatt(Warenkorb $korb): Betrag;
}

final class Mengenrabatt implements Rabattregel
{
    public function rabatt(Warenkorb $korb): Betrag
    {
        return $korb->positionen() >= 10 ? $korb->summe()->prozent(5) : Betrag::null();
    }
}

final class Preisberechnung
{
    public function __construct(private readonly Rabattregel $regel) {}

    public function endpreis(Warenkorb $korb): Betrag
    {
        return $korb->summe()->minus($this->regel->rabatt($korb));
    }
}

Das ist das Open-Closed-Prinzip in seiner konkretesten Form: offen für neue Regeln, geschlossen gegen Änderung der bestehenden.

Praxisnotiz

Strategy ist das Muster, das ich in Reviews am häufigsten vorschlage — meist dort, wo eine Methode über Jahre um elseif-Zweige gewachsen ist und jede Änderung an einer Variante das Risiko trägt, eine andere zu beschädigen. Nach der Zerlegung ist jede Regel einzeln testbar, und die Tests werden klein genug, dass sie tatsächlich geschrieben werden.

Die entscheidende Vorprüfung: Sind die Varianten wirklich gleichrangig? Ich habe oft gesehen, dass eine Strategie zusätzliche Parameter braucht, die andere nicht kennt. Dann bläht sich die Schnittstelle auf den größten gemeinsamen Nenner auf, Implementierungen ignorieren Argumente, und die Abstraktion wird zur Last. Wenn die Signatur nicht für alle Varianten natürlich ist, sind es keine Strategien — dann verbergen sich dahinter unterschiedliche Aufgaben.

Der zweite Punkt ist die Auswahl. Sie muss irgendwo stattfinden, und oft steht am Ende doch ein match — nur eben an genau einer Stelle statt verteilt. Das ist der eigentliche Gewinn, nicht das Verschwinden der Fallunterscheidung. Wer erwartet, dass sie ganz weggeht, wird enttäuscht.

Wenn Strategien zustandslos sind — und das sollten sie sein —, genügt eine Instanz pro Variante. Der Rest ist Verdrahtung im Container.

Wann es nicht passt

  • Bei zwei stabilen Varianten mit je drei Zeilen. Die Klassen kosten dann mehr Aufmerksamkeit, als die Verzweigung je gekostet hat.

  • Wenn die Varianten sich in ihren Voraussetzungen unterscheiden — siehe oben.

  • Wenn die Wahl der Variante selbst komplexe Fachlogik ist. Dann ist die Auswahlregel das eigentliche Modell und gehört benannt, nicht in ein match am Rand.

Alle Design-Patterns