Strategiedesignmuster in Java 8

1. Einleitung

In diesem Artikel werden wir uns ansehen, wie wir das Strategie-Design-Muster in Java 8 implementieren können.

Zunächst 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 verringert wird.

2. Strategiemuster

Im Wesentlichen ermöglicht 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 implementieren sie dann mehrmals für jeden möglichen Algorithmus.

Angenommen, wir müssen verschiedene Arten von Rabatten auf einen Kauf anwenden, je nachdem, ob es sich um Weihnachten, Ostern oder Neujahr handelt. Lassen Sie uns zunächst eine Discounter- Schnittstelle erstellen, die von jeder unserer Strategien implementiert wird:

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

Nehmen wir an, wir möchten 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)); } } 

Lassen Sie uns abschließend eine Strategie in einem Test ausprobieren:

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 mühsam sein kann, für jede Strategie eine konkrete Klasse zu erstellen. Die Alternative wäre, anonyme innere Typen zu verwenden, aber das ist immer noch ziemlich 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 innere Typen mehr oder weniger überflüssig gemacht. Das bedeutet, dass das Erstellen von Strategien im Einklang jetzt viel sauberer und einfacher ist.

Darüber hinaus können wir mit dem deklarativen Stil der funktionalen Programmierung Muster implementieren, die vorher nicht möglich waren.

3.1. Reduzieren der Code-Ausführlichkeit

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

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

Wie wir sehen können, ist unser Code jetzt viel sauberer und wartbarer und 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 Discounter in einer Reihe deklarieren möchten :

List 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 Discounter definieren möchten , können wir sie statisch alle an einem Ort deklarieren. Mit Java 8 können wir sogar statische Methoden in Schnittstellen definieren, wenn wir möchten.

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 können, erreichen wir viel in einem nicht sehr großen Code.

3.2. Funktionszusammensetzung nutzen

Lassen Sie uns unsere Discounter- Schnittstelle so ändern , dass sie die UnaryOperator- Schnittstelle erweitert, und dann eine kombinieren () -Methode hinzufügen :

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

Im Wesentlichen werden Refactoring wir unsere Discounter und eine Tatsache nutzen , dass ein Rabatt Anwendung eine Funktion ist , die eine wandelt BigDecimal Instanz in eine andere BigDecimal Instanz , so dass wir Methoden Zugriff vordefiniert . Da der UnaryOperator mit einer apply () -Methode geliefert wird, können wir applyDiscount einfach durch diese ersetzen .

Die kombinierte () Methode ist nur eine Abstraktion, bei der ein Discounter auf die Ergebnisse angewendet wird. Um dies zu erreichen, wird die integrierte Funktion apply () verwendet.

Versuchen wir nun, mehrere Discounter kumulativ auf einen Betrag anzuwenden . Wir werden dies tun, indem wir das funktionale Reduzieren () und unser Kombinieren () verwenden:

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

Achten Sie besonders auf das erste Reduktionsargument . 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 berücksichtigen, die wir für die funktionale Komposition aus der Box bekommen, erhalten wir auch viel mehr kostenlose Funktionen.

4. Fazit

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

Die Implementierung dieser Beispiele finden Sie auf GitHub. Dies ist ein Maven-basiertes Projekt, sollte also so wie es ist einfach auszuführen sein.