Strategidesignmønster i Java 8

1. Introduksjon

I denne artikkelen vil vi se på hvordan vi kan implementere strategidesignmønsteret i Java 8.

Først vil vi gi en oversikt over mønsteret, og forklare hvordan det tradisjonelt er implementert i eldre versjoner av Java.

Deretter prøver vi mønsteret igjen, bare denne gangen med Java 8 lambdas, noe som reduserer koden vår.

2. Strategimønster

I hovedsak tillater strategimønsteret oss å endre atferden til en algoritme ved kjøretid.

Vanligvis vil vi starte med et grensesnitt som brukes til å bruke en algoritme, og deretter implementere den flere ganger for hver mulig algoritme.

La oss si at vi har et krav om å bruke forskjellige typer rabatter på et kjøp, basert på om det er jul, påske eller nyttår. La oss først lage en Rabatt grensesnitt som vil bli implementert av hver av våre strategier:

offentlig grensesnitt Discounter {BigDecimal applyDiscount (BigDecimal beløp); } 

La oss si at vi vil bruke 50% rabatt i påsken og 10% rabatt i julen. La oss implementere grensesnittet vårt for hver av disse strategiene:

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)); }} 

Til slutt, la oss prøve en strategi i en test:

Rabatt påskeDiscounter = ny EasterDiscounter (); BigDecimal discountedValue = easterDiscounter .applyDiscount (BigDecimal.valueOf (100)); assertThat (discountedValue) .isEqualByComparingTo (BigDecimal.valueOf (50));

Dette fungerer ganske bra, men problemet er at det kan være litt vondt å måtte lage en konkret klasse for hver strategi. Alternativet ville være å bruke anonyme indre typer, men det er fortsatt ganske ordentlig og ikke mye mer praktisk enn den forrige løsningen:

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

3. Utnytte Java 8

Siden Java 8 er utgitt, har introduksjonen av lambdas gjort anonyme indre typer mer eller mindre overflødige. Det betyr at det å lage strategier på linje er mye renere og enklere.

Videre lar den deklarative stilen med funksjonell programmering oss implementere mønstre som ikke var mulig før.

3.1. Redusere kodeutvikling

La oss prøve å lage en innebygd PåskeDiscounter, bare denne gangen ved hjelp av et lambdauttrykk:

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

Som vi kan se, er koden vår nå mye renere og mer vedlikeholdsrik, og oppnår det samme som før, men i en enkelt linje. I hovedsak, en lambda kan sees på som en erstatning for en anonym indre type.

Denne fordelen blir tydeligere når vi vil erklære enda mer Rabatter på linje:

Liste over rabatter = newArrayList (beløp -> beløp.multiply (BigDecimal.valueOf (0.9)), beløp -> beløp.multiply (BigDecimal.valueOf (0.8)), beløp -> beløp.multiply (BigDecimal.valueOf (0.5))) ;

Når vi vil definere mange Rabatter, vi kan erklære dem statisk alt på ett sted. Java 8 lar oss til og med definere statiske metoder i grensesnitt hvis vi vil.

Så i stedet for å velge mellom konkrete klasser eller anonyme indre typer, la oss prøve å lage lambdas alt i en enkelt klasse:

offentlig grensesnitt Discounter {BigDecimal applyDiscount (BigDecimal beløp); statisk rabatt julDiscounter () {returbeløp -> beløp.multiply (BigDecimal.valueOf (0.9)); } statisk rabatt newYearDiscounter () {returbeløp -> beløp.multiply (BigDecimal.valueOf (0.8)); } statisk rabatt påskeDiscounter () {returbeløp -> beløp.multiply (BigDecimal.valueOf (0.5)); }} 

Som vi kan se, oppnår vi mye i en ikke veldig mye kode.

3.2. Leveraging Funksjonssammensetning

La oss endre vår Rabatt grensesnittet slik at det utvider UnaryOperator grensesnitt, og legg deretter til et kombinere() metode:

offentlig grensesnitt Discounter utvider UnaryOperator {standard Discounter combine (Discounter after) {returverdi -> after.apply (this.apply (verdi)); }}

I det vesentlige refaktoriserer vi vårt Rabatt og utnytte et faktum at bruk av rabatt er en funksjon som konverterer a BigDecimal forekomst i en annen BigDecimal forekomst, slik at vi får tilgang til forhåndsdefinerte metoder. Som den UnaryOperator kommer med en søke om() metode, kan vi bare erstatte ApplyDiscount med det.

De kombinere() metoden er bare en abstraksjon rundt å bruke en Rabatt til resultatene av dette. Den bruker den innebygde funksjonelle søke om() for å oppnå dette.

La oss prøve å bruke flere Rabatter kumulativt til et beløp. Vi vil gjøre dette ved å bruke funksjonaliteten redusere() og vår kombinere():

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

Vær spesielt oppmerksom på den første redusere argument. Når ingen rabatter er gitt, må vi returnere den uendrede verdien. Dette kan oppnås ved å tilby en identitetsfunksjon som standardfrakobler.

Dette er et nyttig og mindre detaljert alternativ til å utføre en standard iterasjon. Hvis vi vurderer metodene vi får ut av boksen for funksjonell komposisjon, gir det oss også mye mer funksjonalitet gratis.

4. Konklusjon

I denne artikkelen har vi forklart strategimønsteret, og også demonstrert hvordan vi kan bruke lambdauttrykk for å implementere det på en måte som er mindre ordrik.

Implementeringen av disse eksemplene finner du på GitHub. Dette er et Maven-basert prosjekt, så det skal være enkelt å kjøre som det er.


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