Dupla feladás DDD-ben

1. Áttekintés

A kettős feladás egy szakkifejezés a a meghívandó módszer kiválasztásának folyamata mind a vevő, mind az argumentumtípusok alapján.

Sok fejlesztő gyakran összekeveri a kettős elküldést a Strategy Patternel.

A Java nem támogatja a kettős elküldést, de vannak olyan technikák, amelyeket alkalmazhatunk ennek a korlátnak a leküzdésére.

Ebben az oktatóanyagban a kettős elosztás példáinak bemutatására összpontosítunk a tartományvezérelt tervezés (DDD) és a stratégiai minta összefüggésében.

2. Dupla feladás

Mielőtt megvitatnánk a kettős elküldést, tekintsünk át néhány alapot, és magyarázzuk el, mi is az az Egyetlen feladás valójában.

2.1. Egyetlen feladás

Az egyszeri feladás lehetővé teszi a vevő futási idején alapuló módszer megvalósításának megválasztását. A Java-ban ez alapvetően ugyanaz, mint a polimorfizmus.

Vessünk egy pillantást például erre az egyszerű kedvezménypolitikai felületre:

nyilvános felület DiscountPolicy {kettős kedvezmény (Megrendelés); }

A DiscountPolicy interfész két megvalósítással rendelkezik. A lapos, amely mindig ugyanazt a kedvezményt adja vissza:

public class FlatDiscountPolicy implementálja a DiscountPolicy {@Orride public double discount (Order order) {return 0.01; }}

És a második megvalósítás, amely visszatérítést ad a megrendelés teljes költsége alapján:

public class AmountBasedDiscountPolicy implementálja a DiscountPolicy {@Orride public double discount (Order order) {if (order.totalCost () .isGreaterThan (Money.of (CurrencyUnit.USD, 500.00)))) {return 0,10; } else {return 0; }}}

Tegyük fel, hogy ennek a példának az igényei szerint a Rendelés osztálynak van egy összköltsége() módszer.

Az egyszeri szállítás a Java-ban csak egy nagyon jól ismert polimorf viselkedés, amelyet a következő teszt mutat be:

@DisplayName ("adott két diszkont házirend," + ", amikor ezeket a házirendeket használja," + ", majd az egyszeri feladás a futásidejű típus alapján választja a megvalósítást") ); DiscountPolicy amountPolicy = új AmountBasedDiscountPolicy (); Order orderWorth501Dollars = orderWorthNDollars (501); // amikor dupla flatDiscount = flatPolicy.discount (orderWorth501Dollars); dupla összegDiszkont = összegPolitika.diszkont (orderWorth501Dollars); // majd assertThat (flatDiscount) .isEqualTo (0,01); assertThat (összegDiszkont) .isEqualTo (0,1); }

Ha mindez elég egyértelműnek tűnik, maradj velünk. Később ugyanezt a példát fogjuk használni.

Most készen állunk a kettős feladás bevezetésére.

2.2. Dupla elküldés vs módszer túlterhelése

A kettős feladás meghatározza a futás közbeni meghívás módját mind a vevő, mind az argumentumtípusok alapján.

A Java nem támogatja a kettős elküldést.

Ne feledje, hogy a kettős küldést gyakran összekeverik a módszer túlterhelésével, ami nem ugyanaz. A módszer túlterhelése csak a fordítási idő információ alapján választja meg a meghívás módját, például a változó deklarációs típusát.

A következő példa részletesen elmagyarázza ezt a viselkedést.

Vezessünk be egy új diszkont felületet SpecialDiscountPolicy:

nyilvános felület A SpecialDiscountPolicy kiterjeszti a DiscountPolicy {kettős kedvezményt (SpecialOrder megrendelés); }

Különleges rendelés egyszerűen kiterjed Rendelés új viselkedés hozzáadása nélkül.

Most, amikor létrehozunk egy példányt Különleges rendelés de nyilvánítsa normálisnak Rendelés, akkor a speciális kedvezményes módszert nem alkalmazzák:

@DisplayName ("adott diszkont politika, amely speciális megrendeléseket fogad el," + ", amikor a rendes megrendelésként deklarált speciális megrendelésekre vonatkozó szabályt alkalmazza," + ", akkor a rendszeres diszkont módszert alkalmazzák") @Test void test () dobja a Kivételt {// adott SpecialDiscountPolicy specialPolicy = new SpecialDiscountPolicy () {@ Nyilvános kettős kedvezmény felülbírálása (Megrendelés) {return 0,01; } @Orride public double discount (SpecialOrder order) {return 0,10; }}; Rendelés specialOrder = új SpecialOrder (anyOrderLines ()); // amikor dupla kedvezmény = specialPolicy.discount (specialOrder); // majd assertThat (kedvezmény) .isEqualTo (0,01); }

Ezért a módszer túlterhelése nem kettős elosztás.

Még akkor is, ha a Java nem támogatja a kettős elküldést, használhatunk egy mintát hasonló viselkedés elérésére: Látogató.

2.3. Látogatóminta

A Látogató minta lehetővé teszi számunkra, hogy új viselkedést adjunk a meglévő osztályokhoz anélkül, hogy azokat módosítanánk. Ez a kettős elküldés okos technikájának köszönhető.

Hagyjuk egy pillanatra a kedvezményes példát, hogy bemutassuk a Visitor mintát.

Képzelje el, hogy HTML nézeteket szeretnénk készíteni különböző sablonok használatával az egyes sorrendekhez. Hozzáadhatnánk ezt a viselkedést közvetlenül a rendelési osztályokhoz, de ez nem a legjobb ötlet az SRP megsértése miatt.

Ehelyett a Visitor mintát fogjuk használni.

Először be kell mutatnunk a Látogatható felület:

nyilvános felület Látható {void accept (V látogató); }

Látogatói felületet is használunk, a mi nevünkben OrderVisitor:

nyilvános felület OrderVisitor {void visit (Rendelési sorrend); érvénytelen látogatás (SpecialOrder megrendelés); }

A Látogató minta egyik hátránya azonban az, hogy a látogatható osztályok megkövetelik a Látogató ismeretét.

Ha az osztályokat nem úgy tervezték, hogy támogassák a Látogatót, nehéz lehet (vagy akár lehetetlen is, ha a forráskód nem áll rendelkezésre) ennek a mintának az alkalmazása.

Minden rendeléstípusnak meg kell valósítania a Látogatható felületet, és biztosítja a saját megvalósítását, amely látszólag azonos, egy másik hátrány.

Vegye figyelembe, hogy a Rendelés és Különleges rendelés azonosak:

public class Order végrehajtja Visitable {@Override public void accept (OrderVisitor látogató) {visitor.visit (this); }} public class SpecialOrder kiterjeszti a megrendelést {@Override public void accept (OrderVisitor látogató) {visitor.visit (this); }}

Csábító lehet, hogy nem hajtja végre újra elfogad az alosztályban. Ha azonban nem tennénk, akkor a OrderVisitor.visit (Rendelés) A módszer a polimorfizmus miatt természetesen mindig megszokott.

Végül nézzük meg a OrderVisitor felelős a HTML nézetek létrehozásáért:

public class HtmlOrderViewCreator implementálja a OrderVisitor {private String html; public String getHtml () {return html; } @Orride public void visit (Order order) {html = String.format ("

Rendszeres megrendelés teljes költsége:% s

", order.totalCost ());} @ Nyilvános érvénytelen látogatás felülbírálása (SpecialOrder megrendelés) {html = String.format ("

összköltség

", order.totalCost ());}}

A következő példa bemutatja a HtmlOrderViewCreator:

@DisplayName ("adott rendes és különleges megrendelések gyűjteménye," + ", amikor HTML nézetet hoz létre minden egyes megrendeléshez a látogató segítségével," + ", majd az egyes rendelésekhez létrejön a dedikált nézet") adott Lista anyOrderLines = OrderFixtureUtils.anyOrderLines (); Lista megrendelések = Arrays.asList (új rendelés (anyOrderLines), új SpecialOrder (anyOrderLines)); HtmlOrderViewCreator htmlOrderViewCreator = new HtmlOrderViewCreator (); // amikor megrendelések.get (0) .accept (htmlOrderViewCreator); String regularOrderHtml = htmlOrderViewCreator.getHtml (); megrendelések.get (1) .accept (htmlOrderViewCreator); String specialOrderHtml = htmlOrderViewCreator.getHtml (); // majd assertThat (regularOrderHtml) .containsPattern ("

Rendszeres megrendelés teljes költsége:. *

"); assertThat (specialOrderHtml) .containsPattern ("

összköltsége: .*

"); }

3. Dupla feladás DDD-ben

Az előző szakaszokban a kettős feladásról és a Látogató mintáról tárgyaltunk.

Most végre készen állunk megmutatni, hogyan kell használni ezeket a technikákat a DDD-ben.

Térjünk vissza a megrendelésekre és a kedvezménypolitikákra.

3.1. A diszkont politika mint stratégiai minta

Korábban bemutattuk a Rendelés osztály és annak összköltsége() módszer, amely kiszámítja az összes rendelési sor összegét:

public class Megrendelés {public Money totalCost () {// ...}}

Ott van még a DiscountPolicy felület a megrendelés kedvezményének kiszámításához. Ezt a kezelőfelületet azért hozták létre, hogy lehetővé tegye a különféle kedvezménypolitikák használatát és futás közben történő megváltoztatását.

Ez a kialakítás sokkal rugalmasabb, mint egyszerűen az összes lehetséges kedvezményes politika kemény kódolása Rendelés osztályok:

nyilvános felület DiscountPolicy {kettős kedvezmény (Megrendelés); }

Ezt eddig nem említettük kifejezetten, de ez a példa a stratégiai mintát használja. A DDD gyakran használja ezt a mintát, hogy megfeleljen a mindenütt használt nyelv elvének és alacsony csatolást érjen el. A DDD világban a stratégiai mintát gyakran Policy-nek nevezik.

Lássuk, hogyan lehet kombinálni a kettős diszpécselési technikát és a kedvezménypolitikát.

3.2. Dupla elküldési és engedményezési politika

A házirend-minta megfelelő használatához gyakran érdemes ezt érvként átadni. Ez a megközelítés a Tell, Don't Ask elvét követi, amely támogatja a jobb beágyazást.

Például a Rendelés osztály megvalósíthatja összköltsége így:

public class Megrendelés / * ... * / {// ... public Money totalCost (SpecialDiscountPolicy discountPolicy) {return totalCost (). multipliedBy (1 - discountPolicy.discount (this), RoundingMode.HALF_UP); } // ...}

Tegyük fel, hogy az egyes rendeléstípusokat másképp szeretnénk feldolgozni.

Például a speciális megrendelések árengedményének kiszámításakor néhány más szabály megköveteli a (z) Különleges rendelés osztály. Kerülni akarjuk a leadást és a reflexiót, és képesek vagyunk mindegyikre kiszámolni a teljes költségeket Rendelés a helyesen alkalmazott kedvezménnyel.

Azt már tudjuk, hogy a módszer túlterhelése fordítási időben történik. Tehát felmerül a természetes kérdés: hogyan tudjuk dinamikusan diszponálni a rendelési engedmény logikáját a megfelelő módszerre a megbízás futásideje alapján?

A válasz? Kicsit módosítanunk kell a rendelési osztályokat.

A gyökér Rendelés osztálynak futás közben el kell küldenie a kedvezménypolitika érvet. Ennek legegyszerűbb módja egy védett hozzáadása ApplyDiscountPolicy módszer:

public class Megrendelés / * ... * / {// ... public Money totalCost (SpecialDiscountPolicy discountPolicy) {return totalCost (). multipliedBy (1 - ApplyDiscountPolicy (discountPolicy), RoundingMode.HALF_UP); } védett kettős ApplyDiscountPolicy (SpecialDiscountPolicy discountPolicy) {return discountPolicy.discount (this); } // ...}

Ennek a kialakításnak köszönhetően elkerüljük az üzleti logika duplikálását a összköltsége módszer ben Rendelés alosztályok.

Mutassunk egy használati bemutatót:

@DisplayName ("adott rendszeres megrendelés 100 USD értékű tételekkel," + ", ha 10% -os kedvezménypolitikát alkalmazunk," + ", majd a kedvezmény utáni költség 90 USD") @Test void test () kivételt dob Rendelés (OrderFixtureUtils.orderLineItemsWorthNDollars (100)); SpecialDiscountPolicy discountPolicy = new SpecialDiscountPolicy () {@Orride public double discount (Order order) {return 0,10; } @Orride public double discount (SpecialOrder order) {return 0; }}; // amikor Money totalCostAfterDiscount = order.totalCost (discountPolicy); // majd assertThat (totalCostAfterDiscount) .isEqualTo (Money.of (CurrencyUnit.USD, 90)); }

Ez a példa továbbra is a Visitor mintát használja, de kissé módosított változatban. A rendosztályok tisztában vannak ezzel SpecialDiscountPolicy (a Látogató) rendelkezik valamilyen jelentéssel és kiszámítja a kedvezményt.

Amint azt korábban említettük, szeretnénk tudni, hogy a futási idő típusától függően különböző kedvezményes szabályokat alkalmazzunk Rendelés. Ezért felül kell írnunk a védettet ApplyDiscountPolicy módszer minden gyermekosztályban.

Írjuk felül ezt a módszert Különleges rendelés osztály:

public class SpecialOrder meghosszabbítja a megrendelést {// ... @Orride védett kettős applyDiscountPolicy (SpecialDiscountPolicy discountPolicy) {return discountPolicy.discount (this); } // ...}

Most már felhasználhatunk további információkat a következőkről: Különleges rendelés a kedvezményes politikában a megfelelő kedvezmény kiszámításához:

@DisplayName ("adott különleges megrendelésre extra kedvezmény jár, összesen 100 dollár értékű termékekkel," + ", amikor 20% -os kedvezménypolitikát alkalmaz az extra kedvezményes megrendelésekre," + ", majd a kedvezmény utáni költség 80 dollár") @Test void teszt () kivételt dob {// megadott logikai érték jogosultForExtraDiscount = true; Megrendelés = new SpecialOrder (OrderFixtureUtils.orderLineItemsWorthNDollars (100), compatibleForExtraDiscount); SpecialDiscountPolicy discountPolicy = új SpecialDiscountPolicy () {@Orride public double discount (Order order) {return 0; } @Orride public double discount (SpecialOrder order) {if (order.isEligibleForExtraDiscount ()) return 0,20; visszatér 0,10; }}; // amikor Money totalCostAfterDiscount = order.totalCost (discountPolicy); // majd assertThat (totalCostAfterDiscount) .isEqualTo (Money.of (CurrencyUnit.USD, 80.00)); }

Ezenkívül, mivel a polimorf viselkedést alkalmazzuk a sorrendosztályokban, könnyen módosíthatjuk a teljes költség számítási módszert.

4. Következtetés

Ebben a cikkben megtanultuk a kettős diszpécsertechnika és Stratégia (más néven Irányelv) minta Domain-alapú tervezésben.

Az összes példa teljes forráskódja elérhető a GitHub oldalon.