نمط قالب الطريقة
جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 99 من 104.
يحدد نمط طريقة القالب (Template Method pattern) الهيكل العام لخوارزمية في فئة أساسية، مما يسمح للفئات الفرعية بتجاوز خطوات معينة دون تغيير الهيكل العام للخوارزمية. وعلى عكس نمط الاستراتيجية (Strategy)، الذي يقوم بتبديل خوارزميات كاملة، يحافظ نمط طريقة القالب على الخوارزمية ثابتة مع السماح بتخصيص خطوات فردية.
تنفذ الفئة الأساسية (base class) طريقة القالب (الخوارزمية) وتستدعي دوالاً مجردة أو افتراضية يجب على الفئات الفرعية توفيرها:
#include <iostream>
class DataProcessor {
public:
// Template method - يحدد الهيكل الأساسي للخوارزمية
void process() {
loadData();
processData();
saveResults();
}
virtual ~DataProcessor() = default;
protected:
virtual void loadData() = 0; // يجب تنفيذه
virtual void processData() = 0; // يجب تنفيذه
// Hook - تجاوز اختياري مع سلوك افتراضي
virtual void saveResults() {
std::cout << "Saving to default location\n";
}
};
class CSVProcessor : public DataProcessor {
protected:
void loadData() override {
std::cout << "Loading CSV file\n";
}
void processData() override {
std::cout << "Parsing CSV data\n";
}
};
class JSONProcessor : public DataProcessor {
protected:
void loadData() override {
std::cout << "Loading JSON file\n";
}
void processData() override {
std::cout << "Parsing JSON data\n";
}
void saveResults() override {
std::cout << "Saving to cloud storage\n";
}
};
int main() {
CSVProcessor csv;
csv.process(); // يستخدم saveResults الافتراضية
JSONProcessor json;
json.process(); // يستخدم saveResults مخصصة
}تُعد الطريقة process() هي "الطريقة القالب" (template method) - فهي تحدد التسلسل الثابت للخطوات. تقوم الفئات الفرعية بتنفيذ loadData() و processData() بشكل مختلف، ولكن الترتيب لا يتغير أبداً. أما الطريقة saveResults() فهي عبارة عن "خطاف" (hook) - حيث تمتلك تنفيذاً افتراضياً يمكن للفئات الفرعية تجاوزه اختيارياً.
استخدم نمط القالب (Template Method) عندما يكون لديك خوارزمية ذات خطوات ثابتة ولكنك تحتاج إلى مرونة في عمليات محددة، أو عندما تريد تجنب تكرار الكود عبر فئات (classes) متشابهة.
التحدي
سهللنقم ببناء نظام Report Generator (منشئ تقارير) باستخدام نمط Template Method. ستقوم بإنشاء إطار عمل تتبع فيه أنواع مختلفة من التقارير (مثل تقارير المبيعات وتقارير المخزون) نفس عملية الإنشاء، ولكن كل نوع تقرير يخصص خطوات محددة. هذا سيناريو مثالي لنمط Template Method — حيث يظل سير العمل العام ثابتاً بينما تختلف الخطوات الفردية.
ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:
ReportGenerator.h: تحديد الفئة الأساسية المجردة (abstract base class) مع الـ template method.قم بإنشاء فئة
ReportGeneratorالتي تحدد الهيكل العظمي لخوارزمية إنشاء التقارير. يجب أن تقوم الـ template method الخاصة بكgenerateReport()بتنفيذ هذه الخطوات بالترتيب:gatherData()— افتراضية بحتة (pure virtual)، يجب تنفيذها بواسطة الفئات الفرعيةformatReport()— افتراضية بحتة (pure virtual)، يجب تنفيذها بواسطة الفئات الفرعيةaddHeader()— خطاف (hook) بسلوك افتراضي يطبع--- Report ---printReport()— افتراضية بحتة (pure virtual)، يجب تنفيذها بواسطة الفئات الفرعية
تعمل الطريقة
addHeader()كخطاف (hook) يمكن للفئات الفرعية تجاوزه اختيارياً لتخصيص الترويسة.Reports.h: تنفيذ اثنين من منشئي التقارير الملموسين (concrete report generators).قم بإنشاء فئة
SalesReportالتي تقوم بـ:gatherData()تطبعGathering sales data from databaseformatReport()تطبعFormatting sales figuresprintReport()تطبعSales Total: $[amount]حيث يتم تمرير amount إلى المنشئ (constructor)
قم بإنشاء فئة
InventoryReportالتي تقوم بـ:gatherData()تطبعScanning inventory recordsformatReport()تطبعOrganizing inventory by categoryaddHeader()تتجاوز الخطاف (hook) لتطبع=== Inventory Report ===printReport()تطبعItems in stock: [count]حيث يتم تمرير count إلى المنشئ (constructor)
يجب أن تقبل كلتا الفئتين قيمهما الخاصة (amount أو count) من خلال المنشئات الخاصة بهما.
main.cpp: عرض نمط Template Method.اقرأ مدخلين:
- مبلغ المبيعات (عدد صحيح)
- عدد المخزون (عدد صحيح)
قم بإنشاء
SalesReportبمبلغ المبيعات وقم بإنشائه. ثم قم بإنشاءInventoryReportبعدد المخزون وقم بإنشائه. اطبع سطراً فارغاً بين التقريرين لسهولة القراءة.
على سبيل المثال، مع المدخلات 15000 و 250:
Gathering sales data from database
Formatting sales figures
--- Report ---
Sales Total: $15000
Scanning inventory records
Organizing inventory by category
=== Inventory Report ===
Items in stock: 250مع المدخلات 8500 و 120:
Gathering sales data from database
Formatting sales figures
--- Report ---
Sales Total: $8500
Scanning inventory records
Organizing inventory by category
=== Inventory Report ===
Items in stock: 120لاحظ كيف يتبع كلا التقريرين نفس تسلسل الخطوات المحدد في generateReport() تماماً، ولكن كل منهما ينفذ تلك الخطوات بشكل مختلف. يوضح InventoryReport أيضاً تجاوز طريقة الخطاف (hook method) لتخصيص الترويسة، بينما يستخدم SalesReport الترويسة الافتراضية. هذا هو نمط Template Method قيد العمل — هيكل الخوارزمية مقفل في الفئة الأساسية، لكن التفاصيل مرنة.
ورقة مرجعية
يحدد نمط طريقة القالب (Template Method pattern) الهيكل العظمي لخوارزمية في فئة أساسية (base class)، مما يسمح للفئات الفرعية (subclasses) بتجاوز خطوات محددة دون تغيير الهيكل العام للخوارزمية.
تقوم الفئة الأساسية بتنفيذ طريقة القالب (الخوارزمية) وتستدعي طرقاً مجردة (abstract) أو افتراضية (virtual) يجب على الفئات الفرعية توفيرها:
#include <iostream>
class DataProcessor {
public:
// Template method - defines the algorithm skeleton
void process() {
loadData();
processData();
saveResults();
}
virtual ~DataProcessor() = default;
protected:
virtual void loadData() = 0; // Must be implemented
virtual void processData() = 0; // Must be implemented
// Hook - optional override with default behavior
virtual void saveResults() {
std::cout << "Saving to default location\n";
}
};
class CSVProcessor : public DataProcessor {
protected:
void loadData() override {
std::cout << "Loading CSV file\n";
}
void processData() override {
std::cout << "Parsing CSV data\n";
}
};
class JSONProcessor : public DataProcessor {
protected:
void loadData() override {
std::cout << "Loading JSON file\n";
}
void processData() override {
std::cout << "Parsing JSON data\n";
}
void saveResults() override {
std::cout << "Saving to cloud storage\n";
}
};تحدد طريقة القالب التسلسل الثابت للخطوات. تقوم الفئات الفرعية بتنفيذ الطرق المجردة بشكل مختلف، لكن الترتيب لا يتغير أبداً. تمتلك طرق الخطاف (Hook methods) تنفيذات افتراضية يمكن للفئات الفرعية تجاوزها اختيارياً.
استخدم نمط طريقة القالب عندما يكون لديك خوارزمية ذات خطوات ثابتة ولكنك تحتاج إلى مرونة في عمليات محددة، أو عندما تريد تجنب تكرار الكود عبر فئات متشابهة.
جرّب بنفسك
#include <iostream>
#include "Reports.h"
using namespace std;
int main() {
// قراءة المدخلات
int salesAmount;
int inventoryCount;
cin >> salesAmount;
cin >> inventoryCount;
// TODO: قم بإنشاء SalesReport باستخدام salesAmount وقم بتوليده
// TODO: اطبع سطراً فارغاً بين التقارير
// TODO: قم بإنشاء InventoryReport باستخدام inventoryCount وقم بتوليده
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
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مراجعة - الحاويات العامة