לדלג לתוכן

תבניות התנהגות בתכנות - הסבר ודוגמאות

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

Strategy - כשיש כמה דרכים לבצע את אותה פעולה

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

class RegularDiscount:
    def apply(self, price):
        return price

class VIPDiscount:
    def apply(self, price):
        return price * 0.8

class Cart:
    def __init__(self, discount_strategy):
        self._strategy = discount_strategy

    def total(self, price):
        return self._strategy.apply(price)

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

Command - כשרוצים להפוך פעולה לאובייקט

תבנית Command הופכת בקשה או פעולה לאובייקט עצמאי, שאפשר להעביר, לתור, לבטל, או להריץ מאוחר יותר. זה מאוד שימושי במערכות עם תור משימות, פעולות "בטל" (undo), או לוג של כל פעולה שבוצעה.

class AddItemCommand:
    def __init__(self, cart, item):
        self.cart = cart
        self.item = item

    def execute(self):
        self.cart.append(self.item)

    def undo(self):
        self.cart.remove(self.item)

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

State - כשההתנהגות משתנה לפי מצב פנימי

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

במקום שדה status ועשרות בדיקות if order.status == "shipped" פזורות בכל הקוד, כל מצב הופך למחלקה נפרדת שיודעת בעצמה אילו פעולות מותרות ממנה, ולאיזה מצב אפשר לעבור הלאה. זה מונע מצב שבו לוגיקת המעברים בין סטטוסים מפוזרת בעשרות מקומות שונים בקוד ומתחילה לסתור את עצמה.

Observer - קצת עליו כאן, הרבה יותר בפוסט נפרד

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

איך יודעים איזו תבנית מתאימה

כמו תמיד - מהבעיה, לא מהתבנית. יש כמה דרכים לבצע את אותה פעולה והבחירה משתנה בזמן ריצה? Strategy. רוצים להפוך פעולה לאובייקט שאפשר לתור, לבטל או לתעד? Command. אובייקט מתנהג אחרת לגמרי בהתאם למצב פנימי? State. וכמה צדדים צריכים לדעת על אירוע בלי תלות ישירה? Observer.

לסיכום

תבניות התנהגות עוסקות בתקשורת בין אובייקטים, לא ביצירה או במבנה שלהם. Strategy מחליף if-else בבחירת אלגוריתם גמישה, Command הופך פעולה לאובייקט עצמאי, ו-State מארגן התנהגות שמשתנה לפי מצב. יחד עם תבניות היצירה והמבנה שעברנו בפוסטים הקודמים, יש לכם עכשיו את מפת הדרכים המלאה של שלוש הקטגוריות.

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

יש לכם שרשרת if-elif שהתפוצצה לממדים בלתי אפשריים? הצטרפו לקהילה בדיסקורד ותבדקו יחד עם אחרים איזו תבנית תחסוך לכם את הכאב הבא.