نمط 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;
}تأخذ Abstract Factory هذا المفهوم إلى أبعد من ذلك من خلال إنشاء عائلات من الكائنات المرتبطة. وبدلاً من طريقة إنشاء واحدة، لديك واجهة مصنع تحتوي على عدة طرق للإنشاء:
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 عندما تحتاج إلى ضمان عمل الكائنات المرتبطة معًا بصورة متسقة.
التحدي
سهللنَبْنِ نظام Document Generator باستخدام نمطي Factory وAbstract Factory. ستنشئ نظامًا يمكنه إنشاء أنواع مختلفة من المستندات (التقارير والفواتير) بتنسيقات إخراج مختلفة (PDF وHTML)، موضحًا كيف تفصل المصانع إنشاء الكائنات عن كود العميل.
ستنظّم الكود عبر أربعة ملفات:
Document.h: عرّف تسلسل هرمي لمنتجات المستندات.أنشئ فئة أساسية abstract
Documentمع طريقة افتراضية محضةgenerate()تُرجع سلسلة تصف المستند المُنشأ، ومُدمّر افتراضي.ثم أنشئ أربع فئات مستندات concrete:
PDFReport: تُرجعgenerate()القيمة"Generating PDF Report"HTMLReport: تُرجعgenerate()القيمة"Generating HTML Report"PDFInvoice: تُرجعgenerate()القيمة"Generating PDF Invoice"HTMLInvoice: تُرجعgenerate()القيمة"Generating HTML Invoice"
DocumentFactory.h: نفّذ كلًا من دالة مصنع بسيطة ومصنع abstract.أولًا، أنشئ دالة مصنع بسيطة تُسمى
createDocumentوتأخذ سلسلتين: نوع مستند ("report"أو"invoice") وتنسيقًا ("pdf"أو"html"). يجب أن تُرجعstd::unique_ptr<Document>يشير إلى المستند concrete المناسب. أعدnullptrللتوليفات غير الصالحة.ثم أنشئ فئة مصنع abstract تُسمى
DocumentFactoryمع طريقتين افتراضيتين محضتين:createReport(): تُرجعstd::unique_ptr<Document>createInvoice(): تُرجعstd::unique_ptr<Document>
نفّذ مصنعين concrete:
PDFFactory: ينشئPDFReportوPDFInvoiceHTMLFactory: ينشئHTMLReportوHTMLInvoice
DocumentService.h: أنشئ خدمة تستخدم المصنع abstract.عرّف فئة
DocumentServiceتأخذstd::unique_ptr<DocumentFactory>في مُنشئها. يجب أن تحتوي على طريقة تُسمىgenerateAllDocuments()تستخدم المصنع لإنشاء تقرير وفاتورة، ثم تطبع نتيجة استدعاءgenerate()على كل منهما، كل نتيجة في سطر.main.cpp: وضّح طريقتَي المصنع كلتيهما.اقرأ مدخلين:
- نوع مستند (
reportأوinvoice) - تنسيق (
pdfأوhtml)
أولًا، استخدم دالة المصنع البسيطة لإنشاء مستند استنادًا إلى المدخلات. اطبع نتيجة استدعاء
generate()عليه.ثم اطبع سطرًا فارغًا.
بعد ذلك، أنشئ
DocumentServiceباستخدام المصنع appropriate استنادًا إلى مدخل التنسيق (استخدمPDFFactoryمعpdf، وHTMLFactoryمعhtml). استدعِgenerateAllDocuments()لتوضيح كيف ينشئ المصنع abstract عائلة من المستندات المرتبطة.- نوع مستند (
على سبيل المثال، مع المدخلين report وpdf:
Generating PDF Report
Generating PDF Report
Generating PDF Invoiceومع المدخلين invoice وhtml:
Generating HTML Invoice
Generating HTML Report
Generating HTML Invoiceلاحظ كيف ينشئ المصنع البسيط مستندًا واحدًا استنادًا إلى المعلمتين، بينما يضمن المصنع abstract أن جميع المستندات التي تنشئها خدمة ما تشترك في التنسيق نفسه. هذه هي الفائدة الأساسية من 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;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات الـ OOP
الملفات الخارجيةبناء وتجميع (Compilation) لغة C++ملفات الـ Header وملفات الـ Sourceالـ Namespaces والـ Scopeمقدمة في الـ OOP في C++الـ Classes مقابل الـ Objectsالمؤشر 'this'الـ Methods (الدوال الأعضاء)الـ Attributes (بيانات الأعضاء)أساسيات الـ Ctors والـ Dtorsمراجعة - آلة حاسبة بسيطة4خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticدوال الـ Getters والـ Settersدوال الأعضاء الثابتة (Const)الكلمة المفتاحية Mutableالدوال والمتغيرات الساكنة (Static)الدوال والفئات الصديقة (Friend)مراجعة - مدير الحساب البنكي7الوراثة
الوراثة الأساسيةمستويات الوصول في الوراثةترتيب استدعاء الـ Ctor والـ Dtorإعادة تعريف الدوال (Method Overriding)الدوال الافتراضية والـ VTableالوراثة المتعددةالوراثة الافتراضيةمراجعة - هيكلية الموظفين10نظرة عامة على STL
نظرة عامة وفلسفة STLحاويات STLالـ Iteratorsخوارزميات STLالـ Functors وتعبيرات Lambdaمراجعة - تكرار الكلمات13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factory و Abstract Factoryنمط Builderنمط Observerنمط Strategy2إدارة الذاكرة
ذاكرة Stack مقابل Heapالمؤشرات والمراجعالذاكرة الديناميكية (new/delete)المؤشرات الذكية في C++RAII في C++مراجعة - مدير المصفوفات الديناميكية5التغليف (Encapsulation)
محددات الوصول في C++محددات الوصول بشكل متعمقإخفاء المعلوماتStruct مقابل Classالأصناف المتداخلة والداخليةمراجعة - نظام سجلات الطلاب8تعدد الأشكال (Polymorphism)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالتحميل الزائد للدوال (Function Overloading)مراجعة الدوال الافتراضية (Virtual Functions)الدوال الافتراضية البحتة (Pure Virtual Functions)الأصناف المجردة (Abstract Classes)تصميم الواجهات (Interface) في C++التحويل الديناميكي (Dynamic Casting) و RTTIملخص - حاسبة الأشكال11مفاهيم OOP المتقدمة
التركيب مقابل الوراثةالـ Mixins عبر CRTPنمط Pimplمحو النوع (Type Erasure)أصناف Enum والأنواع القويةمعالجة الاستثناءات في OOPتسلسلات الاستثناءات المخصصة14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط3المنشئات والموادم
المنشئ الافتراضيالمنشئ ذو المعاملاتمنشئ النسخمنشئ النقلقوائم تهيئة المنشئالمنشئات المفوضةتعمق في الموادمقاعدة الثلاثة / الخمسة / الصفرمراجعة - فئة String6تحميل العوامل (Operator Overloading)
مقدمة في تحميل العواملتحميل العوامل الحسابيةتحميل عوامل المقارنةعوامل Streamتحميل عامل التعيينتحميل العوامل [] و ()عوامل تحويل النوعمراجعة - Matrix Class9القوالب
قوالب الدوالقوالب الأصنافتخصيص القوالبالقوالب المتغيرةأساسيات SFINAE و Type Traitsمراجعة - الحاويات العامةتدرّب بنفسك: مترجم C++ عبر الإنترنت