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: 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: Ukupni trošak redovne narudžbe:% s ukupni trošak:% s Sljedeći primjer pokazuje upotrebu HtmlOrderViewCreator: Ukupni trošak redovne narudžbe:. * Ukupni trošak: .* 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. Ranije smo predstavili Narudžba razreda i svoje Ukupni trošak() metoda koja izračunava zbroj svih stavki reda narudžbe: 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: 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. 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: 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: Zahvaljujući ovom dizajnu, izbjegavamo dupliciranje poslovne logike u Ukupni trošak metoda u Narudžba potklase. Pokažimo demonstraciju upotrebe: 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: Sada možemo koristiti dodatne informacije o Posebna narudžba u diskontnoj politici za izračun pravog popusta: Uz to, budući da u razredima redoslijeda koristimo polimorfno ponašanje, lako možemo izmijeniti metodu izračuna ukupnih troškova. 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.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); }}
javna klasa HtmlOrderViewCreator implementira OrderVisitor {private String html; javni String getHtml () {return html; } @Override public void visit (Narudžba narudžbe) {html = String.format ("
@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 ("
3. Dvostruka otprema u DDD-u
3.1. Politika popusta kao obrazac strategije
nalog javne klase {javni novac totalCost () {// ...}}
javno sučelje DiscountPolicy {dvostruki popust (narudžba); }
3.2. Pravila dvostruke otpreme i popusta
narudžba javne klase / * ... * / {// ... javni novac totalCost (SpecialDiscountPolicy discountPolicy) {return totalCost (). multiptedBy (1 - discountPolicy.discount (this), RoundingMode.HALF_UP); } // ...}
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); } // ...}
@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)); }
javna klasa SpecialOrder produžuje narudžbu {// ... @Override protected double applyDiscountPolicy (SpecialDiscountPolicy discountPolicy) {return discountPolicy.discount (this); } // ...}
@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)); }
4. Zaključak