Dobbel forsendelse i DDD

1. Oversikt

Dobbel forsendelse er et teknisk begrep for å beskrive prosess for å velge metoden å påkalle basert på både mottaker og argumenttyper.

Mange utviklere forveksler ofte dobbel forsendelse med strategimønster.

Java støtter ikke dobbel forsendelse, men det er teknikker vi kan bruke for å overvinne denne begrensningen.

I denne opplæringen vil vi fokusere på å vise eksempler på dobbel forsendelse i sammenheng med Domain-driven Design (DDD) og Strategy Pattern.

2. Dobbel forsendelse

Før vi diskuterer dobbel forsendelse, la oss se gjennom noen grunnleggende ting og forklare hva Single Dispatch faktisk er.

2.1. Enkelt forsendelse

Enkel forsendelse er en måte å velge implementering av en metode basert på mottakerens kjøretidstype. I Java er dette i utgangspunktet det samme som polymorfisme.

La oss for eksempel se på dette enkle grensesnittet for rabattregler:

offentlig grensesnitt DiscountPolicy {dobbeltrabatt (Bestill ordre); }

De Rabattpolitikk grensesnittet har to implementeringer. Den flate, som alltid gir samme rabatt:

offentlig klasse FlatDiscountPolicy implementerer DiscountPolicy {@Override offentlig dobbeltrabatt (Bestill ordre) {retur 0.01; }}

Og den andre implementeringen, som returnerer rabatten basert på den totale kostnaden for bestillingen:

offentlig klasse AmountBasedDiscountPolicy implementerer DiscountPolicy {@Override public double discount (Order order) {if (order.totalCost () .isGreaterThan (Money.of (CurrencyUnit.USD, 500.00))) {return 0,10; } annet {retur 0; }}}

For behovene i dette eksemplet, la oss anta Rekkefølge klasse har en totalkostnad() metode.

Nå er enkelt forsendelse i Java bare en veldig kjent polymorf oppførsel demonstrert i følgende test:

@DisplayName ("gitt to rabattretningslinjer," + "når du bruker disse retningslinjene," + "og deretter velger enkel forsendelse implementeringen basert på kjøretidstype") @Test ugyldig test () kaster Unntak {// gitt DiscountPolicy flatPolicy = new FlatDiscountPolicy ( ); DiscountPolicy amountPolicy = new AmountBasedDiscountPolicy (); Order orderWorth501Dollars = orderWorthNDollars (501); // når dobbel flatDiscount = flatPolicy.discount (orderWorth501Dollars); dobbelt beløpDiscount = amountPolicy.discount (orderWorth501Dollars); // deretter assertThat (flatDiscount) .isEqualTo (0.01); assertThat (amountDiscount) .isEqualTo (0.1); }

Hvis alt dette virker ganske rett frem, følg med. Vi bruker det samme eksemplet senere.

Vi er nå klare til å introdusere dobbel forsendelse.

2.2. Double Dispatch vs Method Overloading

Dobbel forsendelse bestemmer metoden for å påkalle ved kjøretid basert på mottakertypen og argumenttypene.

Java støtter ikke dobbel forsendelse.

Merk at dobbel forsendelse ofte forveksles med metodeoverbelastning, noe som ikke er det samme. Metodeoverbelastning velger metoden å påkalle basert bare på informasjon om kompileringstid, som variabelens erklæringstype.

Følgende eksempel forklarer denne oppførselen i detalj.

La oss introdusere et nytt rabattgrensesnitt kalt SpecialDiscountPolicy:

offentlig grensesnitt SpecialDiscountPolicy utvider DiscountPolicy {dobbeltrabatt (SpecialOrder-ordre); }

Spesial bestilling bare utvider Rekkefølge uten ny oppførsel lagt til.

Nå, når vi oppretter en forekomst av Spesial bestilling men erklær det som normalt Rekkefølge, så brukes ikke den spesielle rabattmetoden:

@DisplayName ("gitt rabattpolicy som godtar spesialbestillinger," + "når policyen på spesialbestilling er deklarert som vanlig ordre," + "så brukes vanlig rabattmetode") @Test ugyldig test () kaster unntak {// gitt SpecialDiscountPolicy specialPolicy = ny SpecialDiscountPolicy () {@Override offentlig dobbeltrabatt (Bestill ordre) {retur 0.01; } @Override offentlig dobbeltrabatt (SpecialOrder-ordre) {retur 0,10; }}; Bestill specialOrder = ny SpecialOrder (anyOrderLines ()); // når dobbeltrabatt = specialPolicy.discount (specialOrder); // deretter assertThat (rabatt) .isEqualTo (0.01); }

Derfor er ikke overbelastning av metoden dobbelt forsendelse.

Selv om Java ikke støtter dobbel forsendelse, kan vi bruke et mønster for å oppnå lignende oppførsel: Visitor.

2.3. Besøksmønster

Besøkermønsteret lar oss legge til ny oppførsel i eksisterende klasser uten å endre dem. Dette er mulig takket være den smarte teknikken for å etterligne dobbel forsendelse.

La oss forlate rabatteksemplet et øyeblikk, slik at vi kan introdusere besøksmønsteret.

Tenk deg at vi vil produsere HTML-visninger ved hjelp av forskjellige maler for hver type ordre. Vi kan legge til denne oppførselen direkte i ordreklassene, men det er ikke den beste ideen på grunn av å være et brudd på SRP.

I stedet bruker vi besøksmønsteret.

Først må vi introdusere Besøksvennlig grensesnitt:

offentlig grensesnitt Visitable {void accept (V besøkende); }

Vi bruker også et besøksgrensesnitt, i vårt tilfelle navngitt OrderVisitor:

offentlig grensesnitt OrderVisitor {void visit (Order order); ugyldig besøk (SpecialOrder order); }

En av ulempene med besøksmønsteret er imidlertid at det krever besøkbare klasser for å være klar over besøkende.

Hvis klasser ikke var designet for å støtte besøkende, kan det være vanskelig (eller til og med umulig hvis kildekoden ikke er tilgjengelig) å bruke dette mønsteret.

Hver ordretype må implementere Besøksvennlig grensesnitt og gi sin egen implementering som tilsynelatende er identisk, en annen ulempe.

Legg merke til at de tilførte metodene til Rekkefølge og Spesial bestilling er identiske:

public class Order implementerer Visitable {@Override public void accept (OrderVisitor besøkende) {visitor.visit (dette); }} offentlig klasse SpecialOrder utvider bestillingen {@Override public void accept (OrderVisitor visitor) {visitor.visit (this); }}

Det kan være fristende å ikke implementere det på nytt aksepterer i underklassen. Men hvis vi ikke gjorde det, så OrderVisitor.visit (Bestilling) metoden vil alltid bli brukt, selvfølgelig, på grunn av polymorfisme.

Til slutt, la oss se implementeringen av OrderVisitor ansvarlig for å lage HTML-visninger:

offentlig klasse HtmlOrderViewCreator implementerer OrderVisitor {private String html; offentlig streng getHtml () {return html; } @Override offentlig ugyldig besøk (bestillingsordre) {html = String.format ("

Vanlig ordrekostnad:% s

", order.totalCost ());} @ Overstyr offentlig ugyldig besøk (SpecialOrder-rekkefølge) {html = String.format ("

totale kostnader

", order.totalCost ());}}

Følgende eksempel viser bruken av HtmlOrderViewCreator:

@DisplayName ("gitt samling av vanlige og spesielle bestillinger," + "når du oppretter HTML-visning ved hjelp av besøkende for hver bestilling," + "så blir den dedikerte visningen opprettet for hver bestilling") @Test ugyldig test () kaster Unntak {// gitt Liste anyOrderLines = OrderFixtureUtils.anyOrderLines (); Listeordrer = Arrays.asList (ny ordre (anyOrderLines), ny SpecialOrder (anyOrderLines)); HtmlOrderViewCreator htmlOrderViewCreator = ny HtmlOrderViewCreator (); // når orders.get (0) .accept (htmlOrderViewCreator); Streng regularOrderHtml = htmlOrderViewCreator.getHtml (); orders.get (1) .accept (htmlOrderViewCreator); Streng spesialOrderHtml = htmlOrderViewCreator.getHtml (); // deretter assertThat (regularOrderHtml) .containsPattern ("

Vanlig ordrekostnad:. *

"); assertThat (specialOrderHtml) .containsPattern ("

totalkostnad: .*

"); }

3. Dobbel forsendelse i DDD

I tidligere avsnitt diskuterte vi dobbel forsendelse og besøksmønsteret.

Vi er nå endelig klare til å vise hvordan du bruker disse teknikkene i DDD.

La oss gå tilbake til eksemplet på ordrer og rabatter.

3.1. Rabattpolitikk som strategimønster

Tidligere introduserte vi Rekkefølge klasse og dens totalkostnad() metode som beregner summen av alle ordrelinjene:

public class Bestill {public Money totalCost () {// ...}}

Det er også Rabattpolitikk grensesnitt for å beregne rabatten for bestillingen. Dette grensesnittet ble introdusert for å tillate bruk av forskjellige rabattregler og endre dem ved kjøretid.

Denne designen er mye mer smidig enn bare å hardkode alle mulige rabattregler Rekkefølge klasser:

offentlig grensesnitt DiscountPolicy {dobbeltrabatt (Bestill ordre); }

Vi har ikke nevnt dette eksplisitt så langt, men dette eksemplet bruker strategimønsteret. DDD bruker ofte dette mønsteret for å samsvare med Ubiquitous Language-prinsippet og oppnå lav kobling. I DDD-verdenen blir Strategimønsteret ofte kalt Policy.

La oss se hvordan du kombinerer dobbel forsendelsesteknikk og rabattpolicy.

3.2. Retningslinjer for dobbel forsendelse og rabatt

For å bruke policy-mønsteret riktig, er det ofte en god ide å sende det som et argument. Denne tilnærmingen følger Tell, Don't Ask-prinsippet som støtter bedre innkapsling.

For eksempel Rekkefølge klasse kan implementere totalkostnad som så:

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

La oss anta at vi vil behandle hver type ordre forskjellig.

For eksempel, når du beregner rabatten for spesialbestillinger, er det noen andre regler som krever informasjon som er unik for Spesial bestilling klasse. Vi vil unngå støping og refleksjon og samtidig kunne beregne totalkostnadene for hver Rekkefølge med riktig anvendt rabatt.

Vi vet allerede at overbelastning av metoden skjer på kompileringstidspunktet. Så det naturlige spørsmålet oppstår: hvordan kan vi dynamisk sende ordrerabattlogikk til riktig metode basert på ordrenes kjøretid?

Svaret? Vi må endre ordreklassene litt.

Roten Rekkefølge klassen må sende til argumentet for rabattpolicy ved kjøretid. Den enkleste måten å oppnå dette på er å legge til en beskyttet ApplyDiscountPolicy metode:

public class Order / * ... * / {// ... public Money totalCost (SpecialDiscountPolicy discountPolicy) {return totalCost (). multipliedBy (1 - applyDiscountPolicy (discountPolicy), RoundingMode.HALF_UP); } beskyttet dobbel applyDiscountPolicy (SpecialDiscountPolicy discountPolicy) {retur discountPolicy.discount (dette); } // ...}

Takket være dette designet unngår vi å duplisere forretningslogikken i totalkostnad metode i Rekkefølge underklasser.

La oss vise en demonstrasjon av bruken:

@DisplayName ("gitt vanlig ordre med varer verdt $ 100 totalt," + "når det gjelder 10% rabattpolicy," + "og kostnad etter rabatt er $ 90") @Test ugyldig test () kaster unntak {// gitt ordreordre = ny Order (OrderFixtureUtils.orderLineItemsWorthNDollars (100)); SpecialDiscountPolicy discountPolicy = ny SpecialDiscountPolicy () {@Override offentlig dobbeltrabatt (Bestill ordre) {retur 0,10; } @Override offentlig dobbeltrabatt (SpecialOrder-ordre) {retur 0; }}; // når Money totalCostAfterDiscount = order.totalCost (discountPolicy); // deretter assertThat (totalCostAfterDiscount) .isEqualTo (Money.of (CurrencyUnit.USD, 90)); }

Dette eksemplet bruker fortsatt besøksmønsteret, men i en litt modifisert versjon. Bestillingsklasser er klar over det SpecialDiscountPolicy (den besøkende) har en viss betydning og beregner rabatten.

Som nevnt tidligere, ønsker vi å kunne bruke forskjellige rabattregler basert på kjøretidstypen Rekkefølge. Derfor må vi overstyre den beskyttede ApplyDiscountPolicy metode i hver barneklasse.

La oss overstyre denne metoden i Spesial bestilling klasse:

offentlig klasse SpecialOrder utvider bestillingen {// ... @Override-beskyttet dobbelt gjelderDiscountPolicy (SpecialDiscountPolicy discountPolicy) {retur discountPolicy.discount (dette); } // ...}

Vi kan nå bruke ekstra informasjon om Spesial bestilling i rabattpolicyen for å beregne riktig rabatt:

@DisplayName ("gitt spesialbestilling som er kvalifisert for ekstra rabatt med varer verdt $ 100 totalt," + "når det gjelder 20% rabattregler for ekstra rabattbestillinger," + ", så koster det etter rabatt $ 80") @ Test ugyldig test () kaster unntak {// gitt boolsk eligForExtraDiscount = true; Bestillingsordre = ny SpecialOrder (OrderFixtureUtils.orderLineItemsWorthNDollars (100), eligForExtraDiscount); SpecialDiscountPolicy discountPolicy = ny SpecialDiscountPolicy () {@Override public double discount (Order order) {retur 0; } @Override offentlig dobbeltrabatt (SpecialOrder-ordre) {if (order.isEligibleForExtraDiscount ()) returnerer 0,20; retur 0,10; }}; // når Money totalCostAfterDiscount = order.totalCost (discountPolicy); // deretter assertThat (totalCostAfterDiscount) .isEqualTo (Money.of (CurrencyUnit.USD, 80.00)); }

I tillegg, siden vi bruker polymorf oppførsel i ordreklasser, kan vi enkelt endre den totale kostnadsberegningsmetoden.

4. Konklusjon

I denne artikkelen har vi lært hvordan du bruker dobbel forsendelsesteknikk og Strategi (aka Politikk) mønster i Domain-driven design.

Den fullstendige kildekoden for alle eksemplene er tilgjengelig på GitHub.


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