Menu
Coddy logo textTech

תבנית Strategy

חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 95 מתוך 104.

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

התבנית מורכבת משלושה חלקים: ממשק אסטרטגיה שמצהיר על שיטת האלגוריתם, אסטרטגיות קונקרטיות שמממשות וריאציות שונות, והקשר שמשתמש באסטרטגיה:

#include <iostream>
#include <memory>

// ממשק האסטרטגיה
class PaymentStrategy {
public:
    virtual void pay(int amount) = 0;
    virtual ~PaymentStrategy() = default;
};

// אסטרטגיות קונקרטיות
class CreditCardPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " via Credit Card\n";
    }
};

class PayPalPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " via PayPal\n";
    }
};

// הקשר
class ShoppingCart {
    std::unique_ptr<PaymentStrategy> strategy;
public:
    void setPaymentMethod(std::unique_ptr<PaymentStrategy> s) {
        strategy = std::move(s);
    }
    
    void checkout(int total) {
        if (strategy) strategy->pay(total);
    }
};

int main() {
    ShoppingCart cart;
    
    cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
    cart.checkout(100);
    
    cart.setPaymentMethod(std::make_unique<PayPalPayment>());
    cart.checkout(50);
}

ShoppingCart לא יודע באיזו שיטת תשלום הוא משתמש — הוא פשוט קורא ל־pay() באסטרטגיה שנקבעה. אפשר להחליף אסטרטגיות בזמן ריצה באמצעות setPaymentMethod(), וכך להפוך את המערכת לגמישה וקלה להרחבה עם אפשרויות תשלום חדשות.

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

challenge icon

אתגר

קל

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

תארגנו את הקוד שלכם בשלושה קבצים:

  • ShippingStrategy.h: הגדירו את ממשק האסטרטגיה ואת אסטרטגיות המשלוח הקונקרטיות.

    צרו מחלקה מופשטת בשם ShippingStrategy עם מתודה וירטואלית טהורה בשם calculateCost(double weight) שמחזירה את עלות המשלוח כערך מסוג double, וכן עם מפרק וירטואלי.

    לאחר מכן, ממשו שלוש אסטרטגיות קונקרטיות:

    • GroundShipping: העלות היא 1.5 לכל יחידת משקל (weight * 1.5)
    • AirShipping: העלות היא 4.0 לכל יחידת משקל (weight * 4.0)
    • ExpressShipping: העלות היא 6.5 לכל יחידת משקל, בתוספת תשלום קבוע של 10.0 (weight * 6.5 + 10.0)
  • ShippingService.h: צרו מחלקת הקשר שמשתמשת באסטרטגיית משלוח.

    המחלקה ShippingService שלכם צריכה להחזיק std::unique_ptr<ShippingStrategy> כחבר פרטי. ממשו:

    • מתודה בשם setStrategy(std::unique_ptr<ShippingStrategy> strategy) לשינוי שיטת המשלוח
    • מתודה בשם calculateShipping(double weight) שמשתמשת באסטרטגיה הנוכחית כדי לחשב ולהחזיר את העלות

    אם לא הוגדרה אסטרטגיה כשקוראים ל־calculateShipping, החזירו 0.0.

  • main.cpp: הדגימו החלפה של אסטרטגיות בזמן ריצה.

    קראו שני קלטים:

    1. משקל החבילה (double)
    2. שיטת המשלוח: ground, air או express

    צרו ShippingService והגדירו את האסטרטגיה המתאימה לפי שיטת המשלוח שהתקבלה בקלט. חשבו והדפיסו את עלות המשלוח.

    לאחר מכן, עברו לאסטרטגיה אחרת (השתמשו ב־air אם הקלט לא היה air, אחרת השתמשו ב־ground) וחשבו שוב את העלות עבור אותו משקל. כך מודגם היתרון שבהחלפת אסטרטגיות בזמן ריצה.

    הדפיסו כל עלות בשורה נפרדת, עם ספרה אחת בדיוק אחרי הנקודה העשרונית, ובתחילתה שם השיטה:

    [Method]: $[cost]

לדוגמה, עבור הקלטים 5.0 ו־ground:

Ground: $7.5
Air: $20.0

עבור הקלטים 3.0 ו־express:

Express: $29.5
Air: $12.0

עבור הקלטים 10.0 ו־air:

Air: $40.0
Ground: $15.0

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

נסו בעצמכם

#include <iostream>
#include <string>
#include <iomanip>
#include <memory>
#include "ShippingStrategy.h"
#include "ShippingService.h"

int main() {
    double weight;
    std::string method;
    
    std::cin >> weight;
    std::cin >> method;
    
    // הגדר את הפלט לספרה אחת אחרי הנקודה העשרונית
    std::cout << std::fixed << std::setprecision(1);
    
    ShippingService service;
    
    // TODO: בהתאם לשיטת הקלט ("ground", "air" או "express"):
    // 1. הגדר את האסטרטגיה המתאימה בשירות
    // 2. חשב והדפס את העלות בפורמט: "[Method]: $[cost]"
    
    // TODO: עבור לאסטרטגיה אחרת:
    // - אם הקלט היה "air", עבור ל-GroundShipping
    // - אחרת, עבור ל-AirShipping
    // חשב והדפס את העלות החדשה
    
    return 0;
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר C++ אונליין