תבנית 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 כשיש לכם כמה אלגוריתמים למשימה מסוימת ואתם רוצים לעבור ביניהם באופן דינמי, או כשאתם רוצים להימנע מהצהרות תנאי לבחירת אופן הפעולה.
אתגר
קלבואו נבנה מחשבון משלוחים שמשתמש בתבנית 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: הדגימו החלפה של אסטרטגיות בזמן ריצה.קראו שני קלטים:
- משקל החבילה (double)
- שיטת המשלוח:
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;
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP
קבצים חיצונייםבנייה וקומפילציה של C++קובצי Header וקובצי מקורמרחבי שמות והיקףמבוא ל-OOP ב-C++מחלקות לעומת אובייקטיםמצביע thisמתודות (פונקציות איבר)תכונות (חברי נתונים)יסודות הבנאים וההורסיםסיכום – מחשבון פשוט4מאפייני מחלקה
חברי מופע לעומת חברים סטטייםגטרים וסטריםפונקציות איבר קבועותמילת המפתח mutableמתודות ומשתנים סטטייםפונקציות ומחלקות ידידותסיכום – מנהל חשבונות בנק7ירושה
ירושה בסיסיתרמות גישה בירושהסדר הקריאות לבנאי ולמפרקדריסת מתודותפונקציות וירטואליות ו־VTableירושה מרובהירושה וירטואליתסיכום – היררכיית עובדים10סקירה כללית של STL
סקירה כללית והפילוסופיה של STLקונטיינרים של STLאיטרטוריםאלגוריתמים של STLפונקטורים וביטויי למבדהסיכום – תדירות מילים13תבניות עיצוב חלק 1
מבוא לתבניות עיצובתבנית SingletonFactory ו־Abstract Factoryתבנית Builderתבנית Observerתבנית Strategy2ניהול זיכרון
זיכרון Stack לעומת Heapמצביעים והפניותזיכרון דינמי (new/delete)מצביעים חכמים ב-C++RAII ב-C++סיכום – מנהל מערכים דינמיים5כימוס
מצייני גישה ב-C++מצייני גישה לעומקהסתרת מידעמבנה לעומת מחלקהמחלקות מקוננות ופנימיותסיכום – מערכת רשומות תלמידים8רב־צורתיות
רב־צורתיות בזמן קומפילציה לעומת בזמן ריצההעמסת פונקציותפונקציות וירטואליות — מבט נוסףפונקציות וירטואליות טהורותמחלקות מופשטותתכנון ממשקים ב־C++המרה דינמית ו־RTTIסיכום — מחשבון צורות3בנאים והורסים
בנאי ברירת מחדלבנאי עם פרמטריםבנאי העתקהבנאי העברהרשימות אתחול בבנאיםבנאים מאציליםמעמיקים בנושא הורסיםכלל השלושה / החמישה / האפססיכום – מחלקת String6העמסת אופרטורים
מבוא להעמסת אופרטוריםהעמסת אופרטורים אריתמטייםהעמסת אופרטורי השוואהאופרטורי זרםהעמסת אופרטור השמההעמסת האופרטורים [] ו־()אופרטורים להמרת טיפוסיםסיכום – מחלקת Matrix9תבניות
תבניות פונקציהתבניות מחלקההתמחות תבניותתבניות ארגומנטים משתנותיסודות SFINAE ותכונות טיפוסיםסיכום – מכל גנריתרגלו בעצמכם: קומפיילר C++ אונליין