תבנית העיצוב
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 98 מתוך 104.
תבנית הדקורטור מאפשרת לך להוסיף התנהגויות חדשות לאובייקטים באופן דינמי על ידי עטיפתם באובייקטים מיוחדים שנקראים דקורטורים. בניגוד לירושה, שמוסיפה התנהגות בזמן הקומפילציה, דקורטורים מאפשרים לך להרחיב את הפונקציונליות בזמן הריצה בלי לשנות את המחלקה המקורית.
התבנית פועלת בכך שגם האובייקט המקורי וגם העוטפים מממשים את אותו ממשק. כל עוטף מחזיק הפניה לרכיב ומוסיף התנהגות משלו לפני שהוא מעביר את הביצוע לאובייקט העטוף או אחריו:
#include <iostream>
#include <memory>
// ממשק רכיב
class Coffee {
public:
virtual std::string getDescription() const = 0;
virtual double getCost() const = 0;
virtual ~Coffee() = default;
};
// רכיב קונקרטי
class SimpleCoffee : public Coffee {
public:
std::string getDescription() const override { return "Coffee"; }
double getCost() const override { return 2.0; }
};
// דקורטור בסיסי
class CoffeeDecorator : public Coffee {
protected:
std::unique_ptr<Coffee> coffee;
public:
CoffeeDecorator(std::unique_ptr<Coffee> c) : coffee(std::move(c)) {}
};
// דקורטורים קונקרטיים
class MilkDecorator : public CoffeeDecorator {
public:
MilkDecorator(std::unique_ptr<Coffee> c) : CoffeeDecorator(std::move(c)) {}
std::string getDescription() const override {
return coffee->getDescription() + ", Milk";
}
double getCost() const override { return coffee->getCost() + 0.5; }
};
class SugarDecorator : public CoffeeDecorator {
public:
SugarDecorator(std::unique_ptr<Coffee> c) : CoffeeDecorator(std::move(c)) {}
std::string getDescription() const override {
return coffee->getDescription() + ", Sugar";
}
double getCost() const override { return coffee->getCost() + 0.2; }
};אפשר לערום עיצובים — כל אחד עוטף את הקודם, ובונה פונקציונליות שכבה אחר שכבה:
int main() {
std::unique_ptr<Coffee> order = std::make_unique<SimpleCoffee>();
order = std::make_unique<MilkDecorator>(std::move(order));
order = std::make_unique<SugarDecorator>(std::move(order));
std::cout << order->getDescription() << ": $" << order->getCost();
// פלט: Coffee, Milk, Sugar: $2.7
}השתמשו ב־Decorator כשצריך להוסיף אחריויות לאובייקטים באופן דינמי בלי להשפיע על אובייקטים אחרים, או כשניסיון להרחיב את הפונקציונליות באמצעות יצירת תת־מחלקות יגרום להתפוצצות במספר המחלקות.
אתגר
קלבואו נבנה מערכת להזמנת פיצה באמצעות תבנית העיצוב Decorator. תיצרו מערכת גמישה שבה לקוחות יכולים להתחיל מפיצה בסיסית ולהוסיף תוספות שונות: כל תוספת עוטפת את ההזמנה הקודמת ומוסיפה לה עלות ותיאור משלה. זהו מקרה שימוש מושלם במחלקות עוטפות: אפשר לערום תוספות בכל שילוב, בלי ליצור המון מחלקות משנה.
תארגנו את הקוד בשלושה קבצים:
Pizza.h: הגדירו את ממשק הרכיב ואת מחלקת הפיצה הבסיסית.צרו מחלקה מופשטת
Pizzaעם המתודות הווירטואליות הטהורותgetDescription()(מחזירה מחרוזת) ו-getCost()(מחזירה מספר מסוג double), וכן מפרק וירטואלי.לאחר מכן, צרו מחלקה קונקרטית בשם
PlainPizzaשמייצגת את הפיצה הבסיסית. עליה להחזיר"Pizza"כתיאור שלה, ולעלותה הבסיסית להיות8.0.ToppingDecorator.h: בנו את מחלקת הבסיס של העוטף ואת העוטפים הקונקרטיים של התוספות.צרו מחלקה בשם
ToppingDecoratorשיורשת מ-Pizzaומחזיקהstd::unique_ptr<Pizza>לרכיב העטוף. מחלקה זו משמשת בסיס לכל העוטפים של התוספות.ממשו שלושה עוטפים קונקרטיים של תוספות:
CheeseTopping: מוסיפה", Cheese"לתיאור ו-1.5לעלותPepperoniTopping: מוסיפה", Pepperoni"לתיאור ו-2.0לעלותMushroomTopping: מוסיפה", Mushrooms"לתיאור ו-1.0לעלות
כל עוטף צריך להעביר את הקריאה לפיצה העטופה ואז להוסיף את התרומה שלו.
main.cpp: צרו הזמנת פיצה מותאמת אישית על סמך קלט המשתמש.קראו שלושה קלטים (כל אחד מהם יהיה
yesאוno):- להוסיף גבינה?
- להוסיף פפרוני?
- להוסיף פטריות?
התחילו עם
PlainPizza, ואז עטפו אותה בעוטפי התוספות המתאימים בהתאם לקלטים (בסדר הזה: גבינה, פפרוני, פטריות). לבסוף, הדפיסו את פרטי ההזמנה בפורמט הבא:Order: [description] Total: $[cost]
לדוגמה, עם הקלטים yes, yes, no:
Order: Pizza, Cheese, Pepperoni
Total: $11.5עם הקלטים yes, no, yes:
Order: Pizza, Cheese, Mushrooms
Total: $10.5עם הקלטים no, no, no:
Order: Pizza
Total: $8עם הקלטים yes, yes, yes:
Order: Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.5שימו לב שכל עוטף עוטף את העוטף הקודם, וכך בונה בהדרגה הן את התיאור והן את העלות. אפשר להוסיף בקלות תוספות חדשות באמצעות יצירת מחלקות עוטפות חדשות, בלי לשנות את הקוד הקיים של הפיצה או התוספות: זה היופי בתבנית העיצוב Decorator.
נסו בעצמכם
#include <iostream>
#include <string>
#include <memory>
#include "Pizza.h"
#include "ToppingDecorator.h"
using namespace std;
int main() {
// קראו שלושה קלטים (כן או לא)
string addCheese, addPepperoni, addMushrooms;
cin >> addCheese;
cin >> addPepperoni;
cin >> addMushrooms;
// TODO: התחילו עם PlainPizza באמצעות std::unique_ptr
// TODO: אם addCheese הוא "yes", עטפו את הפיצה ב-CheeseTopping
// TODO: אם addPepperoni הוא "yes", עטפו את הפיצה ב-PepperoniTopping
// TODO: אם addMushrooms הוא "yes", עטפו את הפיצה ב-MushroomTopping
// TODO: הדפיסו את ההזמנה בפורמט:
// הזמנה: [תיאור]
// סה״כ: $[עלות]
return 0;
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP
קבצים חיצונייםבנייה וקומפילציה של C++קובצי Header וקובצי מקורמרחבי שמות והיקףמבוא ל-OOP ב-C++מחלקות לעומת אובייקטיםמצביע thisמתודות (פונקציות איבר)תכונות (חברי נתונים)יסודות הבנאים וההורסיםסיכום – מחשבון פשוט4מאפייני מחלקה
חברי מופע לעומת חברים סטטייםגטרים וסטריםפונקציות איבר קבועותמילת המפתח mutableמתודות ומשתנים סטטייםפונקציות ומחלקות ידידותסיכום – מנהל חשבונות בנק7ירושה
ירושה בסיסיתרמות גישה בירושהסדר הקריאות לבנאי ולמפרקדריסת מתודותפונקציות וירטואליות ו־VTableירושה מרובהירושה וירטואליתסיכום – היררכיית עובדים2ניהול זיכרון
זיכרון Stack לעומת Heapמצביעים והפניותזיכרון דינמי (new/delete)מצביעים חכמים ב-C++RAII ב-C++סיכום – מנהל מערכים דינמיים5כימוס
מצייני גישה ב-C++מצייני גישה לעומקהסתרת מידעמבנה לעומת מחלקהמחלקות מקוננות ופנימיותסיכום – מערכת רשומות תלמידים8רב־צורתיות
רב־צורתיות בזמן קומפילציה לעומת בזמן ריצההעמסת פונקציותפונקציות וירטואליות — מבט נוסףפונקציות וירטואליות טהורותמחלקות מופשטותתכנון ממשקים ב־C++המרה דינמית ו־RTTIסיכום — מחשבון צורות11מושגי OOP מתקדמים
הרכבה לעומת הורשהMixins באמצעות CRTPאידיום Pimplמחיקת טיפוסיםמחלקות Enum וטיפוסיות חזקהטיפול בחריגות ב-OOPהיררכיות חריגות מותאמות אישית14תבניות עיצוב חלק 2
תבנית הפקודהתבנית המתאםתבנית העיצובתבנית השיטה התבניתיתתבנית המצבתבנית המורכבRAII כתבנית3בנאים והורסים
בנאי ברירת מחדלבנאי עם פרמטריםבנאי העתקהבנאי העברהרשימות אתחול בבנאיםבנאים מאציליםמעמיקים בנושא הורסיםכלל השלושה / החמישה / האפססיכום – מחלקת String6העמסת אופרטורים
מבוא להעמסת אופרטוריםהעמסת אופרטורים אריתמטייםהעמסת אופרטורי השוואהאופרטורי זרםהעמסת אופרטור השמההעמסת האופרטורים [] ו־()אופרטורים להמרת טיפוסיםסיכום – מחלקת Matrix9תבניות
תבניות פונקציהתבניות מחלקההתמחות תבניותתבניות ארגומנטים משתנותיסודות SFINAE ותכונות טיפוסיםסיכום – מכל גנריתרגלו בעצמכם: קומפיילר C++ אונליין