Dobbelt forsendelse i DDD

1. Oversigt

Dobbelt forsendelse er et teknisk udtryk til beskrivelse af proces med at vælge den metode, der skal påberåbes baseret på både modtager og argumenttyper.

Mange udviklere forveksler ofte dobbelt forsendelse med strategimønster.

Java understøtter ikke dobbelt forsendelse, men der er teknikker, vi kan bruge til at overvinde denne begrænsning.

I denne vejledning fokuserer vi på at vise eksempler på dobbelt forsendelse i forbindelse med Domain-driven Design (DDD) og strategimønster.

2. Dobbelt forsendelse

Før vi diskuterer dobbelt forsendelse, lad os gennemgå nogle grundlæggende og forklare, hvad Single Dispatch faktisk er.

2.1. Enkelt forsendelse

Enkel forsendelse er en måde at vælge implementeringen af ​​en metode baseret på modtagerens runtime-type. I Java er dette dybest set den samme som polymorfisme.

Lad os for eksempel se på denne enkle interface til rabatpolitik:

offentlig grænseflade DiscountPolicy {dobbeltrabat (ordreordre); }

Det Rabatpolitik interface har to implementeringer. Den flade, som altid returnerer den samme rabat:

offentlig klasse FlatDiscountPolicy implementerer DiscountPolicy {@Override offentlig dobbeltrabat (ordreordre) {retur 0,01; }}

Og den anden implementering, der returnerer rabatten baseret på de samlede omkostninger ved ordren:

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

Lad os antage, at behovene i dette eksempel er Bestille klasse har en Udgifter i alt() metode.

Nu er enkelt forsendelse i Java bare en meget velkendt polymorf adfærd demonstreret i følgende test:

@DisplayName ("givet to rabatpolitikker," + "når du bruger disse politikker," + "så vælger enkelt forsendelse implementeringen baseret på runtime-type") @Test ugyldig test () kaster Undtagelse {// givet DiscountPolicy flatPolicy = ny FlatDiscountPolicy ( ); DiscountPolicy amountPolicy = new AmountBasedDiscountPolicy (); Order orderWorth501Dollars = orderWorthNDollars (501); // når dobbelt flatDiscount = flatPolicy.discount (orderWorth501Dollars); dobbelt beløbDiscount = amountPolicy.discount (orderWorth501Dollars); // derefter assertThat (flatDiscount) .isEqualTo (0.01); assertThat (amountDiscount) .isEqualTo (0,1); }

Hvis alt dette virker ret ligetil, skal du holde dig opdateret. Vi bruger det samme eksempel senere.

Vi er nu klar til at indføre dobbelt forsendelse.

2.2. Dobbelt forsendelse vs Overbelastning af metode

Dobbelt forsendelse bestemmer metoden, der skal påkaldes ved kørsel, både på modtagertypen og argumenttyperne.

Java understøtter ikke dobbelt forsendelse.

Bemærk, at dobbelt forsendelse ofte forveksles med metodeoverbelastning, hvilket ikke er det samme. Metodeoverbelastning vælger den metode, der skal påberåbes, kun baseret på information om kompileringstid, som f.eks. Variabelens erklæringstype.

Følgende eksempel forklarer denne adfærd i detaljer.

Lad os introducere en ny rabatgrænseflade kaldet SpecialDiscountPolicy:

offentlig grænseflade SpecialDiscountPolicy udvider DiscountPolicy {dobbeltrabat (SpecialOrder-ordre); }

Specialbestilling blot strækker sig Bestille uden tilføjet ny adfærd.

Nu, når vi opretter en forekomst af Specialbestilling men erklær det som normalt Bestille, så bruges den særlige rabatmetode ikke:

@DisplayName ("givet rabatpolitik, der accepterer specialordrer," + ", når politikken gælder for specialordrer, der er deklareret som almindelig ordre," + ", så bruges den almindelige rabatmetode") @Test ugyldig test () kaster undtagelse {// givet SpecialDiscountPolicy specialPolicy = ny SpecialDiscountPolicy () {@Override offentlig dobbeltrabat (ordreordre) {returner 0,01; } @Override offentlig dobbeltrabat (SpecialOrder-ordre) {retur 0,10; }}; Bestil specialOrder = ny SpecialOrder (anyOrderLines ()); // når dobbeltrabat = specialPolicy.discount (specialOrder); // derefter assertThat (rabat) .isEqualTo (0.01); }

Derfor er metodeoverbelastning ikke dobbelt forsendelse.

Selvom Java ikke understøtter dobbelt forsendelse, kan vi bruge et mønster til at opnå lignende adfærd: Besøgende.

2.3. Besøgende mønster

Besøgende mønster giver os mulighed for at tilføje ny adfærd til de eksisterende klasser uden at ændre dem. Dette er muligt takket være den smarte teknik til at efterligne dobbelt forsendelse.

Lad os forlade rabateksemplet et øjeblik, så vi kan introducere besøgende mønster.

Forestil dig, at vi gerne vil producere HTML-visninger ved hjælp af forskellige skabeloner til hver slags ordre. Vi kunne tilføje denne adfærd direkte til ordreklasserne, men det er ikke den bedste idé på grund af at være en SRP-overtrædelse.

I stedet bruger vi besøgende mønster.

Først skal vi introducere Besøgende grænseflade:

offentlig grænseflade Besøgelig {void accept (V-besøgende); }

Vi bruger også en besøgsgrænseflade i vores kabinet med navnet OrderVisitor:

offentlig grænseflade OrderVisitor {void visit (Order order); ugyldigt besøg (SpecialOrder-ordre); }

En af ulemperne ved besøgende-mønsteret er imidlertid, at det kræver besøgelige klasser for at være opmærksom på den besøgende.

Hvis klasser ikke var designet til at understøtte den besøgende, kan det være svært (eller endda umuligt, hvis kildekoden ikke er tilgængelig) at anvende dette mønster.

Hver ordretype skal implementere Besøgende interface og give sin egen implementering, som tilsyneladende er identisk, en anden ulempe.

Bemærk, at de tilføjede metoder til Bestille og Specialordre er identiske:

offentlig klasse Bestil implementerer Besøgende {@Override public void accept (OrderVisitor visitor) {visitor.visit (this); }} offentlig klasse SpecialOrder udvider ordre {@Override public void accept (OrderVisitor visitor) {visitor.visit (this); }}

Det kan være fristende at ikke implementere det igen acceptere i underklassen. Men hvis vi ikke gjorde det, så OrderVisitor.visit (ordre) Metoden ville naturligvis altid blive brugt på grund af polymorfisme.

Lad os endelig se implementeringen af OrderVisitor ansvarlig for oprettelse af HTML-visninger:

offentlig klasse HtmlOrderViewCreator implementerer OrderVisitor {private String html; offentlig streng getHtml () {return html; } @Override offentligt ugyldigt besøg (ordreordre) {html = String.format ("

Almindelig ordre samlede omkostning:% s

", order.totalCost ());} @ Overtride offentligt ugyldigt besøg (SpecialOrder-rækkefølge) {html = String.format ("

samlede omkostninger:% s

", order.totalCost ());}}

Følgende eksempel viser brugen af HtmlOrderViewCreator:

@DisplayName ("given samling af regelmæssige og specielle ordrer," + "når du opretter HTML-visning ved hjælp af besøgende for hver ordre," + "så oprettes den dedikerede visning for hver ordre") @Test ugyldig test () kaster undtagelse {// given List 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 specialOrderHtml = htmlOrderViewCreator.getHtml (); // derefter assertThat (regularOrderHtml) .containsPattern ("

Regelmæssige ordre samlede omkostninger:. *

"); assertThat (specialOrderHtml) .containsPattern ("

Udgifter i alt: .*

"); }

3. Dobbelt forsendelse i DDD

I tidligere sektioner diskuterede vi dobbelt forsendelse og besøgermønsteret.

Vi er nu endelig klar til at vise, hvordan vi bruger disse teknikker i DDD.

Lad os gå tilbage til eksemplet med ordrer og rabatpolitikker.

3.1. Rabatpolitik som strategimønster

Tidligere introducerede vi Bestille klasse og dens Udgifter i alt() metode, der beregner summen af ​​alle ordrelinjeposter:

offentlig klasse Bestil {public Money totalCost () {// ...}}

Der er også Rabatpolitik interface til at beregne rabatten for ordren. Denne grænseflade blev introduceret for at tillade brug af forskellige rabatpolitikker og ændre dem ved kørsel.

Dette design er meget smidigere end blot hardcodning af alle mulige rabatpolitikker i Bestille klasser:

offentlig grænseflade DiscountPolicy {dobbeltrabat (ordreordre); }

Vi har ikke nævnt dette eksplicit indtil videre, men dette eksempel bruger strategimønsteret. DDD bruger ofte dette mønster til at overholde det allestedsnærværende sprog-princip og opnå lav kobling. I DDD-verdenen hedder Strategimønsteret ofte Politik.

Lad os se, hvordan man kombinerer dobbelt forsendelse teknik og rabat politik.

3.2. Dobbelt forsendelse og rabatpolitik

For korrekt at bruge politikmønsteret er det ofte en god ide at sende det som et argument. Denne tilgang følger Tell, Don't Ask-princippet, som understøtter bedre indkapsling.

F.eks Bestille klasse kan implementere Udgifter i alt ligesom:

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

Lad os antage, at vi gerne vil behandle hver type ordre forskelligt.

For eksempel, når der beregnes rabat for specialordrer, er der nogle andre regler, der kræver information, der er unik for Specialordre klasse. Vi ønsker at undgå støbning og refleksion og samtidig være i stand til at beregne de samlede omkostninger for hver Bestille med den korrekt anvendte rabat.

Vi ved allerede, at metodeoverbelastning sker på kompileringstidspunktet. Så det naturlige spørgsmål opstår: hvordan kan vi dynamisk sende ordrerabatlogik til den rigtige metode baseret på ordrenes kørselstype?

Svaret? Vi er nødt til at ændre ordreklasser lidt.

Roden Bestille klasse skal afsendes til argumentet om rabatpolitik ved kørsel. Den nemmeste måde at opnå dette på er at tilføje en beskyttet ApplyDiscountPolicy metode:

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

Takket være dette design undgår vi at duplikere forretningslogik i Udgifter i alt metode i Bestille underklasser.

Lad os vise en demo af brugen:

@DisplayName ("givet almindelig ordre med varer til en værdi af $ 100 i alt," + "når der anvendes 10% rabatpolitik," + ", så koster prisen efter rabat $ 90") @Test ugyldig test () kaster undtagelse {// givet ordreordre = ny Bestil (OrderFixtureUtils.orderLineItemsWorthNDollars (100)); SpecialDiscountPolicy discountPolicy = ny SpecialDiscountPolicy () {@Override offentlig dobbeltrabat (ordreordre) {retur 0,10; } @Override offentlig dobbeltrabat (SpecialOrder-ordre) {retur 0; }}; // når Money totalCostAfterDiscount = order.totalCost (discountPolicy); // derefter assertThat (totalCostAfterDiscount) .isEqualTo (Money.of (CurrencyUnit.USD, 90)); }

Dette eksempel bruger stadig besøgende mønster, men i en let ændret version. Ordreklasser er opmærksomme på det SpecialDiscountPolicy (Besøgende) har en vis betydning og beregner rabatten.

Som tidligere nævnt ønsker vi at kunne anvende forskellige rabatregler baseret på runtime-typen af Bestille. Derfor er vi nødt til at tilsidesætte den beskyttede ApplyDiscountPolicy metode i hver barneklasse.

Lad os tilsidesætte denne metode i Specialbestilling klasse:

offentlig klasse SpecialOrder udvider ordre {// ... @ Override beskyttet dobbelt ApplyDiscountPolicy (SpecialDiscountPolicy discountPolicy) {retur discountPolicy.discount (dette); } // ...}

Vi kan nu bruge ekstra information om Specialbestilling i rabatpolitikken for at beregne den rigtige rabat:

@DisplayName ("givet særlig ordre, der er berettiget til ekstra rabat med varer til en værdi af $ 100 i alt," + "når der anvendes 20% rabatpolitik for ekstra rabatordrer," + ", så koster prisen efter rabat $ 80") @ Test ugyldig test () kaster undtagelse {// givet boolsk eligForExtraDiscount = true; Ordreordre = ny SpecialOrder (OrderFixtureUtils.orderLineItemsWorthNDollars (100), eligForExtraDiscount); SpecialDiscountPolicy discountPolicy = ny SpecialDiscountPolicy () {@Override offentlig dobbeltrabat (ordreordre) {retur 0; } @Override offentlig dobbeltrabat (SpecialOrder-ordre) {if (order.isEligibleForExtraDiscount ()) returnerer 0,20; returnere 0,10; }}; // når penge totalCostAfterDiscount = order.totalCost (discountPolicy); // derefter assertThat (totalCostAfterDiscount) .isEqualTo (Money.of (CurrencyUnit.USD, 80,00)); }

Derudover kan vi nemt ændre den samlede omkostningsberegningsmetode, da vi bruger polymorf adfærd i ordreklasser.

4. Konklusion

I denne artikel har vi lært, hvordan man bruger dobbelt forsendelse teknik og Strategi (aka Politik) mønster i Domain-driven design.

Den fulde kildekode for alle eksemplerne er tilgængelig på GitHub.