Double Dispatch in DDD

1. Prehľad

Dvojité odoslanie je technický pojem na popísanie proces výberu metódy na vyvolanie na základe typov prijímačov aj argumentov.

Mnoho vývojárov si často zamieňa dvojité odoslanie so Strategy Pattern.

Java nepodporuje dvojité odoslanie, ale na prekonanie tohto obmedzenia existujú techniky, ktoré môžeme použiť.

V tomto tutoriáli sa zameriame na ukážku príkladov dvojitého odoslania v kontexte Domain-driven Design (DDD) a Strategy Pattern.

2. Dvojitý odoslanie

Predtým, ako diskutujeme o dvojitom odoslaní, poďme si prečítať niektoré základné informácie a vysvetliť, čo to vlastne Single Dispatch je.

2.1. Single Expedícia

Jediné odoslanie je spôsob, ako zvoliť implementáciu metódy založenej na type runtime prijímača. V Jave je to v podstate to isté ako polymorfizmus.

Pozrime sa napríklad na toto jednoduché rozhranie pre politiku zľavy:

verejné rozhranie DiscountPolicy {dvojnásobná zľava (objednávka objednávky); }

The ZľavaPolitika rozhranie má dve implementácie. Plochý, ktorý vracia vždy rovnakú zľavu:

verejná trieda FlatDiscountPolicy implementuje DiscountPolicy {@Override verejná dvojitá zľava (objednávka objednávky) {návrat 0,01; }}

A druhá implementácia, ktorá vracia zľavu na základe celkových nákladov na objednávku:

verejná trieda AmountBasedDiscountPolicy implementuje DiscountPolicy {@Override verejná dvojitá zľava (objednávka objednávky) {if (order.totalCost () .isGreaterThan (Money.of (CurrencyUnit.USD, 500,00))) {návrat 0,10; } else {návrat 0; }}}

Pre potreby tohto príkladu predpokladajme objednať trieda má a Celkové náklady() metóda.

Jediné odoslanie v Jave je teraz len veľmi známe polymorfné správanie demonštrované v nasledujúcom teste:

@DisplayName („dané dve pravidlá pre zľavy,„ + “pri použití týchto politík,„ + “a potom jedna expedícia vyberie implementáciu na základe typu modulu runtime)) @Test void test () vyvolá výnimku {// daný DiscountPolicy flatPolicy = nový FlatDiscountPolicy ( ); DiscountPolicy amountPolicy = nová AmountBasedDiscountPolicy (); Order orderWorth501Dollars = orderWorthNDollars (501); // keď je double flatDiscount = flatPolicy.discount (orderWorth501Dollars); dvojnásobná sumaZľava = sumaPolitika.diskont (orderWorth501Dolárov); // potom assertThat (flatDiscount) .isEqualTo (0,01); assertThat (amountDiscount) .isEqualTo (0,1); }

Ak sa to zdá všetko celkom jednoznačné, zostaňte naladení. Rovnaký príklad použijeme neskôr.

Teraz sme pripravení predstaviť dvojité odoslanie.

2.2. Double Dispatch vs Method Overloading

Dvojité odoslanie určuje metódu, ktorá sa má vyvolať za behu, na základe typu prijímača aj typov argumentov.

Java nepodporuje dvojité odoslanie.

Upozorňujeme, že dvojité odoslanie je často zamieňané s preťažovaním metód, čo nie je to isté. Metóda preťaženia vyberie metódu, ktorá sa má vyvolať, iba na základe informácií pri kompilácii, ako je napríklad deklaračný typ premennej.

Nasledujúci príklad podrobne vysvetľuje toto správanie.

Poďme si predstaviť nové zľavové rozhranie s názvom Špeciálna zľavová politika:

verejné rozhranie SpecialDiscountPolicy rozširuje DiscountPolicy {dvojnásobná zľava (objednávka SpecialOrder); }

Špeciálna objednávka jednoducho sa rozširuje objednať bez pridania nového správania.

Teraz, keď vytvoríme inštanciu Špeciálna objednávka ale vyhlas to za normalne objednať, potom sa nepoužíva špeciálna metóda zľavy:

@DisplayName ("dané pravidlá zľavy prijímajúce špeciálne objednávky," + "keď uplatňujú pravidlá pre špeciálne objednávky deklarované ako bežné objednávky," + "potom sa používa metóda bežnej zľavy") @Test neplatný test () vyvolá výnimku {// daný SpecialDiscountPolicy specialPolicy = nová SpecialDiscountPolicy () {@Override verejná dvojitá zľava (objednávka objednávky) {návrat 0,01; } @Override verejná dvojitá zľava (objednávka SpecialOrder) {návrat 0,10; }}; Objednať specialOrder = nový SpecialOrder (anyOrderLines ()); // keď dvojnásobná zľava = specialPolicy.discount (specialOrder); // potom assertThat (zľava) .isEqualTo (0,01); }

Preto preťaženie metódy nie je dvojité odoslanie.

Aj keď Java nepodporuje dvojité odoslanie, na dosiahnutie podobného správania môžeme použiť vzor: Návštevník.

2.3. Vzor návštevníka

Vzor návštevníka nám umožňuje pridať nové správanie k existujúcim triedam bez ich úpravy. To je možné vďaka šikovnej technike emulácie dvojitého odoslania.

Nechajme na chvíľu príklad zľavy, aby sme mohli predstaviť vzor Návštevník.

Predstavte si, že by sme chceli vytvoriť zobrazenia HTML pomocou rôznych šablón pre každý druh objednávky. Toto správanie by sme mohli pridať priamo do tried objednávok, ale nie je to najlepší nápad z dôvodu porušenia SRP.

Namiesto toho použijeme vzor Návštevník.

Najprv musíme predstaviť Viditeľné rozhranie:

verejné rozhranie Visitable {void accept (V visitor); }

Použijeme tiež rozhranie pre návštevníkov v našom krytom názve OrderVisitor:

verejné rozhranie OrderVisitor {void visit (objednávka objednávky); neplatná návšteva (objednávka SpecialOrder); }

Jednou z nevýhod vzoru Návštevník je však to, že vyžaduje, aby si triedy pre návštevníkov boli vedomí.

Ak triedy neboli navrhnuté tak, aby podporovali návštevníka, mohlo by byť ťažké (alebo dokonca nemožné, ak nie je k dispozícii zdrojový kód) použiť tento vzor.

Každý typ objednávky musí byť implementovaný Viditeľné rozhranie a poskytnúť vlastnú implementáciu, ktorá je zdanlivo identická, ďalšou nevýhodou.

Všimnite si, že do metódy pridané objednať a Špeciálna objednávka sú identické:

public class Order implementuje Visitable {@Override public void accept (OrderVisitor visitor) {visitor.visit (this); }} public class SpecialOrder rozširuje Order {@Override public void accept (OrderVisitor visitor) {visitor.visit (this); }}

Mohlo by to byť lákavé znova implementovať súhlasiť v podtriede. Ak sme to však neurobili, potom OrderVisitor.visit (objednávka) metóda by sa vždy zvykla, samozrejme, kvôli polymorfizmu.

Na záver sa pozrime na implementáciu OrderVisitor zodpovedný za vytváranie zobrazení HTML:

verejná trieda HtmlOrderViewCreator implementuje OrderVisitor {private String html; public String getHtml () {return html; } @Override verejná neplatná návšteva (objednávka objednávky) {html = String.format ("

Celkové náklady na bežnú objednávku:% s

", order.totalCost ());} @Override verejná neplatná návšteva (objednávka SpecialOrder) {html = String.format ("

celkové náklady:% s

", order.totalCost ());}}

Nasledujúci príklad demonštruje použitie HtmlOrderViewCreator:

@DisplayName ("daná zbierka bežných a špeciálnych objednávok," + "pri vytváraní zobrazenia HTML pomocou návštevníka pre každú objednávku," + "potom sa vytvorí vyhradené zobrazenie pre každú objednávku") @Test void test () vyvolá výnimku {// daný zoznam anyOrderLines = OrderFixtureUtils.anyOrderLines (); Zoznam objednávok = Arrays.asList (nová objednávka (anyOrderLines), nová špeciálna objednávka (anyOrderLines)); HtmlOrderViewCreator htmlOrderViewCreator = nový HtmlOrderViewCreator (); // when commands.get (0) .accept (htmlOrderViewCreator); Reťazec regularOrderHtml = htmlOrderViewCreator.getHtml (); commands.get (1) .accept (htmlOrderViewCreator); Reťazec specialOrderHtml = htmlOrderViewCreator.getHtml (); // potom assertThat (regularOrderHtml) .containsPattern ("

Celkové náklady na bežnú objednávku:. *

"); assertThat (specialOrderHtml) .containsPattern ("

Celkové náklady: .*

"); }

3. Double Dispatch in DDD

V predchádzajúcich častiach sme sa zaoberali dvojitým odoslaním a vzorom návštevníkov.

Teraz sme konečne pripravení ukázať, ako používať tieto techniky v DDD.

Vráťme sa k príkladu objednávok a zliav.

3.1. Zľavová politika ako vzor stratégie

Predtým sme predstavili objednať trieda a jej Celkové náklady() metóda, ktorá počíta súčet všetkých riadkových položiek objednávky:

verejná objednávka triedy {public Money totalCost () {// ...}}

K dispozícii je tiež ZľavaPolitika rozhranie pre výpočet zľavy za objednávku. Toto rozhranie bolo zavedené, aby umožňovalo používanie rôznych politík zliav a ich zmenu za behu programu.

Tento dizajn je oveľa pružnejší ako jednoduché naprogramovanie všetkých možných politík zľavy v objednať triedy:

verejné rozhranie DiscountPolicy {dvojnásobná zľava (objednávka objednávky); }

Toto sme doteraz výslovne neuviedli, ale tento príklad využíva model Strategy. DDD často používa tento vzor, ​​aby vyhovoval princípu všadeprítomného jazyka a dosiahol nízku väzbu. Vo svete DDD sa vzor stratégie často nazýva politika.

Pozrime sa, ako skombinovať techniku ​​dvojitého odoslania a zľavovú politiku.

3.2. Zásady dvojitého odoslania a zľavy

Ak chcete vzor politiky správne použiť, je často dobré ho uviesť ako argument. Tento prístup sa riadi zásadou Povedz, nepýtaj sa, ktorá podporuje lepšie zapuzdrenie.

Napríklad objednať trieda môže implementovať Celkové náklady ako:

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

Teraz predpokladajme, že by sme chceli spracovať každý typ objednávky inak.

Napríklad pri výpočte zľavy pre špeciálne objednávky existujú niektoré ďalšie pravidlá vyžadujúce informácie jedinečné pre Špeciálna objednávka trieda. Chceme sa vyhnúť castingu a reflexii a zároveň byť schopní vypočítať celkové náklady pre každého objednať so správne uplatnenou zľavou.

Už vieme, že preťaženie metódy sa deje v čase kompilácie. Nastáva teda prirodzená otázka: ako môžeme dynamicky odoslať logiku zľavy objednávky na správnu metódu založenú na runtime type objednávky?

Odpoveď? Triedy objednávok musíme mierne upraviť.

Koreň objednať trieda musí byť odoslaná do argumentu politiky zľavy za behu. Najjednoduchší spôsob, ako to dosiahnuť, je pridať chránený applyDiscountPolicy metóda:

public class Order / * ... * / {// ... public Money totalCost (SpecialDiscountPolicy discountPolicy) {return totalCost (). multipliedBy (1 - applyDiscountPolicy (discountPolicy), RoundingMode.HALF_UP); } chránené dvojité applyDiscountPolicy (SpecialDiscountPolicy discountPolicy) {vrátiť zľavuPolicy.discount (toto); } // ...}

Vďaka tomuto dizajnu sa vyhneme duplikovaniu obchodnej logiky v Celkové náklady metóda v objednať podtriedy.

Ukážme si ukážku použitia:

@DisplayName („zadaná bežná objednávka s položkami v celkovej hodnote 100 USD,„ + “pri uplatnení 10% zľavovej politiky,„ + “potom cena po zľave 90 USD)) @ Test neplatného testu () vyvolá výnimku {// zadanú objednávku = nové Objednávka (OrderFixtureUtils.orderLineItemsWorthNDollars (100)); SpecialDiscountPolicy discountPolicy = nový SpecialDiscountPolicy () {@Override verejná dvojitá zľava (objednávka objednávky) {návrat 0,10; } @Override verejná dvojitá zľava (objednávka SpecialOrder) {návrat 0; }}; // kedy peniaze totalCostAfterDiscount = order.totalCost (discountPolicy); // potom assertThat (totalCostAfterDiscount) .isEqualTo (Money.of (CurrencyUnit.USD, 90)); }

V tomto príklade sa stále používa vzor Návštevník, ale v mierne upravenej verzii. Objednávkové triedy si to uvedomujú Špeciálna zľavová politika (Návštevník) má nejaký význam a počíta zľavu.

Ako už bolo spomenuté vyššie, chceme mať možnosť uplatňovať rôzne pravidlá zliav na základe typu runtime systému objednať. Preto musíme prepísať chránené applyDiscountPolicy metóda v každej triede dieťaťa.

Prepíšme túto metódu v Špeciálna objednávka trieda:

public class SpecialOrder extends Order {// ... @Override protected double applyDiscountPolicy (SpecialDiscountPolicy discountPolicy) {return discountPolicy.discount (this); } // ...}

Teraz môžeme použiť ďalšie informácie o Špeciálna objednávka v zľavovej politike vypočítať správnu zľavu:

@DisplayName („daná špeciálna objednávka oprávnená na ďalšiu zľavu s položkami v celkovej hodnote 100 USD,„ + “pri uplatnení 20% zľavovej politiky na ďalšie objednávky so zľavou,„ + “potom cena po zľave predstavuje 80 USD)) @ Test neplatnosti testu () vyvolá výnimku {// dané booleovské vhodné ForExtraDiscount = true; Objednávka objednávky = nový SpecialOrder (OrderFixtureUtils.orderLineItemsWorthNDollars (100), authorizedForExtraDiscount); SpecialDiscountPolicy discountPolicy = nový SpecialDiscountPolicy () {@Override verejná dvojitá zľava (objednávka objednávky) {návrat 0; } @Override verejná dvojitá zľava (objednávka SpecialOrder) {if (order.isEligibleForExtraDiscount ()) návratnosť 0,20; spiatočná 0,10; }}; // kedy peniaze totalCostAfterDiscount = order.totalCost (discountPolicy); // potom assertThat (totalCostAfterDiscount) .isEqualTo (Money.of (CurrencyUnit.USD, 80,00)); }

Ďalej, keďže v triedach objednávok používame polymorfné správanie, môžeme ľahko upraviť metódu výpočtu celkových nákladov.

4. Záver

V tomto článku sme sa naučili, ako používať techniku ​​dvojitého odoslania a Stratégia (alias Politika) vzor v dizajne riadenom doménou.

Celý zdrojový kód všetkých príkladov je k dispozícii na GitHub.


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