Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نمط قالب الطريقة

جزء من قسم 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) متشابهة.

challenge icon

التحدي

سهل

لنقم ببناء نظام Report Generator (منشئ تقارير) باستخدام نمط Template Method. ستقوم بإنشاء إطار عمل تتبع فيه أنواع مختلفة من التقارير (مثل تقارير المبيعات وتقارير المخزون) نفس عملية الإنشاء، ولكن كل نوع تقرير يخصص خطوات محددة. هذا سيناريو مثالي لنمط Template Method — حيث يظل سير العمل العام ثابتاً بينما تختلف الخطوات الفردية.

ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:

  • ReportGenerator.h: تحديد الفئة الأساسية المجردة (abstract base class) مع الـ template method.

    قم بإنشاء فئة ReportGenerator التي تحدد الهيكل العظمي لخوارزمية إنشاء التقارير. يجب أن تقوم الـ template method الخاصة بك generateReport() بتنفيذ هذه الخطوات بالترتيب:

    1. gatherData() — افتراضية بحتة (pure virtual)، يجب تنفيذها بواسطة الفئات الفرعية
    2. formatReport() — افتراضية بحتة (pure virtual)، يجب تنفيذها بواسطة الفئات الفرعية
    3. addHeader() — خطاف (hook) بسلوك افتراضي يطبع --- Report ---
    4. printReport() — افتراضية بحتة (pure virtual)، يجب تنفيذها بواسطة الفئات الفرعية

    تعمل الطريقة addHeader() كخطاف (hook) يمكن للفئات الفرعية تجاوزه اختيارياً لتخصيص الترويسة.

  • Reports.h: تنفيذ اثنين من منشئي التقارير الملموسين (concrete report generators).

    قم بإنشاء فئة SalesReport التي تقوم بـ:

    • gatherData() تطبع Gathering sales data from database
    • formatReport() تطبع Formatting sales figures
    • printReport() تطبع Sales Total: $[amount] حيث يتم تمرير amount إلى المنشئ (constructor)

    قم بإنشاء فئة InventoryReport التي تقوم بـ:

    • gatherData() تطبع Scanning inventory records
    • formatReport() تطبع Organizing inventory by category
    • addHeader() تتجاوز الخطاف (hook) لتطبع === Inventory Report ===
    • printReport() تطبع Items in stock: [count] حيث يتم تمرير count إلى المنشئ (constructor)

    يجب أن تقبل كلتا الفئتين قيمهما الخاصة (amount أو count) من خلال المنشئات الخاصة بهما.

  • main.cpp: عرض نمط Template Method.

    اقرأ مدخلين:

    1. مبلغ المبيعات (عدد صحيح)
    2. عدد المخزون (عدد صحيح)

    قم بإنشاء 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;
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming

14أنماط التصميم - الجزء الثاني

نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط