Menu
Coddy logo textTech

Factory ו־Abstract Factory

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

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

מפעל פשוט משתמש בשיטה שמחזירה סוגים נגזרים שונים בהתאם לקלט:

#include <iostream>
#include <memory>

class Button {
public:
    virtual void render() = 0;
    virtual ~Button() = default;
};

class WindowsButton : public Button {
public:
    void render() override { std::cout << "Windows button\n"; }
};

class MacButton : public Button {
public:
    void render() override { std::cout << "Mac button\n"; }
};

// פונקציית מפעל
std::unique_ptr<Button> createButton(const std::string& os) {
    if (os == "windows") return std::make_unique<WindowsButton>();
    if (os == "mac") return std::make_unique<MacButton>();
    return nullptr;
}

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

class Checkbox {
public:
    virtual void check() = 0;
    virtual ~Checkbox() = default;
};

class WindowsCheckbox : public Checkbox {
public:
    void check() override { std::cout << "Windows checkbox\n"; }
};

class MacCheckbox : public Checkbox {
public:
    void check() override { std::cout << "Mac checkbox\n"; }
};

// מפעל מופשט
class GUIFactory {
public:
    virtual std::unique_ptr<Button> createButton() = 0;
    virtual std::unique_ptr<Checkbox> createCheckbox() = 0;
    virtual ~GUIFactory() = default;
};

class WindowsFactory : public GUIFactory {
public:
    std::unique_ptr<Button> createButton() override {
        return std::make_unique<WindowsButton>();
    }
    std::unique_ptr<Checkbox> createCheckbox() override {
        return std::make_unique<WindowsCheckbox>();
    }
};

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

challenge icon

אתגר

קל

בואו נבנה מערכת ליצירת מסמכים באמצעות דפוסי התכנון Factory ו־Abstract Factory. תיצור מערכת שיכולה ליצור סוגים שונים של מסמכים (דוחות וחשבוניות) בפורמטי פלט שונים (PDF ו־HTML), וכך להדגים כיצד מפעלים מנתקים את יצירת האובייקטים מקוד הלקוח.

תארגן את הקוד שלך בארבעה קבצים:

  • Document.h: הגדר את היררכיית המוצרים של המסמכים שלך.

    צור מחלקת בסיס מופשטת Document עם מתודה וירטואלית טהורה generate() שמחזירה מחרוזת המתארת את המסמך שנוצר, ועם מפרק וירטואלי.

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

    • PDFReport: generate() מחזירה "Generating PDF Report"
    • HTMLReport: generate() מחזירה "Generating HTML Report"
    • PDFInvoice: generate() מחזירה "Generating PDF Invoice"
    • HTMLInvoice: generate() מחזירה "Generating HTML Invoice"
  • DocumentFactory.h: ממש הן פונקציית מפעל פשוטה והן מפעל מופשט.

    ראשית, צור פונקציית מפעל פשוטה בשם createDocument שמקבלת שתי מחרוזות: סוג מסמך ("report" או "invoice") ופורמט ("pdf" או "html"). עליה להחזיר std::unique_ptr<Document> שמצביע למסמך הקונקרטי המתאים. החזר nullptr עבור צירופים לא תקינים.

    לאחר מכן, צור מחלקת מפעל מופשטת בשם DocumentFactory עם שתי מתודות וירטואליות טהורות:

    • createReport(): מחזירה std::unique_ptr<Document>
    • createInvoice(): מחזירה std::unique_ptr<Document>

    ממש שני מפעלים קונקרטיים:

    • PDFFactory: יוצרת PDFReport ו־PDFInvoice
    • HTMLFactory: יוצרת HTMLReport ו־HTMLInvoice
  • DocumentService.h: צור שירות שמשתמש במפעל המופשט.

    הגדר מחלקה DocumentService שמקבלת std::unique_ptr<DocumentFactory> בבנאי שלה. צריכה להיות לה מתודה בשם generateAllDocuments() שמשתמשת במפעל כדי ליצור גם דוח וגם חשבונית, ואז מדפיסה את התוצאה של קריאה ל־generate() על כל אחד מהם, שורה אחת לכל תוצאה.

  • main.cpp: הדגם את שתי גישות המפעל.

    קרא שתי קלטים:

    1. סוג מסמך (report או invoice)
    2. פורמט (pdf או html)

    ראשית, השתמש בפונקציית המפעל הפשוטה כדי ליצור מסמך על סמך הקלט. הדפס את התוצאה של קריאה ל־generate() עליו.

    לאחר מכן, הדפס שורה ריקה.

    בשלב הבא, צור DocumentService עם המפעל המתאים לפי קלט הפורמט (השתמש ב־PDFFactory עבור pdf, וב־HTMLFactory עבור html). קרא ל־generateAllDocuments() כדי להראות כיצד המפעל המופשט יוצר משפחה של מסמכים קשורים.

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

Generating PDF Report

Generating PDF Report
Generating PDF Invoice

עבור הקלטים invoice ו־html:

Generating HTML Invoice

Generating HTML Report
Generating HTML Invoice

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

נסו בעצמכם

#include <iostream>
#include <string>
#include <memory>
#include "Document.h"
#include "DocumentFactory.h"
#include "DocumentService.h"

using namespace std;

int main() {
    // קראו את הקלטים
    string documentType;
    string format;
    cin >> documentType;
    cin >> format;
    
    // TODO: השתמשו בפונקציית המפעל הפשוט כדי ליצור מסמך
    // בהתבסס על הקלטים documentType ו-format
    // הדפיסו את התוצאה של הקריאה ל-generate() עליו
    
    // הדפיסו שורה ריקה
    cout << endl;
    
    // TODO: צרו DocumentService עם המפעל המתאים
    // בהתבסס על הקלט format (‏PDFFactory עבור "pdf", ‏HTMLFactory עבור "html")
    // קראו ל-generateAllDocuments() כדי להדגים את המפעל המופשט
    
    return 0;
}
quiz iconבחנו את עצמכם

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

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

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