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

نمط Strategy

جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 95 من 104.

يحدد نمط الاستراتيجية (Strategy pattern) عائلة من الخوارزميات، ويغلف كل واحدة منها، ويجعلها قابلة للتبديل. يتيح لك ذلك تغيير سلوك الكائن في وقت التشغيل دون تعديل الكود الخاص به - حيث تختلف الخوارزمية بشكل مستقل عن العملاء الذين يستخدمونها.

يتكون النمط من ثلاثة أجزاء: واجهة الاستراتيجية (Strategy interface) التي تعلن عن طريقة الخوارزمية، والاستراتيجيات الملموسة (Concrete Strategies) التي تنفذ تنويعات مختلفة، وسياق (Context) يستخدم استراتيجية:

#include <iostream>
#include <memory>

// Strategy interface (واجهة الاستراتيجية)
class PaymentStrategy {
public:
    virtual void pay(int amount) = 0;
    virtual ~PaymentStrategy() = default;
};

// Concrete strategies (الاستراتيجيات الملموسة)
class CreditCardPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " via Credit Card\n";
    }
};

class PayPalPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " via PayPal\n";
    }
};

// Context (السياق)
class ShoppingCart {
    std::unique_ptr<PaymentStrategy> strategy;
public:
    void setPaymentMethod(std::unique_ptr<PaymentStrategy> s) {
        strategy = std::move(s);
    }
    
    void checkout(int total) {
        if (strategy) strategy->pay(total);
    }
};

int main() {
    ShoppingCart cart;
    
    cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
    cart.checkout(100);
    
    cart.setPaymentMethod(std::make_unique<PayPalPayment>());
    cart.checkout(50);
}

لا يعرف ShoppingCart أي وسيلة دفع يستخدمها - فهو ببساطة يستدعي pay() على أي استراتيجية تم تعيينها. يمكنك تبديل الاستراتيجيات أثناء وقت التشغيل (runtime) باستخدام setPaymentMethod()، مما يجعل النظام مرناً وسهل التوسيع بخيارات دفع جديدة.

استخدم نمط Strategy عندما يكون لديك خوارزميات متعددة لمهمة محددة وتريد التبديل بينها ديناميكيًا، أو عندما تريد تجنب العبارات الشرطية لاختيار السلوك.

challenge icon

التحدي

سهل

لنقم ببناء حاسبة شحن (Shipping Calculator) تستخدم نمط الاستراتيجية (Strategy pattern) لحساب تكاليف التوصيل بناءً على طرق شحن مختلفة. هذا سيناريو عملي حيث تحتاج إلى تبديل الخوارزميات أثناء وقت التشغيل — فقد يتم شحن نفس الطرد عبر البر، أو الجو، أو الشحن السريع، ولكل منها منطق تسعير خاص به.

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

  • ShippingStrategy.h: حدد واجهة الاستراتيجية واستراتيجيات الشحن الملموسة.

    أنشئ فئة مجردة ShippingStrategy مع دالة افتراضية بحتة (pure virtual method) باسم calculateCost(double weight) تعيد تكلفة الشحن كـ double، بالإضافة إلى مدمر افتراضي (virtual destructor).

    ثم قم بتنفيذ ثلاث استراتيجيات ملموسة:

    • GroundShipping — تكلفته 1.5 لكل وحدة وزن (weight * 1.5)
    • AirShipping — تكلفته 4.0 لكل وحدة وزن (weight * 4.0)
    • ExpressShipping — تكلفته 6.5 لكل وحدة وزن بالإضافة إلى رسوم ثابتة قدرها 10.0 (weight * 6.5 + 10.0)
  • ShippingService.h: أنشئ فئة السياق (context class) التي تستخدم استراتيجية الشحن.

    يجب أن تحتوي فئة ShippingService الخاصة بك على std::unique_ptr<ShippingStrategy> كعضو خاص. قم بتنفيذ:

    • دالة setStrategy(std::unique_ptr<ShippingStrategy> strategy) لتغيير طريقة الشحن
    • دالة calculateShipping(double weight) تستخدم الاستراتيجية الحالية لحساب وإرجاع التكلفة

    إذا لم يتم تعيين أي استراتيجية عند استدعاء calculateShipping، فقم بإرجاع 0.0.

  • main.cpp: توضيح تبديل الاستراتيجيات أثناء وقت التشغيل.

    اقرأ مدخلين:

    1. وزن الطرد (double)
    2. طريقة الشحن: ground، أو air، أو express

    أنشئ ShippingService وقم بتعيين الاستراتيجية المناسبة بناءً على طريقة الإدخال. احسب واطبع تكلفة الشحن.

    ثم انتقل إلى استراتيجية مختلفة (استخدم air إذا لم يكن الإدخال air، وإلا استخدم ground) واحسب التكلفة مرة أخرى لنفس الوزن. هذا يوضح قوة تبديل الاستراتيجيات أثناء وقت التشغيل.

    اطبع كل تكلفة في سطر خاص بها مع منزلة عشرية واحدة بالضبط، مسبوقة باسم الطريقة:

    [Method]: $[cost]

على سبيل المثال، مع المدخلات 5.0 و ground:

Ground: $7.5
Air: $20.0

مع المدخلات 3.0 و express:

Express: $29.5
Air: $12.0

مع المدخلات 10.0 و air:

Air: $40.0
Ground: $15.0

لاحظ كيف أن ShippingService لا يحتاج إلى معرفة تفاصيل كل خوارزمية تسعير — فهو ببساطة يفوض المهمة إلى أي استراتيجية محددة حاليًا. يمكنك بسهولة إضافة طرق شحن جديدة (مثل التوصيل بالطائرات بدون طيار أو التوصيل في نفس اليوم) دون تعديل فئة الخدمة على الإطلاق.

ورقة مرجعية

يحدد نمط الاستراتيجية (Strategy pattern) عائلة من الخوارزميات، ويغلف كل واحدة منها، ويجعلها قابلة للتبديل. يتيح ذلك تغيير سلوك الكائن في وقت التشغيل دون تعديل الكود الخاص به.

يتكون النمط من ثلاثة أجزاء:

  • واجهة الاستراتيجية (Strategy interface): فئة مجردة تعلن عن طريقة الخوارزمية
  • الاستراتيجيات الملموسة (Concrete Strategies): فئات تنفذ متغيرات مختلفة من الخوارزمية
  • السياق (Context): فئة تستخدم استراتيجية ويمكنها التبديل بينها
// واجهة الاستراتيجية
class PaymentStrategy {
public:
    virtual void pay(int amount) = 0;
    virtual ~PaymentStrategy() = default;
};

// الاستراتيجيات الملموسة
class CreditCardPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " via Credit Card\n";
    }
};

class PayPalPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " via PayPal\n";
    }
};

// السياق
class ShoppingCart {
    std::unique_ptr<PaymentStrategy> strategy;
public:
    void setPaymentMethod(std::unique_ptr<PaymentStrategy> s) {
        strategy = std::move(s);
    }
    
    void checkout(int total) {
        if (strategy) strategy->pay(total);
    }
};

لا يعرف السياق أي استراتيجية ملموسة يستخدمها—فهو يكتفي باستدعاء طريقة الواجهة. يمكن تبديل الاستراتيجيات في وقت التشغيل باستخدام طريقة تعيين (setter method):

ShoppingCart cart;

cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
cart.checkout(100);

cart.setPaymentMethod(std::make_unique<PayPalPayment>());
cart.checkout(50);

متى يُستخدم: استخدم نمط الاستراتيجية عندما يكون لديك خوارزميات متعددة لمهمة محددة وتريد التبديل بينها ديناميكيًا، أو عندما تريد تجنب الجمل الشرطية لاختيار السلوك.

جرّب بنفسك

#include <iostream>
#include <string>
#include <iomanip>
#include <memory>
#include "ShippingStrategy.h"
#include "ShippingService.h"

int main() {
    double weight;
    std::string method;
    
    std::cin >> weight;
    std::cin >> method;
    
    // ضبط المخرجات لتكون بمنزلة عشرية واحدة
    std::cout << std::fixed << std::setprecision(1);
    
    ShippingService service;
    
    // TODO: بناءً على طريقة الإدخال ("ground" أو "air" أو "express"):
    // 1. قم بتعيين الاستراتيجية المناسبة في الـ service
    // 2. احسب واطبع التكلفة بالتنسيق التالي: "[Method]: $[cost]"
    
    // TODO: التبديل إلى استراتيجية مختلفة:
    // - إذا كان الإدخال "air"، فقم بالتبديل إلى GroundShipping
    // - خلاف ذلك، قم بالتبديل إلى AirShipping
    // احسب واطبع التكلفة الجديدة
    
    return 0;
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming