Strategidesignmønster i Java 8

1. Introduktion

I denne artikel vil vi se på, hvordan vi kan implementere strategi design mønster i Java 8.

Først giver vi et overblik over mønsteret og forklarer, hvordan det traditionelt er blevet implementeret i ældre versioner af Java.

Dernæst vil vi prøve mønsteret igen, kun denne gang med Java 8 lambdas, hvilket reducerer vores kodes detaljerethed.

2. Strategimønster

I det væsentlige giver strategimønsteret os mulighed for at ændre en algoritmes opførsel ved kørsel.

Typisk vil vi starte med en grænseflade, der bruges til at anvende en algoritme, og derefter implementere den flere gange for hver mulig algoritme.

Lad os sige, at vi har et krav om at anvende forskellige typer rabatter på et køb, baseret på om det er jul, påske eller nytår. Lad os først oprette en Rabat interface, der vil blive implementeret af hver af vores strategier:

offentlig grænseflade Discounter {BigDecimal applyDiscount (BigDecimal beløb); } 

Lad os sige, at vi vil anvende 50% rabat i påsken og 10% rabat i julen. Lad os implementere vores grænseflade til hver af disse strategier:

offentlig statisk klasse EasterDiscounter implementerer Discounter {@Override public BigDecimal applyDiscount (final BigDecimal amount) {return amount.multiply (BigDecimal.valueOf (0.5)); }} offentlig statisk klasse ChristmasDiscounter implementerer Discounter {@Override public BigDecimal applyDiscount (final BigDecimal amount) {return amount.multiply (BigDecimal.valueOf (0.9)); }} 

Lad os endelig prøve en strategi i en test:

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

Dette fungerer ganske godt, men problemet er, at det kan være en smule smerte at skulle skabe en konkret klasse for hver strategi. Alternativet ville være at bruge anonyme indre typer, men det er stadig ret detaljeret og ikke meget mere praktisk end den tidligere løsning:

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

3. Brug af Java 8

Siden Java 8 er frigivet, har introduktionen af ​​lambdas gjort anonyme indre typer mere eller mindre overflødige. Det betyder, at det er meget renere og lettere at skabe strategier på linje.

Desuden lader den erklærende stil med funktionel programmering os implementere mønstre, der ikke var mulige før.

3.1. Reducering af kodevidenhed

Lad os prøve at oprette en inline PåskeNedsættelse, kun denne gang ved hjælp af et lambda-udtryk:

Rabat påskeDiscounter = beløb -> beløb.multiply (BigDecimal.valueOf (0.5)); 

Som vi kan se, er vores kode nu meget renere og mere vedligeholdelig og opnår den samme som før, men i en enkelt linje. I det væsentlige en lambda kan ses som en erstatning for en anonym indre type.

Denne fordel bliver mere tydelig, når vi vil erklære endnu mere Rabatter i kø:

Liste discounters = newArrayList (beløb -> beløb.multiply (BigDecimal.valueOf (0,9)), beløb -> beløb.multiply (BigDecimal.valueOf (0,8)), beløb -> beløb.multiply (BigDecimal.valueOf (0,5))) ;

Når vi vil definere masser af Rabatter, vi kan erklære dem statisk alle på ét sted. Java 8 lader os endda definere statiske metoder i grænseflader, hvis vi ønsker det.

Så i stedet for at vælge mellem konkrete klasser eller anonyme indre typer, lad os prøve at oprette lambdas i en enkelt klasse:

offentlig grænseflade Discounter {BigDecimal applyDiscount (BigDecimal beløb); statisk rabat julDiscounter () {returbeløb -> beløb.multiply (BigDecimal.valueOf (0.9)); } statisk rabat newYearDiscounter () {returbeløb -> beløb.multiply (BigDecimal.valueOf (0.8)); } statisk rabat påskeDiscounter () {returbeløb -> beløb.multiply (BigDecimal.valueOf (0.5)); }} 

Som vi kan se, opnår vi meget i en ikke meget kode.

3.2. Gearing Function Composition

Lad os ændre vores Rabat interface, så det udvider UnaryOperator interface, og tilføj derefter en forene() metode:

offentlig grænseflade Discounter udvider UnaryOperator {standard Discounter combine (Discounter after) {return value -> after.apply (this.apply (value)); }}

I det væsentlige omlægger vi vores Rabat og udnytte en kendsgerning, at anvendelse af rabat er en funktion, der konverterer a BigDecimal eksempel i en anden BigDecimal eksempel, giver os adgang til foruddefinerede metoder. Som den UnaryOperator kommer med en ansøge() metode, kan vi bare erstatte ApplyDiscount med det.

Det forene() metoden er bare en abstraktion omkring anvendelse af en Rabat til resultaterne af det her. Det bruger den indbyggede funktionelle ansøge() for at opnå dette.

Lad os nu prøve at anvende flere Rabatter kumulativt til et beløb. Vi gør dette ved at bruge funktionaliteten reducere() og vores forene():

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

Vær særlig opmærksom på den første reducere argument. Når der ikke gives rabatter, skal vi returnere den uændrede værdi. Dette kan opnås ved at tilvejebringe en identitetsfunktion som standardafbryder.

Dette er et nyttigt og mindre detaljeret alternativ til at udføre en standard iteration. Hvis vi overvejer de metoder, vi kommer ud af kassen til funktionel sammensætning, giver det os også meget mere funktionalitet gratis.

4. Konklusion

I denne artikel har vi forklaret strategimønsteret og også demonstreret, hvordan vi kan bruge lambda-udtryk til at implementere det på en måde, der er mindre detaljeret.

Implementeringen af ​​disse eksempler findes på GitHub. Dette er et Maven-baseret projekt, så det skal være let at køre som det er.


$config[zx-auto] not found$config[zx-overlay] not found