Menu
Coddy logo textTech

פונקציות וירטואליות טהורות

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

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

class Shape {
public:
    virtual double area() = 0;  // פונקציה וירטואלית טהורה
    virtual ~Shape() = default;
};

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

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    
    double area() override {
        return 3.14159 * radius * radius;
    }
};

class Rectangle : public Shape {
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    
    double area() override {
        return width * height;
    }
};

גם Circle וגם Rectangle חייבות לממש את area(), משום שהיא וירטואלית טהורה ב-Shape. אם מחלקה נגזרת לא מממשת את כל הפונקציות הווירטואליות הטהורות, גם היא הופכת למחלקה אבסטרקטית ואי אפשר ליצור ממנה מופעים.

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

challenge icon

אתגר

קל

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

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

  • PaymentProcessor.h: הגדירו מחלקה מופשטת בשם PaymentProcessor שתשמש כתבנית לכל שיטות התשלום. למחלקה הזו צריכים להיות:
    • איבר מוגן std::string accountId
    • בנאי שמאתחל את מזהה החשבון
    • מתודה וירטואלית טהורה processPayment(double amount): כל סוג תשלום חייב לממש אותה באופן שונה
    • מתודה וירטואלית טהורה getProcessorName() שמחזירה std::string
    • הורס וירטואלי
  • PaymentMethods.h: ממשו שני מעבדי תשלומים קונקרטיים שיורשים מ-PaymentProcessor:

    CreditCardProcessor:

    • איבר פרטי double feePercentage (שיעור עמלת העסקה)
    • בנאי שמקבל מזהה חשבון ושיעור עמלה
    • ממשו את processPayment() כדי לחשב את העמלה (amount * feePercentage / 100), ואז להדפיס:
      Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>)
    • ממשו את getProcessorName() כך שתחזיר "CreditCard"

    BankTransferProcessor:

    • איבר פרטי std::string bankName
    • בנאי שמקבל מזהה חשבון ושם בנק
    • ממשו את processPayment() כדי להדפיס:
      Bank Transfer [<accountId>] via <bankName>: Transferred $<amount>
    • ממשו את getProcessorName() כך שתחזיר "BankTransfer"
  • main.cpp: קראו ארבעה קלטים (כל אחד בשורה נפרדת):
    1. מזהה חשבון של כרטיס אשראי
    2. שיעור העמלה של כרטיס האשראי (double)
    3. מזהה חשבון בנק
    4. שם הבנק

    צרו את שני מעבדי התשלומים ושמרו אותם במערך של מצביעים מסוג PaymentProcessor*. עבדו תשלום בסך 100.0 באמצעות כל אחד מהמעבדים, והציגו את שם המעבד לפני כל עסקה:

    Processing with <processorName>:
    <processPayment output>

    הדפיסו שורה ריקה בין המעבדים. בסיום, נקו את האובייקטים שהוקצו דינמית.

לדוגמה, עבור הקלטים CC-4521, 2.5, BA-7890 ו-National Bank:

Processing with CreditCard:
Credit Card [CC-4521]: Charged $100 (Fee: $2.5)

Processing with BankTransfer:
Bank Transfer [BA-7890] via National Bank: Transferred $100

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

נסו בעצמכם

#include <iostream>
#include <string>
#include "PaymentMethods.h"

using namespace std;

int main() {
    // קראו את הקלטים
    string ccAccountId;
    double feePercentage;
    string bankAccountId;
    string bankName;
    
    getline(cin, ccAccountId);
    cin >> feePercentage;
    cin.ignore();
    getline(cin, bankAccountId);
    getline(cin, bankName);
    
    // TODO: צרו מערך של מצביעי PaymentProcessor* עם 2 איברים
    
    // TODO: צרו אובייקטים מסוג CreditCardProcessor ו-BankTransferProcessor
    // ואחסנו אותם במערך
    
    // TODO: עברו על המערך, ועבור כל מעבד:
    // 1. הדפיסו "Processing with <processorName>:"
    // 2. קראו ל-processPayment עם הסכום 100.0
    // 3. הדפיסו שורה ריקה בין המעבדים (לא אחרי האחרון)
    
    // TODO: שחררו את האובייקטים שהוקצו דינמית
    
    return 0;
}
quiz iconבחנו את עצמכם

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

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

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