Dvostruka otprema u DDD-u

1. Pregled

Dvostruka otprema je tehnički izraz koji opisuje postupak odabira metode za pozivanje na temelju i tipa prijamnika i argumenta.

Mnogi programeri često brkaju dvostruku otpremu sa Strateškim uzorkom.

Java ne podržava dvostruko slanje, ali postoje tehnike koje možemo upotrijebiti da bismo prevladali ovo ograničenje.

U ovom uputstvu usredotočit ćemo se na prikazivanje primjera dvostrukog slanja u kontekstu dizajna vođenog domenom (DDD) i uzora strategije.

2. Dvostruka otprema

Prije nego što razgovaramo o dvostrukoj otpremi, pregledajmo neke osnove i objasnimo što je zapravo pojedinačna otprema.

2.1. Jedna otprema

Pojedinačno slanje način je odabira provedbe metode koja se temelji na vrsti vremena primanja prijemnika. U Javi je to u osnovi ista stvar kao i polimorfizam.

Na primjer, pogledajmo ovo jednostavno sučelje politike popusta:

javno sučelje DiscountPolicy {dvostruki popust (narudžba); }

The PopustPolicijski sučelje ima dvije izvedbe. Onaj ravni, koji uvijek vraća isti popust:

javna klasa FlatDiscountPolicy implementira DiscountPolicy {@Preuzmi javni dvostruki popust (narudžba narudžbe) {return 0,01; }}

I druga implementacija koja vraća popust na temelju ukupnih troškova narudžbe:

javna klasa AmountBasedDiscountPolicy implementira DiscountPolicy {@Preuzmi javni dvostruki popust (nalog za narudžbu) {if (order.totalCost () .isGreaterThan (Money.of (CurrencyUnit.USD, 500.00))) {return 0.10; } else {povratak 0; }}}

Za potrebe ovog primjera, pretpostavimo Narudžba razred ima a Ukupni trošak() metoda.

Sada je pojedinačno slanje u Javi samo vrlo dobro poznato polimorfno ponašanje koje se pokazalo u sljedećem testu:

@DisplayName ("s obzirom na dvije politike popusta," + "kada se koriste ova pravila," + "zatim pojedinačno slanje odabire implementaciju na temelju vrste vremena izvođenja") @ Test void test () baca izuzetak {// zadaje DiscountPolicy flatPolicy = novo FlatDiscountPolicy ( ); DiscountPolicy iznosPolicy = novi iznosBasedDiscountPolicy (); Naručite orderWorth501Dollars = orderWorthNDollars (501); // kada je dvostruki flatDiscount = flatPolicy.discount (orderWorth501Dollars); dvostruki iznosDiscount = iznosPolicy.discount (orderWorth501Dollars); // zatim assertThat (flatDiscount) .isEqualTo (0,01); assertThat (amountDiscount) .isEqualTo (0,1); }

Ako se ovo sve čini prilično izravno, pratite nas. Isti ćemo primjer upotrijebiti kasnije.

Sada smo spremni predstaviti dvostruku otpremu.

2.2. Dvostruka otprema u odnosu na preopterećenje metode

Dvostruko slanje određuje metodu koju će se prizivati ​​tijekom izvođenja na temelju vrste prijemnika i vrste argumenata.

Java ne podržava dvostruko slanje.

Imajte na umu da se dvostruka otprema često miješa s preopterećenjem metoda, što nije ista stvar. Preopterećenje metode odabire metodu koju će pozvati samo na temelju podataka o vremenu kompajliranja, poput vrste deklaracije varijable.

Sljedeći primjer detaljno objašnjava ovo ponašanje.

Uvedimo novo sučelje s popustom pod nazivom SpecialDiscountPolicy:

javno sučelje SpecialDiscountPolicy proširuje DiscountPolicy {dvostruki popust (narudžba SpecialOrder); }

Posebna narudžba jednostavno se proteže Narudžba bez dodavanja novog ponašanja.

Sada, kada kreiramo instancu Posebna narudžba ali to proglasite normalnim Narudžba, tada se ne koristi metoda posebnog popusta:

@DisplayName ("zadana politika popusta prihvaća posebne narudžbe," + "kada se primjenjuje politika posebne narudžbe koja je proglašena redovnom narudžbom," + "tada se koristi redovna metoda popusta") @ Test void test () baca izuzetak {// daje SpecialDiscountPolicy specialPolicy = novi SpecialDiscountPolicy () {@Preuzmi javni dvostruki popust (narudžba narudžbe) {return 0,01; } @Override javni dvostruki popust (narudžba po posebnoj narudžbi) {return 0.10; }}; Naruči specialOrder = novi SpecialOrder (anyOrderLines ()); // kada je dvostruki popust = specialPolicy.discount (specialOrder); // zatim assertThat (popust) .isEqualTo (0,01); }

Stoga preopterećenje metode nije dvostruko slanje.

Čak i ako Java ne podržava dvostruku otpremu, možemo koristiti obrazac za postizanje sličnog ponašanja: Visitor.

2.3. Uzorak posjetitelja

Uzorak Visitor omogućuje nam dodavanje novog ponašanja postojećim klasama bez njihovog mijenjanja. To je moguće zahvaljujući pametnoj tehnici oponašanja dvostruke otpreme.

Ostavimo na trenutak primjer popusta kako bismo mogli predstaviti obrazac posjetitelja.

Zamislite da bismo željeli izraditi HTML preglede koristeći različite predloške za svaku vrstu narudžbe. To bismo ponašanje mogli dodati izravno u klase narudžbi, ali to nije najbolja ideja zbog kršenja SRP-a.

Umjesto toga, upotrijebit ćemo obrazac Visitor.

Prvo, moramo predstaviti Dostupno sučelje:

javno sučelje Dostupno {void accept (V posjetitelj); }

Također ćemo koristiti sučelje za posjetitelje u našem nazvanom slučaju OrderVisitor:

javno sučelje OrderVisitor {poništena posjeta (narudžba narudžbe); poništeni posjet (narudžba SpecialOrder); }

Međutim, jedan od nedostataka uzorka posjetitelja je taj što zahtijeva vidljive klase kako bi bili svjesni posjetitelja.

Ako klase nisu dizajnirane da podržavaju posjetitelja, možda bi bilo teško (ili čak nemoguće ako izvorni kod nije dostupan) primijeniti ovaj obrazac.

Svaka vrsta narudžbe treba implementirati Dostupno sučelje i osigurati vlastitu implementaciju koja je naizgled identična, još jedan nedostatak.

Primijetite da su dodane metode za Narudžba i Posebna narudžba su identični:

javna klasa Nalog primjenjuje Visible {@Override public void accept (OrderVisitor visitor) {visitor.visit (this); }} public class SpecialOrder produžuje Narudžbu {@Override public void accept (posjetitelj OrderVisitor) {visitor.visit (this); }}

Možda bi bilo primamljivo ne ponoviti provedbu prihvatiti u podrazredu. Međutim, ako nismo, onda OrderVisitor.visit (Narudžba) metoda bi se uvijek koristila, naravno, zbog polimorfizma.

Napokon, pogledajmo provedbu OrderVisitor odgovoran za stvaranje HTML prikaza:

javna klasa HtmlOrderViewCreator implementira OrderVisitor {private String html; javni String getHtml () {return html; } @Override public void visit (Narudžba narudžbe) {html = String.format ("

Ukupni trošak redovne narudžbe:% s

", order.totalCost ());} @Preuzmi javni void posjet (narudžba SpecialOrder) {html = String.format ("

ukupni trošak:% s

", order.totalCost ());}}

Sljedeći primjer pokazuje upotrebu HtmlOrderViewCreator:

@DisplayName ("zadana kolekcija redovnih i posebnih narudžbi," + "kada se izrađuje HTML prikaz pomoću posjetitelja za svaku narudžbu," + ", a zatim se za svaku narudžbu stvara namjenski prikaz") @ Test void test () baca iznimku {// zadani Popis anyOrderLines = OrderFixtureUtils.anyOrderLines (); Lista narudžbi = Arrays.asList (nova narudžba (anyOrderLines), nova SpecialOrder (anyOrderLines)); HtmlOrderViewCreator htmlOrderViewCreator = novi HtmlOrderViewCreator (); // kada naloga.get (0) .accept (htmlOrderViewCreator); Niz regularOrderHtml = htmlOrderViewCreator.getHtml (); orders.get (1) .accept (htmlOrderViewCreator); String specialOrderHtml = htmlOrderViewCreator.getHtml (); // zatim assertThat (regularOrderHtml) .containsPattern ("

Ukupni trošak redovne narudžbe:. *

"); assertThat (specialOrderHtml) .containsPattern ("

Ukupni trošak: .*

"); }

3. Dvostruka otprema u DDD-u

U prethodnim smo odjeljcima razgovarali o dvostrukoj otpremi i uzorku posjetitelja.

Sad smo napokon spremni pokazati kako koristiti ove tehnike u DDD-u.

Vratimo se primjeru narudžbi i politikama popusta.

3.1. Politika popusta kao obrazac strategije

Ranije smo predstavili Narudžba razreda i svoje Ukupni trošak() metoda koja izračunava zbroj svih stavki reda narudžbe:

nalog javne klase {javni novac totalCost () {// ...}}

Tu je i PopustPolicijski sučelje za izračun popusta za narudžbu. Ovo sučelje uvedeno je kako bi se omogućilo korištenje različitih pravila o popustima i njihovo mijenjanje tijekom izvođenja.

Ovaj je dizajn mnogo gipkiji od jednostavnog kodiranja svih mogućih politika popusta u Narudžba klase:

javno sučelje DiscountPolicy {dvostruki popust (narudžba); }

Do sada to nismo izričito spomenuli, ali ovaj primjer koristi obrazac Strategije. DDD često koristi ovaj obrazac kako bi se prilagodio načelu sveprisutnog jezika i postigao nisku povezanost. U DDD svijetu obrazac Strategije često se naziva Politika.

Pogledajmo kako kombinirati tehniku ​​dvostruke otpreme i politiku popusta.

3.2. Pravila dvostruke otpreme i popusta

Da biste pravilno koristili obrazac politike, često je dobro to proslijediti kao argument. Ovaj pristup slijedi princip Kaži, ne pitaj koji podržava bolju inkapsulaciju.

Na primjer, Narudžba klasa može provesti Ukupni trošak ovako:

narudžba javne klase / * ... * / {// ... javni novac totalCost (SpecialDiscountPolicy discountPolicy) {return totalCost (). multiptedBy (1 - discountPolicy.discount (this), RoundingMode.HALF_UP); } // ...}

Pretpostavimo sada da bismo željeli obrađivati ​​svaku vrstu naloga drugačije.

Na primjer, prilikom izračuna popusta za posebne narudžbe postoje neka druga pravila koja zahtijevaju podatke jedinstvene za Posebna narudžba razred. Želimo izbjeći lijevanje i razmišljanje, a istodobno ćemo moći izračunati ukupne troškove za svakoga Narudžba uz pravilno primijenjeni popust.

Već znamo da se preopterećenje metode događa u vrijeme prevođenja. Dakle, postavlja se prirodno pitanje: kako možemo dinamički slati logiku popusta za narudžbu na pravu metodu na temelju vremena izvršavanja naloga?

Odgovor? Moramo malo izmijeniti klase narudžbi.

Korijen Narudžba klasa se mora poslati na argument politike popusta za vrijeme izvođenja. To ćete najlakše postići dodavanjem zaštićenog applyDiscountPolicy metoda:

narudžba javne klase / * ... * / {// ... javni novac totalCost (SpecialDiscountPolicy discountPolicy) {return totalCost (). multiptedBy (1 - applyDiscountPolicy (discountPolicy), RoundingMode.HALF_UP); } zaštićeni dvostruki applyDiscountPolicy (SpecialDiscountPolicy discountPolicy) {return discountPolicy.discount (this); } // ...}

Zahvaljujući ovom dizajnu, izbjegavamo dupliciranje poslovne logike u Ukupni trošak metoda u Narudžba potklase.

Pokažimo demonstraciju upotrebe:

@DisplayName ("zadana redovita narudžba s artiklima ukupne vrijednosti 100 USD," + "kada se primjenjuje politika popusta od 10%," + "tada je cijena nakon popusta 90 USD") @Test void test () baca izuzetak {// zadata narudžba = novo Narudžba (OrderFixtureUtils.orderLineItemsWorthNDollars (100)); SpecialDiscountPolicy discountPolicy = novi SpecialDiscountPolicy () {@Preuzmi javni dvostruki popust (narudžba narudžbe) {return 0,10; } @Override javni dvostruki popust (narudžba po posebnoj narudžbi) {return 0; }}; // kada je novac totalCostAfterDiscount = order.totalCost (discountPolicy); // zatim assertThat (totalCostAfterDiscount) .isEqualTo (Money.of (CurrencyUnit.USD, 90)); }

Ovaj primjer još uvijek koristi obrazac Visitor, ali u malo izmijenjenoj verziji. Klase narudžbe svjesne su toga SpecialDiscountPolicy (Posjetitelj) ima neko značenje i izračunava popust.

Kao što je prethodno spomenuto, želimo biti u mogućnosti primijeniti različita pravila o popustima na temelju vrste izvršavanja Narudžba. Stoga moramo nadjačati zaštićeno applyDiscountPolicy metoda u svakom razredu djeteta.

Zamijenimo ovu metodu u Posebna narudžba razred:

javna klasa SpecialOrder produžuje narudžbu {// ... @Override protected double applyDiscountPolicy (SpecialDiscountPolicy discountPolicy) {return discountPolicy.discount (this); } // ...}

Sada možemo koristiti dodatne informacije o Posebna narudžba u diskontnoj politici za izračun pravog popusta:

@DisplayName ("zadana posebna narudžba ispunjava uvjete za dodatni popust kod predmeta vrijednih 100 USD," + "kada se primjenjuju politike popusta od 20% za dodatne narudžbe s popustom," + "onda je trošak nakon popusta 80 USD") @ Test void test () baca izuzetak {// dano je logičko logičnoForExtraDiscount = true; Narudžba narudžbe = nova posebna narudžba (OrderFixtureUtils.orderLineItemsWorthNDollars (100), podobnaExtraDiscount); SpecialDiscountPolicy discountPolicy = novi SpecialDiscountPolicy () {@Preuzmi javni dvostruki popust (narudžba) {return 0; } @Override javni dvostruki popust (narudžba SpecialOrder) {if (order.isEligibleForExtraDiscount ()) return 0.20; povrat 0,10; }}; // kada je novac totalCostAfterDiscount = order.totalCost (discountPolicy); // zatim assertThat (totalCostAfterDiscount) .isEqualTo (Money.of (CurrencyUnit.USD, 80.00)); }

Uz to, budući da u razredima redoslijeda koristimo polimorfno ponašanje, lako možemo izmijeniti metodu izračuna ukupnih troškova.

4. Zaključak

U ovom smo članku naučili kako koristiti tehniku ​​dvostruke otpreme i Strategija (zvani Politika) uzorak u dizajnu vođenom domenom.

Potpuni izvorni kod svih primjera dostupan je na GitHubu.