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

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

جزء من قسم البرمجة كائنية التوجه في رحلة C++ على Coddy. الدرس 99 من 104.

يحدد نمط Template Method الهيكل الأساسي لـ algorithm في base class، مما يتيح للفئات الفرعية تجاوز خطوات محددة دون تغيير البنية العامة لـ algorithm. وعلى عكس Strategy، الذي يستبدل algorithms كاملة، يحافظ Template Method على ثبات algorithm مع السماح بتخصيص الخطوات الفردية.

ينفّذ base class طريقة القالب (algorithm) ويستدعي طرقًا abstract أو virtual يجب على الفئات الفرعية توفيرها:

#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() هو أسلوب القالب - فهو يحدد التسلسل الثابت للخطوات. تنفذ الفئات الفرعية loadData() وprocessData() بطرق مختلفة، لكن الترتيب لا يتغير أبدًا. أما أسلوب saveResults() فهو "خطاف" - إذ يملك تنفيذًا افتراضيًا يمكن للفئات الفرعية تجاوزه اختياريًا.

استخدم Template Method عندما تكون لديك خوارزمية ذات خطوات ثابتة، لكنك تحتاج إلى المرونة في عمليات محددة، أو عندما تريد تجنّب تكرار التعليمات البرمجية بين الفئات المتشابهة.

challenge icon

التحدي

سهل

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

ستنظم شيفرتك عبر ثلاثة ملفات:

  • ReportGenerator.h: عرّف الفئة الأساسية المجردة التي تحتوي على طريقة القالب.

    أنشئ فئة ReportGenerator تعرّف الهيكل العام لـ algorithm توليد التقرير. يجب أن تنفذ طريقة القالب generateReport() هذه الخطوات بالترتيب:

    1. gatherData(): افتراضية خالصة، ويجب أن تُنفَّذ بواسطة الفئات الفرعية
    2. formatReport(): افتراضية خالصة، ويجب أن تُنفَّذ بواسطة الفئات الفرعية
    3. addHeader(): Hook بسلوك افتراضي يطبع --- Report ---
    4. printReport(): افتراضية خالصة، ويجب أن تُنفَّذ بواسطة الفئات الفرعية

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

  • Reports.h: نفّذ مولدي تقارير Concrete.

    أنشئ فئة 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) من خلال constructor الخاص بكل منهما.

  • 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 لتخصيص الترويسة، بينما يستخدم SalesReport السلوك الافتراضي. هذا هو نمط Template Method قيد التنفيذ: يكون هيكل algorithm مثبتًا في الفئة الأساسية، لكن التفاصيل تظل مرنة.

جرّب بنفسك

#include <iostream>
#include "Reports.h"

using namespace std;

int main() {
    // اقرأ المدخلات
    int salesAmount;
    int inventoryCount;
    cin >> salesAmount;
    cin >> inventoryCount;
    
    // TODO: أنشئ SalesReport باستخدام salesAmount وقم بتوليده
    
    // TODO: اطبع سطرًا فارغًا بين التقارير
    
    // TODO: Create an InventoryReport with inventoryCount and generate it
    
    return 0;
}
quiz iconاختبر نفسك

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

جميع دروس البرمجة كائنية التوجه

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

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

تدرّب بنفسك: مترجم C++ عبر الإنترنت