Strategieontwerppatroon in Java 8

1. Inleiding

In dit artikel zullen we bekijken hoe we het strategieontwerppatroon in Java 8 kunnen implementeren.

Eerst geven we een overzicht van het patroon en leggen we uit hoe het traditioneel wordt geïmplementeerd in oudere versies van Java.

Vervolgens zullen we het patroon opnieuw uitproberen, alleen deze keer met Java 8 lambda's, waardoor de breedsprakigheid van onze code wordt verminderd.

2. Strategiepatroon

In wezen stelt het strategiepatroon ons in staat om het gedrag van een algoritme tijdens runtime te veranderen.

Normaal gesproken beginnen we met een interface die wordt gebruikt om een ‚Äč‚Äčalgoritme toe te passen, en implementeren het vervolgens meerdere keren voor elk mogelijk algoritme.

Stel dat we een vereiste hebben om verschillende soorten kortingen toe te passen op een aankoop, afhankelijk van of het Kerstmis, Pasen of Nieuwjaar is. Laten we eerst een Discounter interface die zal worden geïmplementeerd door elk van onze strategieën:

openbare interface Discounter {BigDecimal applyDiscount (BigDecimal-bedrag); } 

Stel dat we met Pasen 50% korting willen toepassen en met Kerstmis 10% korting. Laten we onze interface implementeren voor elk van deze strategieën:

openbare statische klasse EasterDiscounter implementeert Discounter {@Override public BigDecimal applyDiscount (laatste BigDecimal-bedrag) {return amount.multiply (BigDecimal.valueOf (0.5)); }} public static class ChristmasDiscounter implementeert Discounter {@Override public BigDecimal applyDiscount (laatste BigDecimal bedrag) {return amount.multiply (BigDecimal.valueOf (0.9)); }} 

Laten we tot slot een strategie proberen in een test:

Discounter easterDiscounter = nieuwe EasterDiscounter (); BigDecimal discountedValue = easterDiscounter .applyDiscount (BigDecimal.valueOf (100)); assertThat (discountedValue) .isEqualByComparingTo (BigDecimal.valueOf (50));

Dit werkt best goed, maar het probleem is dat het een beetje vervelend kan zijn om voor elke strategie een concrete klasse te moeten maken. Het alternatief zou zijn om anonieme innerlijke typen te gebruiken, maar dat is nog steeds behoorlijk uitgebreid en niet veel handiger dan de vorige oplossing:

Discounter easterDiscounter = nieuwe Discounter () {@Override public BigDecimal applyDiscount (laatste BigDecimal-bedrag) {return amount.multiply (BigDecimal.valueOf (0.5)); }}; 

3. Gebruikmaken van Java 8

Sinds Java 8 is uitgebracht, heeft de introductie van lambda's anonieme innerlijke typen min of meer overbodig gemaakt. Dat betekent dat het op één lijn brengen van strategieën nu een stuk schoner en eenvoudiger is.

Bovendien stelt de declaratieve stijl van functioneel programmeren ons in staat patronen te implementeren die voorheen niet mogelijk waren.

3.1. Code breedsprakigheid verminderen

Laten we proberen een inline te maken PasenDiscounter, alleen deze keer met een lambda-uitdrukking:

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

Zoals we kunnen zien, is onze code nu een stuk schoner en beter te onderhouden, met hetzelfde resultaat als voorheen, maar op één regel. Eigenlijk, een lambda kan gezien worden als vervanging voor een anoniem innerlijk type.

Dit voordeel wordt duidelijker wanneer we nog meer willen aangeven Discounters in lijn:

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

Als we er veel willen definiëren Discounters, we kunnen ze allemaal statisch op één plek declareren. Met Java 8 kunnen we zelfs statische methoden in interfaces definiëren als we dat willen.

Dus in plaats van te kiezen tussen concrete klassen of anonieme innerlijke typen, laten we proberen om lambda's allemaal in één klasse te maken:

openbare interface Discounter {BigDecimal applyDiscount (BigDecimal-bedrag); statische Discounter christmasDiscounter () {retourbedrag -> amount.multiply (BigDecimal.valueOf (0.9)); } statische Discounter newYearDiscounter () {retourbedrag -> amount.multiply (BigDecimal.valueOf (0.8)); } statische Discounter easterDiscounter () {retourbedrag -> amount.multiply (BigDecimal.valueOf (0,5)); }} 

Zoals we kunnen zien, bereiken we veel in een niet erg veel code.

3.2. Gebruikmaken van functiesamenstelling

Laten we onze Discounter interface zodat het de extensie UnaryOperator interface en voeg vervolgens een combineren() methode:

openbare interface Discounter breidt UnaryOperator uit {standaard Discounter-combinatie (Discounter na) {retourwaarde -> after.apply (this.apply (waarde)); }}

In wezen herstructureren we onze Discounter en gebruikmakend van het feit dat het toepassen van een korting een functie is die een BigDecimal instantie in een andere BigDecimal voorbeeld, waardoor we toegang hebben tot vooraf gedefinieerde methoden. Zoals de UnaryOperator wordt geleverd met een van toepassing zijn() methode, kunnen we gewoon vervangen toepassenDiscount ermee.

De combineren() methode is slechts een abstractie rond het toepassen ervan Discounter naar de resultaten van dit. Het maakt gebruik van de ingebouwde functionaliteit van toepassing zijn() om dit te bereiken.

Laten we nu proberen meerdere toe te passen Discounters cumulatief tot een bedrag. Dit doen we door gebruik te maken van de functional verminderen() en onze combineren():

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

Besteed speciale aandacht aan de eerste verminderen argument. Als er geen kortingen worden gegeven, moeten we de ongewijzigde waarde retourneren. Dit kan worden bereikt door een identiteitsfunctie te bieden als de standaard discounter.

Dit is een handig en minder uitgebreid alternatief voor het uitvoeren van een standaard iteratie. Als we kijken naar de methoden die we uit de doos halen voor functionele compositie, geeft het ons ook veel meer gratis functionaliteit.

4. Conclusie

In dit artikel hebben we het strategiepatroon uitgelegd en ook laten zien hoe we lambda-expressies kunnen gebruiken om het op een minder uitgebreide manier te implementeren.

De implementatie van deze voorbeelden is te vinden op GitHub. Dit is een op Maven gebaseerd project, dus het zou gemakkelijk moeten zijn zoals het is.