שיגור כפול ב- DDD

1. סקירה כללית

משלוח כפול הוא מונח טכני לתיאור ה- תהליך בחירת השיטה להפעיל על סמך סוגי מקלט וטיעון ארגומנטים.

הרבה מפתחים מבלבלים לעתים קרובות משלוח כפול עם תבנית אסטרטגיה.

ג'אווה אינה תומכת בשיגור כפול, אך ישנן טכניקות שבהן אנו יכולים להשתמש כדי להתגבר על מגבלה זו.

במדריך זה נתמקד בהצגת דוגמאות להעברה כפולה בהקשר של עיצוב מונחה תחום (DDD) ותבנית אסטרטגיה.

2. שיגור כפול

לפני שנדון בשיגור כפול, בואו נסקור כמה יסודות ונסביר מהי בעצם שיגור יחיד.

2.1. שיגור יחיד

משלוח יחיד הוא דרך לבחור יישום של שיטה על בסיס סוג זמן הריצה של המקלט. בג'אווה זה בעצם אותו דבר כמו פולימורפיזם.

לדוגמא, בואו נסתכל על ממשק מדיניות ההנחות הפשוט הזה:

ממשק ציבורי DiscountPolicy {הנחה כפולה (הזמנת הזמנה); }

ה מדיניות דיסקונט לממשק יש שתי יישומים. הדירה, שתמיד מחזירה את אותה ההנחה:

מחלקה ציבורית FlatDiscountPolicy מיישמת DiscountPolicy {@Override הנחה כפולה ציבורית (הזמנת הזמנה) {החזר 0.01; }}

והיישום השני, שמחזיר את ההנחה על בסיס העלות הכוללת של ההזמנה:

מחלקה ציבורית AmountBasedDiscountPolicy מיישמת DiscountPolicy {@Override הנחה כפולה ציבורית (הזמנת הזמנה) {if (order.totalCost () .isGreaterThan (Money.of (CurrencyUnit.USD, 500.00))) {החזר 0.10; } אחר {להחזיר 0; }}}

לצרכי הדוגמה הזו, נניח ש- להזמין בכיתה יש עלות כוללת() שיטה.

כעת, משלוח יחיד בג'אווה הוא רק התנהגות פולימורפית ידועה מאוד שהוכחה במבחן הבא:

@DisplayName ("ניתנות לשתי מדיניות הנחות," + "בעת השימוש במדיניות זו," + "ואז משלוח יחיד בוחר את היישום על סמך סוג זמן ריצה") @Test void test () זורק Exception {// given DiscountPolicy flatPolicy = new FlatDiscountPolicy ( ); DiscountPolicy amountPolicy = New AmountBasedDiscountPolicy (); Order orderWorth501Dollars = orderWorthNDollars (501); // כאשר flatDiscount כפולה = flatPolicy.discount (orderWorth501Dollars); סכום כפול הנחה = סכום מדיניות. הנחה (orderWorth501Dollars); // ואז טוענים כי (flatDiscount) .isEqualTo (0.01); assertThat (amountDiscount) .isEqualTo (0.1); }

אם כל זה נראה די ישר קדימה, הישאר מכוון. נשתמש באותה דוגמה בהמשך.

כעת אנו מוכנים להציג משלוח כפול.

2.2. שיגור כפול לעומת עומס יתר על השיטה

שיגור כפול קובע את השיטה להפעיל בזמן ריצה בהתבסס על סוג המקלט וסוגי הארגומנט.

Java אינה תומכת בשיגור כפול.

שים לב כי שיגור כפול מתבלבל לרוב עם העמסת יתר של השיטה, וזה לא אותו הדבר. העמסת יתר של השיטה בוחרת בשיטה להפעיל על סמך מידע זמן הידור בלבד, כמו סוג ההצהרה של המשתנה.

הדוגמה הבאה מסבירה התנהגות זו בפירוט.

בואו ונציג ממשק הנחה חדש שנקרא מדיניות מיוחדת:

ממשק ציבורי SpecialDiscountPolicy מרחיב DiscountPolicy {הנחה כפולה (הזמנה מיוחדת); }

הזמנה מיוחדת פשוט מתארך להזמין ללא התנהגות חדשה שנוספה.

עכשיו, כשאנחנו יוצרים מופע של הזמנה מיוחדת אבל להכריז על זה כרגיל להזמין, אז לא משתמשים בשיטת ההנחה המיוחדת:

@DisplayName ("מדיניות ההנחות שניתנה לקבל הזמנות מיוחדות," + "כאשר מיישמת את המדיניות בהזמנה מיוחדת שהוכרזה כהזמנה רגילה," + ואז משתמשים בשיטת הנחה רגילה ") @ מבחן בטל בטל () זורק חריג {// נתון SpecialDiscountPolicy specialPolicy = SpecialDiscountPolicy חדש () {@Override הנחה כפולה ציבורית (הזמנת הזמנה) {החזר 0.01; } @ הנחה כפולה פומבית של @ Override (הזמנה מיוחדת) {החזר 0.10; }}; להזמין specialOrder = SpecialOrder חדש (anyOrderLines ()); // כאשר הנחה כפולה = specialPolicy.discount (specialOrder); // ואז טוענים כי (הנחה) .isEqualTo (0.01); }

לכן, העמסת יתר של השיטה אינה משלוח כפול.

גם אם ג'אווה אינה תומכת בשיגור כפול, אנו יכולים להשתמש בתבנית להשגת התנהגות דומה: מבקר.

2.3. דפוס מבקר

דפוס המבקרים מאפשר לנו להוסיף התנהגות חדשה לשיעורים הקיימים מבלי לשנות אותם. זה אפשרי בזכות הטכניקה החכמה של חיקוי משלוח כפול.

בוא נעזוב את דוגמת ההנחה לרגע כדי שנוכל להציג את דפוס המבקרים.

תאר לעצמך שברצוננו לייצר תצוגות HTML באמצעות תבניות שונות לכל סוג הזמנה. אנו יכולים להוסיף התנהגות זו ישירות לשיעורי ההזמנות, אך זה לא הרעיון הטוב ביותר בגלל היותנו הפרה של ה- SRP.

במקום זאת, נשתמש בתבנית המבקרים.

ראשית, עלינו להציג את ניתן לבקר מִמְשָׁק:

ממשק ציבורי ניתן לביקור {void accept (מבקר V); }

אנו נשתמש גם בממשק מבקרים, בשם שלנו OrderVisitor:

ממשק ציבורי OrderVisitor {void visit (הזמנת הזמנה); ביקור בטל (הזמנת הזמנה מיוחדת); }

עם זאת, אחד החסרונות של דפוס המבקרים הוא שהוא דורש שיעורים לביקור כדי להיות מודע למבקר.

אם שיעורים לא תוכננו לתמוך במבקר, ייתכן שיהיה קשה (או אפילו בלתי אפשרי אם קוד המקור אינו זמין) ליישם תבנית זו.

כל סוג הזמנה צריך ליישם את ה- ניתן לבקר ממשק ולספק יישום משלו שלכאורה זהה, חסרון נוסף.

שימו לב שהשיטות שנוספו ל להזמין ו הזמנה מיוחדת זהים:

יישום ההזמנה ברמה הציבורית ניתן לביקור {@Override public void accept (אורח OrderVisitor) {בקר.ביקור (זה); }} מחלקה ציבורית SpecialOrder מרחיב את ההזמנה {@Override public void accept (OrderVisitor visitor) {visitor.visit (this); }}

זה עשוי להיות מפתה לא ליישם מחדש לְקַבֵּל במעמד המשנה. עם זאת, אם לא עשינו זאת, אז OrderVisitor.visit (הזמנה) שיטה תמיד תתרגל, כמובן, בגלל פולימורפיזם.

לבסוף, בואו נראה את היישום של OrderVisitor אחראי ליצירת תצוגות HTML:

מחלקה ציבורית HtmlOrderViewCreator מיישם OrderVisitor {פרטי מחרוזת HTML; מחרוזת ציבורית getHtml () {return html; } ביקורת בטל ציבורית של @ עקירה (הזמנת הזמנה) {html = String.format ("

עלות כוללת של הזמנה רגילה:% s

", order.totalCost ());} ביקור בטל ציבורי @Override (SpecialOrder order) {html = String.format ("

מחיר כולל

", order.totalCost ());}}

הדוגמה הבאה מדגימה את השימוש ב- HtmlOrderViewCreator:

@DisplayName ("אוסף נתון של הזמנות רגילות ומיוחדות," + "בעת יצירת תצוגת HTML באמצעות מבקר לכל הזמנה," + "ואז התצוגה הייעודית נוצרת עבור כל הזמנה") @ Test void test () זורק חריג {// רשימת נתון anyOrderLines = OrderFixtureUtils.anyOrderLines (); הזמנות רשימה = Arrays.asList (הזמנה חדשה (anyOrderLines), הזמנה מיוחדת חדשה (anyOrderLines)); HtmlOrderViewCreator htmlOrderViewCreator = HtmlOrderViewCreator חדש (); // when orders.get (0) .accept (htmlOrderViewCreator); מחרוזת regularOrderHtml = htmlOrderViewCreator.getHtml (); orders.get (1) .accept (htmlOrderViewCreator); מחרוזת specialOrderHtml = htmlOrderViewCreator.getHtml (); // ואז assertThat (regularOrderHtml) .containsPattern ("

עלות כוללת של הזמנה רגילה:. *

"); assertThat (specialOrderHtml) .containsPattern ("

עלות כוללת: .*

"); }

3. שיגור כפול ב- DDD

בחלקים הקודמים דנו בשיגור כפול ובדפוס המבקרים.

כעת סוף סוף אנו מוכנים להראות כיצד להשתמש בטכניקות אלה ב- DDD.

בואו נחזור לדוגמא של הזמנות ומדיניות הנחות.

3.1. מדיניות הנחות כדפוס אסטרטגיה

מוקדם יותר, הצגנו את להזמין הכיתה שלה עלות כוללת() שיטה המחשבת את סכום כל פריטי ההזמנה:

הזמנה בכיתה ציבורית {ציבורי כסף ציבורי כולל () {// ...}}

יש גם את מדיניות דיסקונט ממשק לחישוב ההנחה להזמנה. ממשק זה הוצג כדי לאפשר שימוש במדיניות הנחות שונות ולשנות אותן בזמן הריצה.

עיצוב זה הוא הרבה יותר גמיש מאשר פשוט קידוד קשיח של כל מדיניות ההנחות האפשריות להזמין שיעורים:

ממשק ציבורי DiscountPolicy {הנחה כפולה (הזמנת הזמנה); }

לא הזכרנו זאת במפורש עד כה, אך דוגמה זו משתמשת בתבנית האסטרטגיה. DDD משתמש לעתים קרובות בתבנית זו כדי להתאים את עקרון השפה בכל מקום ולהשיג צימוד נמוך. בעולם DDD, דפוס האסטרטגיה נקרא לעתים קרובות מדיניות.

בואו נראה כיצד לשלב בין טכניקת שיגור כפול ומדיניות הנחות.

3.2. מדיניות שיגור והנחות כפולה

כדי להשתמש נכון בתבנית המדיניות, לרוב מומלץ להעביר אותה כוויכוח. גישה זו מתאימה לעיקרון Tell, Don't Ask התומך בכמוסה טובה יותר.

לדוגמא, ה להזמין הכיתה עשויה ליישם עלות כוללת ככה:

class class הזמנה / * ... * / {// ... public money totalCost (SpecialDiscountPolicy discountPolicy) {return totalCost (). multipliedBy (1 - discountPolicy.discount (this), RoundingMode.HALF_UP); } // ...}

עכשיו, נניח שנרצה לעבד כל סוג של סדר באופן שונה.

לדוגמא, בעת חישוב ההנחה להזמנות מיוחדות, ישנם כמה כללים אחרים המחייבים מידע ייחודי ל- הזמנה מיוחדת מעמד. אנו רוצים להימנע מלהקה והשתקפות ובמקביל להיות מסוגלים לחשב את העלויות הכוללות עבור כל אחד להזמין עם ההנחה המיושמת כהלכה.

אנחנו כבר יודעים שעומס יתר של השיטה קורה בזמן הקומפילציה. אז, נשאלת השאלה הטבעית: כיצד נוכל לשלוח באופן דינמי לוגיקת הנחות הזמנות לשיטה הנכונה בהתבסס על סוג זמן הריצה של ההזמנה?

התשובה? עלינו לשנות מעט את שיעורי ההזמנות.

השורש להזמין הכיתה צריכה להישלח לטיעון מדיניות ההנחות בזמן הריצה. הדרך הקלה ביותר להשיג זאת היא להוסיף מוגן ApplyDiscountPolicy שיטה:

class class הזמנה / * ... * / {// ... public money totalCost (SpecialDiscountPolicy discountPolicy) {return totalCost (). multipliedBy (1 - applyDiscountPolicy (discountPolicy), RoundingMode.HALF_UP); } כפולה מוגנת applyDiscountPolicy (SpecialDiscountPolicy discountPolicy) {return discountPolicy.discount (this); } // ...}

הודות לעיצוב זה, אנו נמנעים משכפול ההיגיון העסקי במגזין עלות כוללת שיטה ב להזמין מחלקות משנה.

בואו נראה הדגמת שימוש:

@DisplayName ("ניתנת הזמנה רגילה עם פריטים בשווי 100 דולר בסך הכל," + "בעת החלת מדיניות הנחה של 10%," + "ואז העלות לאחר ההנחה היא 90 דולר") @ מבחן בטל בטל () מעניק חריג {// נתון להזמנת הזמנה = חדש הזמנה (OrderFixtureUtils.orderLineItemsWorthNDollars (100)); SpecialDiscountPolicy discountPolicy = חדש SpecialDiscountPolicy () {@Override הנחה כפולה ציבורית (הזמנת הזמנה) {החזר 0.10; } @Override הנחה כפולה פומבית (הזמנה מיוחדת) {חזרה 0; }}; // כאשר כסף totalCostAfterDiscount = order.totalCost (discountPolicy); // ואז assertThat (totalCostAfterDiscount) .isEqualTo (Money.of (CurrencyUnit.USD, 90)); }

דוגמה זו עדיין משתמשת בתבנית המבקרים אך בגרסה מעט שונה. שיעורי סדר מודעים לכך מדיניות מיוחדת (למבקר) יש משמעות מסוימת ומחשב את ההנחה.

כאמור, אנו רוצים להיות מסוגלים להחיל כללי הנחות שונים בהתבסס על סוג זמן הריצה של להזמין. לכן, עלינו לבטל את המוגן ApplyDiscountPolicy שיטה בכל כיתת ילדים.

בואו נעקוף את השיטה הזו ב הזמנה מיוחדת מעמד:

מחלקה ציבורית SpecialOrder מרחיבה הזמנה {// ... @Override מוגן כפול ApplyDiscountPolicy (SpecialDiscountPolicy discountPolicy) {return discountPolicy.discount (this); } // ...}

כעת אנו יכולים להשתמש במידע נוסף אודות הזמנה מיוחדת במדיניות ההנחות לחישוב ההנחה הנכונה:

@DisplayName ("ניתן הזמנה מיוחדת זכאית להנחה נוספת עם פריטים בשווי 100 דולר בסך הכל," + "כאשר חלה מדיניות הנחה של 20% בהזמנות נוספות," + "ואז עלות לאחר הנחה היא 80 דולר") @ מבחן בטל בטל () זורק חריג {// ניתן בוליאני כשירForExtraDiscount = נכון; הזמנת הזמנה = הזמנה מיוחדת (OrderFixtureUtils.orderLineItemsWorthNDollars (100), כשיר לפורנטרה-הנחה); SpecialDiscountPolicy discountPolicy = SpecialDiscountPolicy חדש () {@Override הנחה כפולה פומבית (הזמנת הזמנה) {החזר 0; } @ הנחה כפולה פומבית של @ Override (הזמנה מיוחדת) {אם (order.isEligibleForExtraDiscount ()) מחזירה 0.20; החזר 0.10; }}; // כאשר כסף totalCostAfterDiscount = order.totalCost (discountPolicy); // ואז assertThat (totalCostAfterDiscount) .isEqualTo (Money.of (CurrencyUnit.USD, 80.00)); }

בנוסף, מכיוון שאנו משתמשים בהתנהגות פולימורפית בשיעורי סדר, אנו יכולים לשנות בקלות את שיטת חישוב העלות הכוללת.

4. מסקנה

במאמר זה למדנו כיצד להשתמש בטכניקת שיגור כפול ו אִסטרָטֶגִיָה (aka מְדִינִיוּת) תבנית בעיצוב מונחה תחום.

קוד המקור המלא של כל הדוגמאות זמין באתר GitHub.


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