JavaRush /בלוג Java /Random-HE /דפוס עיצוב "אסטרטגיה"

דפוס עיצוב "אסטרטגיה"

פורסם בקבוצה
שלום! בהרצאות קודמות כבר נתקלנו במושג כזה כמו "דפוס עיצובי". למקרה ששכחת, נזכיר לך: מונח זה מציין פתרון סטנדרטי מסוים לבעיה נפוצה בתכנות. דפוס עיצוב "אסטרטגיה" - 1ב-JavaRush אנו אומרים לעתים קרובות שניתן לחפש בגוגל את התשובה כמעט לכל שאלה. לכן, מישהו כנראה כבר פתר בהצלחה בעיה דומה לשלך. אז, דפוסים הם פתרונות שנבדקו בזמן ובניסוי תרגול לבעיות או שיטות נפוצות ביותר לפתרון מצבי בעיה. אלו הם אותם "אופניים" שבשום מקרה לא צריך להמציא בעצמך, אבל צריך לדעת איך ומתי ליישם אותם :) משימה נוספת של דפוסים היא להביא את האדריכלות לסטנדרט אחד. קריאת קוד של מישהו אחר אינה משימה קלה! כל אחד כותב את זה אחרת, כי את אותה בעיה אפשר לפתור בדרכים רבות. אבל השימוש בתבניות מאפשר למתכנתים שונים להבין את ההיגיון של התוכנית מבלי להתעמק בכל שורת קוד (גם אם הם רואים אותה בפעם הראשונה!) היום נסתכל על אחת התבניות הנפוצות ביותר שנקראות "אסטרטגיה". דפוס עיצוב "אסטרטגיה" - 2בואו נדמיין שאנחנו כותבים תוכנית שפועלת באופן פעיל עם אובייקט המכונית. במקרה זה, זה אפילו לא חשוב במיוחד מה בדיוק התוכנית שלנו עושה. לשם כך, יצרנו מערכת ירושה עם כיתת הורה אחת Autoושלוש כיתות ילדים: Sedan, Truckו F1Car.
public class Auto {

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {

       System.out.println("Тормозим!");
   }
}

public class Sedan extends Auto {
}

public class Truck extends Auto {
}

public class F1Car extends Auto {
}
כל שלוש כיתות הילדים יורשות שתי שיטות סטנדרטיות מההורה - gas()והתוכנית stop() שלנו פשוטה מאוד: מכוניות יכולות רק לנסוע קדימה ולבלום. בהמשך עבודתנו, החלטנו להוסיף שיטה חדשה למכוניות - fill()(תדלוק). בואו נוסיף את זה למחלקת האב Auto:
public class Auto {

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {

       System.out.println("Тормозим!");
   }

   public void fill() {
       System.out.println("Заправить бензин!");
   }
}
נראה שעלולות להתעורר בעיות במצב כל כך פשוט? ובכן, למעשה, הם כבר קמו... דפוס עיצוב "אסטרטגיה" - 3
public class ChildrenBuggies extends Auto {

   public void fill() {

       //хм... Это детский багги, его не надо заправлять :/
   }
}
בתוכנית שלנו הופיעה מכונית שלא מתאימה לקונספט הכללי - כרכרה לילדים. יכול להיות שהוא מופעל באמצעות דוושה או בשליטה רדיו, אבל דבר אחד בטוח - אין איפה להכניס לתוכו בנזין. סכימת הירושה שלנו הביאה לכך שאנו מחלקים שיטות נפוצות אפילו למחלקות שאינן זקוקות להן. מה עלינו לעשות במצב כזה? ובכן, למשל, אתה יכול לעקוף את השיטה fill()בכיתה ChildrenBuggiesכך שכאשר אתה מנסה לתדלק את הבאגי, שום דבר לא קורה:
public class ChildrenBuggies extends Auto {

   @Override
   public void fill() {
       System.out.println("Игрушечную машину нельзя заправить!");
   }
}
אבל פתרון זה בקושי יכול להיקרא מוצלח, לפחות בגלל שכפול קוד. לדוגמה, רוב המחלקות ישתמשו בשיטה ממחלקת האב, אך מחלקות אחרות ייאלצו לעקוף אותה. אם יש לנו 15 מחלקות, וב-5-6 נאלץ לעקוף את ההתנהגות, שכפול הקוד יהפוך נרחב למדי. אולי ממשקים יכולים לעזור לנו? לדוגמה, זה:
public interface Fillable {

   public void fill();
}
ניצור ממשק Fillableבשיטה אחת fill(). בהתאם לכך, אותן מכוניות שצריך לתדלק יישמו ממשק זה, אך מכוניות אחרות (למשל, הבאגי שלנו) לא. אבל גם האופציה הזו לא תתאים לנו. ההיררכיה המעמדית שלנו עשויה לגדול למספר גדול מאוד בעתיד (תארו לעצמכם כמה סוגים שונים של מכוניות יש בעולם). נטשנו את אפשרות הירושה הקודמת כי לא רצינו לעקוף את ה- fill(). כאן נצטרך ליישם את זה בכל שיעור! מה אם יש לנו 50 מהם? ואם יבוצעו שינויים תכופים בתוכנית שלנו (ובתוכניות אמיתיות זה כמעט תמיד יקרה!), נצטרך להתרוצץ עם הלשון שלנו מבלה בין כל 50 השיעורים ולשנות את ההתנהגות של כל אחד מהם באופן ידני. אז מה עלינו לעשות בסופו של דבר? כדי לפתור את הבעיה שלנו, בואו נבחר בדרך אחרת. כלומר, בואו נפריד את התנהגות הכיתה שלנו מהכיתה עצמה. מה זה אומר? כידוע, לכל אובייקט יש מצב (סט של נתונים) והתנהגות (סט של שיטות). ההתנהגות של מחלקת המכונה שלנו מורכבת משלוש שיטות - gas(), stop()ו fill(). שתי השיטות הראשונות בסדר. אבל נעביר את השיטה השלישית מחוץ לכיתה Auto. זו תהיה הפרדת ההתנהגות מהכיתה (ליתר דיוק, אנחנו מפרידים רק חלק מההתנהגות - שתי השיטות הראשונות נשארות במקומן). לאן עלינו להעביר את השיטה שלנו fill()? שום דבר לא עולה מיד לראש :/ נראה היה שהוא לגמרי במקומו. נעביר אותו לממשק נפרד - FillStrategy!
public interface FillStrategy {

