Strategieentwurfsmuster in Java 8

1. Einführung

In diesem Artikel wird beschrieben, wie wir das Strategieentwurfsmuster in Java 8 implementieren können.

Zuerst geben wir einen Überblick über das Muster und erläutern, wie es traditionell in älteren Java-Versionen implementiert wurde.

Als nächstes werden wir das Muster erneut ausprobieren, diesmal jedoch mit Java 8-Lambdas, wodurch die Ausführlichkeit unseres Codes reduziert wird.

2. Strategiemuster

Im Wesentlichen erlaubt uns das Strategiemuster, das Verhalten eines Algorithmus zur Laufzeit zu ändern. **

Normalerweise beginnen wir mit einer Schnittstelle, die zum Anwenden eines Algorithmus verwendet wird, und implementiert sie dann mehrmals für jeden möglichen Algorithmus.

Angenommen, wir müssen verschiedene Arten von Preisnachlässen auf einen Kauf anwenden, je nachdem, ob es sich um Weihnachten, Ostern oder Neujahr handelt.

Zunächst erstellen wir eine Discounter -Schnittstelle, die von jeder unserer Strategien implementiert wird:

public interface Discounter {
    BigDecimal applyDiscount(BigDecimal amount);
}

Dann wollen wir zu Ostern einen Rabatt von 50% und zu Weihnachten einen Rabatt von 10% gewähren. Lassen Sie uns unsere Schnittstelle für jede dieser Strategien implementieren:

public static class EasterDiscounter implements Discounter {
    @Override
    public BigDecimal applyDiscount(final BigDecimal amount) {
        return amount.multiply(BigDecimal.valueOf(0.5));
    }
}

public static class ChristmasDiscounter implements Discounter {
   @Override
   public BigDecimal applyDiscount(final BigDecimal amount) {
       return amount.multiply(BigDecimal.valueOf(0.9));
   }
}

Versuchen wir zum Schluss eine Strategie in einem Test:

Discounter easterDiscounter = new EasterDiscounter();

BigDecimal discountedValue = easterDiscounter
  .applyDiscount(BigDecimal.valueOf(100));

assertThat(discountedValue)
  .isEqualByComparingTo(BigDecimal.valueOf(50));

Das funktioniert ganz gut, aber das Problem ist, dass es ein bisschen schmerzhaft sein kann, für jede Strategie eine konkrete Klasse erstellen zu müssen. Die Alternative wäre die Verwendung anonymer innerer Typen, aber das ist immer noch recht ausführlich und nicht viel handlicher als die vorherige Lösung:

Discounter easterDiscounter = new Discounter() {
    @Override
    public BigDecimal applyDiscount(final BigDecimal amount) {
        return amount.multiply(BigDecimal.valueOf(0.5));
    }
};

3. Java 8 nutzen

Seit der Veröffentlichung von Java 8 hat die Einführung von Lambdas anonyme Innentypen mehr oder weniger überflüssig gemacht. Das bedeutet, Strategien in Einklang zu bringen, ist jetzt viel sauberer und einfacher.

Durch den deklarativen Stil der funktionalen Programmierung können wir außerdem Muster implementieren, die zuvor nicht möglich waren.

3.1. Reduzierung der Code-Ausführlichkeit

Versuchen wir, einen Inline- EasterDiscounter zu erstellen, nur diesmal mit einem Lambda-Ausdruck:

Discounter easterDiscounter = amount -> amount.multiply(BigDecimal.valueOf(0.5));

Wie wir sehen, ist unser Code jetzt viel sauberer und wartungsfreundlicher. Er erreicht das gleiche wie zuvor, jedoch in einer einzigen Zeile. Im Wesentlichen kann ein Lambda als Ersatz für einen anonymen inneren Typ angesehen werden.

Dieser Vorteil wird deutlicher, wenn wir noch mehr Discounters in der Zeile deklarieren wollen:

List<Discounter> discounters = newArrayList(
  amount -> amount.multiply(BigDecimal.valueOf(0.9)),
  amount -> amount.multiply(BigDecimal.valueOf(0.8)),
  amount -> amount.multiply(BigDecimal.valueOf(0.5))
);

Wenn wir viele Discounters definieren möchten, können wir sie statisch an einem Ort deklarieren. Java 8 lässt uns sogar statische Methoden in Schnittstellen definieren, wenn wir wollen.

Anstatt zwischen konkreten Klassen oder anonymen inneren Typen zu wählen, versuchen wir, Lambdas in einer einzigen Klasse zu erstellen:

public interface Discounter {
    BigDecimal applyDiscount(BigDecimal amount);

    static Discounter christmasDiscounter() {
        return amount -> amount.multiply(BigDecimal.valueOf(0.9));
    }

    static Discounter newYearDiscounter() {
        return amount -> amount.multiply(BigDecimal.valueOf(0.8));
    }

    static Discounter easterDiscounter() {
        return amount -> amount.multiply(BigDecimal.valueOf(0.5));
    }
}

Wie wir sehen, erreichen wir mit wenig Code viel.

3.2. Funktionszusammenstellung nutzen

Lassen Sie uns unser Discounter -Interface so ändern, dass es das UnaryOperator -Interface erweitert, und fügen Sie dann eine combine () -Methode hinzu:

public interface Discounter extends UnaryOperator<BigDecimal> {
    default Discounter combine(Discounter after) {
        return value -> after.apply(this.apply(value));
    }
}

Im Wesentlichen überarbeiten wir unseren Discounter und nutzen die Tatsache, dass das Anwenden eines Rabatts eine Funktion ist, die eine BigDecimal -Instanz in eine andere BigDecimal -Instanz _, konvertiert, wodurch wir auf vordefinierte Methoden zugreifen können . wir können einfach applyDiscount_ damit ersetzen. **

Die Methode combine () ist nur eine Abstraktion, bei der ein Discounter auf die Ergebnisse von this angewendet wird. Um dies zu erreichen, wird die eingebaute Funktion apply () __ verwendet.

Versuchen wir nun, mehrere Discounters auf einen Betrag kumulativ anzuwenden. Wir werden dies tun, indem wir die Funktion reduce () und unser combine () verwenden:

Discounter combinedDiscounter = discounters
  .stream()
  .reduce(v -> v, Discounter::combine);

combinedDiscounter.apply(...);

Achten Sie besonders auf das erste reduce -Argument. Wenn keine Rabatte gewährt werden, müssen wir den unveränderten Wert zurückgeben. Dies kann erreicht werden, indem eine Identitätsfunktion als Standard-Discounter bereitgestellt wird.

Dies ist eine nützliche und weniger ausführliche Alternative zur Durchführung einer Standarditeration. Wenn wir die Methoden in Betracht ziehen, die wir für die funktionale Komposition verwenden, haben wir auch viel mehr Funktionalität kostenlos.

4. Fazit

In diesem Artikel haben wir das Strategiemuster erläutert und gezeigt, wie wir Lambda-Ausdrücke verwenden können, um es auf weniger weise zu implementieren.

Die Implementierung dieser Beispiele finden Sie unter https://github.com/eugenp/tutorials/tree/master/core-java-8 auf GitHub]. Dies ist ein auf Maven basierendes Projekt. Es sollte daher leicht auszuführen sein.