Stratégiatervezés a Java 8-ban

1. Bemutatkozás

Ebben a cikkben megvizsgáljuk, hogyan tudjuk megvalósítani a stratégia tervezési mintáját a Java 8-ban.

Először áttekintést adunk a mintáról, és elmagyarázzuk, hogyan hajtották végre hagyományosan a Java régebbi verzióiban.

Ezután újra kipróbáljuk a mintát, csak ezúttal a Java 8 lambdas segítségével, ezzel csökkentve a kódunk sokszínűségét.

2. Stratégiai minta

Lényegében a stratégiai minta lehetővé teszi számunkra, hogy megváltoztassuk egy algoritmus futásidejű viselkedését.

Általában egy interfésszel kezdenénk, amelyet egy algoritmus alkalmazására használunk, majd minden lehetséges algoritmushoz többször megvalósítjuk.

Tegyük fel, hogy különböző típusú kedvezményeket kell alkalmaznunk a vásárlásokra, attól függően, hogy karácsonyról, húsvétról vagy újévről van szó. Először hozzunk létre egy Kedvezményes interfész, amelyet minden stratégiánk megvalósít:

nyilvános felület Discounter {BigDecimal ApplyDiscount (BigDecimal summa); } 

Akkor tegyük fel, hogy húsvétkor 50% -os, karácsonykor pedig 10% -os kedvezményt szeretnénk alkalmazni. Végezzük el az interfészünket az alábbi stratégiák mindegyikéhez:

public static class EasterDiscounter diszkontálást hajt végre {@Orride public BigDecimal applyDiscount (final BigDecimal amount) {return summa.multiply (BigDecimal.valueOf (0.5)); }} public static class ChristmasDiscounter discounter implementálja {@Orride public BigDecimal applyDiscount (végső BigDecimal összeg) {return summa.multiply (BigDecimal.valueOf (0,9)); }} 

Végül próbáljunk ki egy stratégiát egy tesztben:

Discounter easterDiscounter = új EasterDiscounter (); BigDecimal discountedValue = easterDiscounter .applyDiscount (BigDecimal.valueOf (100)); assertThat (diszkontáltérték) .isEqualByComparingTo (BigDecimal.valueOf (50));

Ez elég jól működik, de a probléma az, hogy egy kis fájdalmat okozhat, ha minden stratégiához konkrét osztályt kell létrehozni. Az alternatíva a névtelen belső típusok használata lenne, de ez még mindig nagyon bőbeszédű és nem sokkal praktikusabb, mint az előző megoldás:

Discounter easterDiscounter = new Discounter () {@Orride public BigDecimal ApplyDiscount (final BigDecimal amount) {return summa.multiply (BigDecimal.valueOf (0.5)); }}; 

3. A Java 8 kihasználása

A Java 8 kiadása óta a lambdas bevezetése többé-kevésbé feleslegessé tette az anonim belső típusokat. Ez azt jelenti, hogy a stratégiák összehangolása most sokkal tisztább és könnyebb.

Ezenkívül a funkcionális programozás deklaratív stílusa lehetővé teszi olyan minták megvalósítását, amelyek korábban nem voltak lehetségesek.

3.1. A kódbővítés csökkentése

Próbáljunk meg létrehozni egy inline-t Húsvéti vásárló, csak ezúttal lambda kifejezést használva:

Discounter easterDiscounter = mennyiség -> összeg.többszörözés (BigDecimal.valueOf (0.5)); 

Mint láthatjuk, a kódunk ma már sokkal tisztább és karbantarthatóbb, ugyanazt éri el, mint korábban, de egyetlen sorban. Lényegében, a lambda egy névtelen belső típus helyettesítésére tekinthető.

Ez az előny akkor válik nyilvánvalóbbá, ha még többet akarunk nyilatkozni Kedvezmények Sorban:

Lista diszkontok = newArrayList (összeg -> összeg.többszörözés (BigDecimal.valueOf (0.9)), összeg -> összeg.multtiply (BigDecimal.valueOf (0.8)), összeg -> összeg.multtiply (BigDecimal.valueOf (0.5))) ;

Amikor rengeteget akarunk definiálni Kedvezmények, statikusan deklarálhatjuk mindet egy helyen. A Java 8 még statikus módszereket is definiálhat az interfészekben, ha akarjuk.

Tehát ahelyett, hogy konkrét osztályok vagy anonim belső típusok között választanánk, próbáljuk meg lambdákat létrehozni, egyetlen osztályban:

nyilvános felület Discounter {BigDecimal ApplyDiscount (BigDecimal summa); statikus diszkontáló christmasDiscounter () {visszatérési összeg -> összeg.többszörözés (BigDecimal.valueOf (0,9)); } statikus diszkontáló newYearDiscounter () {visszatérési összeg -> összeg.többszörözés (BigDecimal.valueOf (0,8)); } static Discounter easterDiscounter () {visszatérési összeg -> összeg.többszörözés (BigDecimal.valueOf (0.5)); }} 

Mint láthatjuk, sokat érünk el egy nem túl sok kóddal.

3.2. Tőkeáttételes funkcióösszetétel

Módosítsuk a Kedvezményes interfész, így kiterjeszti a UnaryOperator interfészt, majd adjon hozzá egy a kombájn() módszer:

nyilvános felület A diszkontáló kiterjeszti az UnaryOperator {alapértelmezett diszkontáló kombájn (diszkontáló után) {return value -> after.apply (this.apply (value)); }}

Lényegében átalakítjuk a sajátjainkat Kedvezményes és kihasználni azt a tényt, hogy a kedvezmény alkalmazása olyan funkció, amely átalakítja a BigDecimal például egy másikba BigDecimal példa, lehetővé téve számunkra az előre meghatározott módszerek elérését. Mivel a UnaryOperator jön egy alkalmaz() módszerrel, csak kicserélhetjük alkalmazniDiszkont ezzel.

A kombájn() A módszer csak absztrakció az alkalmazás körül Kedvezményes eredményeire ez. A beépített funkciót használja alkalmaz() ennek elérése érdekében.

Most próbáljuk meg többször is alkalmazni Kedvezmények halmozottan egy összegig. Ezt a funkcionális használatával fogjuk megtenni csökkenteni () és a mi kombájn():

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

Különös figyelmet fordítson az elsőre csökkenteni érv. Ha nem biztosítunk kedvezményeket, vissza kell adnunk a változatlan értéket. Ezt úgy érhetjük el, hogy alapértelmezett diszkontként identitásfüggvényt adunk meg.

Ez egy hasznos és kevésbé bőbeszédű alternatíva a szokásos iteráció végrehajtásához. Ha figyelembe vesszük azokat a módszereket, amelyeket a funkcionális összetételhez a dobozból kiszedünk, az sokkal több funkcionalitást is ad nekünk ingyen.

4. Következtetés

Ebben a cikkben elmagyaráztuk a stratégiai mintát, és bemutattuk azt is, hogy miként használhatjuk a lambda kifejezéseket a kevésbé bőbeszédű megvalósításra.

Ezeknek a példáknak a megvalósítása megtalálható a GitHub oldalon. Ez egy Maven alapú projekt, ezért könnyen futtathatónak kell lennie.