   public void fill();
}
למה אנחנו צריכים את הממשק הזה? זה פשוט. כעת נוכל ליצור מספר מחלקות שיטמיעו את הממשק הזה:
public class HybridFillStrategy implements FillStrategy {

   @Override
   public void fill() {
       System.out.println("Заправляем бензином or электричеством на выбор!");
   }
}

public class F1PitstopStrategy implements FillStrategy {

   @Override
   public void fill() {
       System.out.println("Заправляем бензин только после всех остальных procedures пит-стопа!");
   }
}

public class StandartFillStrategy implements FillStrategy {
   @Override
   public void fill() {
       System.out.println("Просто заправляем бензин!");
   }
}
יצרנו שלוש אסטרטגיות התנהגות - למכוניות קונבנציונליות, למכוניות היברידיות ולמכוניות פורמולה 1. כל אסטרטגיה מיישמת אלגוריתם תדלוק נפרד. במקרה שלנו, זה רק פלט לקונסולה, אבל יכול להיות שיש היגיון מורכב בתוך השיטה. מה עלינו לעשות עם זה הלאה?
public class Auto {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }

}
אנו משתמשים בממשק שלנו FillStrategyכשדה בכיתה האב Auto. שימו לב: אנחנו לא מציינים יישום ספציפי, אלא משתמשים בממשק. ונצטרך הטמעות ספציפיות של הממשק FillStrategyבכיתות רכב ילדים:
public class F1Car extends Auto {

   public F1Car() {
       this.fillStrategy = new F1PitstopStrategy();
   }
}

public class HybridAuto extends Auto {

   public HybridAuto() {
       this.fillStrategy = new HybridFillStrategy();
   }
}

public class Sedan extends Auto {

   public Sedan() {
       this.fillStrategy = new StandartFillStrategy();
   }
}
בוא נראה מה קיבלנו:
public class Main {

   public static void main(String[] args) {

       Auto sedan = new Sedan();
       Auto hybrid = new HybridAuto();
       Auto f1car = new F1Car();

       sedan.fill();
       hybrid.fill();
       f1car.fill();
   }
}
פלט מסוף:

Просто заправляем бензин!
Заправляем бензином or электричеством на выбор!
Заправляем бензин только после всех остальных procedures пит-стопа!
מעולה, תהליך התדלוק עובד כמו שצריך! אגב, שום דבר לא מונע מאיתנו להשתמש באסטרטגיה כפרמטר בקונסטרוקטור! לדוגמה, כך:
public class Auto {

   private FillStrategy fillStrategy;

   public Auto(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }

   public void fill() {
       this.fillStrategy.fill();
   }

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }
}

public class Sedan extends Auto {

   public Sedan() {
       super(new StandartFillStrategy());
   }
}



public class HybridAuto extends Auto {

   public HybridAuto() {
       super(new HybridFillStrategy());
   }
}

public class F1Car extends Auto {

   public F1Car() {
       super(new F1PitstopStrategy());
   }
}
בואו נריץ את השיטה שלנו main()(היא נשארת ללא שינוי) ונקבל את אותה תוצאה! פלט מסוף:

Просто заправляем бензин!
Заправляем бензином or электричеством на выбор!
Заправляем бензин только после всех остальных procedures пит-стопа!
דפוס האסטרטגיה מגדיר משפחה של אלגוריתמים, מקפל כל אחד מהם ומבטיח שהם ניתנים להחלפה. היא מאפשרת לשנות אלגוריתמים ללא קשר לשימוש בהם בצד הלקוח (הגדרה זו לקוחה מהספר "חקירת דפוסי עיצוב" ונראית לי מוצלחת ביותר). דפוס עיצוב "אסטרטגיה" - 4בודדנו את משפחת האלגוריתמים שמעניינים אותנו (סוגי מכוניות תדלוק) לממשקים נפרדים עם מספר יישומים. הפרדנו אותם מהמהות של המכונית. לכן, כעת, אם נצטרך לבצע שינויים כלשהם בתהליך התדלוק הזה או אחר, זה לא ישפיע על סוג המכוניות שלנו בשום צורה. באשר להחלפה, כדי להשיג אותה אנחנו רק צריכים להוסיף שיטת מגדיר אחת לכיתה שלנו Auto:
public class Auto {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }

   public void setFillStrategy(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }
}
כעת אנו יכולים לשנות אסטרטגיות תוך כדי תנועה:
public class Main {

   public static void main(String[] args) {

       ChildrenBuggies buggies = new ChildrenBuggies();
       buggies.setFillStrategy(new StandartFillStrategy());

       buggies.fill();
   }
}
אם פתאום מכוניות כרכרות לילדים יתחילו להתמלא בבנזין, התוכנית שלנו תהיה מוכנה לתרחיש כזה :) זה הכל, בעצם! למדת עוד דפוס עיצובי, שללא ספק תצטרך ויעזור לך יותר מפעם אחת בעבודה על פרויקטים אמיתיים :) נתראה שוב!
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION