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

نمط الحالة

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

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

يتكون النمط من Context يحتفظ بمرجع للحالة الحالية، و State interface تُعرف السلوك الخاص بالحالة، و Concrete States تُنفذ السلوك لكل حالة:

#include <iostream>
#include <memory>

class Document;  // إعلان مسبق

// واجهة الحالة
class DocumentState {
public:
    virtual void publish(Document& doc) = 0;
    virtual std::string getName() const = 0;
    virtual ~DocumentState() = default;
};

// السياق
class Document {
    std::unique_ptr<DocumentState> state;
public:
    Document();
    void setState(std::unique_ptr<DocumentState> newState) {
        state = std::move(newState);
    }
    void publish() { state->publish(*this); }
    std::string getStateName() const { return state->getName(); }
};

// الحالات الملموسة
class Draft : public DocumentState {
public:
    void publish(Document& doc) override;
    std::string getName() const override { return "Draft"; }
};

class Review : public DocumentState {
public:
    void publish(Document& doc) override;
    std::string getName() const override { return "Review"; }
};

class Published : public DocumentState {
public:
    void publish(Document& doc) override {
        std::cout << "Already published\n";
    }
    std::string getName() const override { return "Published"; }
};

void Draft::publish(Document& doc) {
    std::cout << "Moving to review\n";
    doc.setState(std::make_unique<Review>());
}

void Review::publish(Document& doc) {
    std::cout << "Publishing document\n";
    doc.setState(std::make_unique<Published>());
}

Document::Document() : state(std::make_unique<Draft>()) {}

تتعامل كل حالة مع إجراء publish() بشكل مختلف وهي مسؤولة عن الانتقال إلى الحالة التالية. لا يحتاج الـ Document إلى معرفة منطق الانتقال - فهو ببساطة يفوض الأمر إلى الحالة الحالية. هذا يلغي جمل switch الكبيرة ويجعل إضافة حالات جديدة أمراً مباشراً.

استخدم نمط الحالة (State) عندما يعتمد سلوك الكائن بشكل كبير على حالته، ويكون لديك العديد من الجمل الشرطية التي تتغير بناءً على حالة الكائن.

challenge icon

التحدي

سهل

لنقم ببناء Traffic Light Controller (متحكم في إشارة المرور) باستخدام نمط الحالة (State pattern). ستقوم بإنشاء نظام تنتقل فيه إشارة المرور عبر حالات مختلفة (Red، Yellow، Green)، حيث تحدد كل حالة ما يحدث عند تغير الإشارة والرسالة التي يتم عرضها. هذا مثال كلاسيكي على نمط الحالة — حيث يعتمد سلوك إشارة المرور تماماً على حالتها الحالية.

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

  • TrafficLightState.h: تحديد واجهة الحالة (state interface) وجميع فئات الحالات الملموسة (concrete state classes).

    قم بإنشاء فئة TrafficLightState مجردة تحتوي على:

    • دالة افتراضية نقية (pure virtual method) باسم change(TrafficLight& light) تتعامل مع الانتقال إلى الحالة التالية
    • دالة افتراضية نقية باسم getColor() تعيد اللون الحالي كسلسلة نصية (string)
    • دالة افتراضية نقية باسم getAction() تعيد ما يجب على السائقين فعله (مثل: "Stop"، "Caution"، "Go")
    • مدمر افتراضي (virtual destructor)

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

    • RedState — اللون هو "Red"، الإجراء هو "Stop"، وتتغير إلى Green
    • YellowState — اللون هو "Yellow"، الإجراء هو "Caution"، وتتغير إلى Red
    • GreenState — اللون هو "Green"، الإجراء هو "Go"، وتتغير إلى Yellow

    ستحتاج إلى إعلان مسبق (forward declaration) للفئة TrafficLight لأن الحالات تشير إليها.

  • TrafficLight.h: إنشاء فئة السياق (context class) التي تحتفظ بالحالة الحالية.

    يجب أن تحتوي فئة TrafficLight الخاصة بك على std::unique_ptr<TrafficLightState> وتبدأ في حالة Red افتراضياً. قم بتنفيذ:

    • setState(std::unique_ptr<TrafficLightState> newState) — يغير الحالة الحالية
    • change() — يفوض المهمة إلى دالة التغيير (change method) الخاصة بالحالة الحالية
    • display() — يطبع حالة الإشارة بالتنسيق التالي: [Color]: [Action]
  • main.cpp: عرض دورة إشارة المرور عبر الحالات.

    اقرأ مدخلاً واحداً: عدد تغييرات الحالة المطلوب تنفيذها (عدد صحيح).

    قم بإنشاء TrafficLight واعرض حالتها الأولية. ثم قم بإجراء العدد المحدد من التغييرات، مع عرض الحالة بعد كل تغيير.

على سبيل المثال، مع المدخل 3:

Red: Stop
Green: Go
Yellow: Caution
Red: Stop

مع المدخل 6:

Red: Stop
Green: Go
Yellow: Caution
Red: Stop
Green: Go
Yellow: Caution
Red: Stop

لاحظ كيف تنتقل إشارة المرور عبر حالاتها بنمط متوقع: Red ← Green ← Yellow ← Red. كل حالة تعرف الحالة التي تليها وتتعامل مع انتقالها الخاص. لا تحتاج فئة TrafficLight إلى أي منطق شرطي (conditional logic) لتحديد ما يحدث — فهي ببساطة تفوض الأمر لأي حالة هي عليها الآن.

ورقة مرجعية

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

يتكون النمط من ثلاثة مكونات:

  • واجهة الحالة (State interface): تحدد السلوك الخاص بكل حالة
  • الحالات الملموسة (Concrete States): تنفذ السلوك لكل حالة
  • السياق (Context): يحتفظ بمرجع للحالة الحالية

الهيكل الأساسي:

#include <memory>

class Context;  // إعلان مسبق

// واجهة الحالة
class State {
public:
    virtual void handleAction(Context& ctx) = 0;
    virtual std::string getName() const = 0;
    virtual ~State() = default;
};

// السياق
class Context {
    std::unique_ptr<State> state;
public:
    void setState(std::unique_ptr<State> newState) {
        state = std::move(newState);
    }
    void performAction() { 
        state->handleAction(*this); 
    }
    std::string getStateName() const { 
        return state->getName(); 
    }
};

// حالة ملموسة
class ConcreteState : public State {
public:
    void handleAction(Context& ctx) override {
        // معالجة الإجراء والانتقال إلى الحالة التالية
        ctx.setState(std::make_unique<AnotherState>());
    }
    std::string getName() const override { 
        return "ConcreteState"; 
    }
};

تتعامل كل حالة مع الإجراءات بشكل مختلف وتكون مسؤولة عن الانتقال إلى الحالة التالية. يقوم السياق (Context) بتفويض السلوك إلى الحالة الحالية دون الحاجة إلى معرفة منطق الانتقال.

استخدم نمط الحالة عندما يعتمد سلوك الكائن بشكل كبير على حالته وتريد تجنب الجمل الشرطية الكبيرة.

جرّب بنفسك

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

using namespace std;

int main() {
    // قراءة عدد تغييرات الحالة
    int numChanges;
    cin >> numChanges;
    
    // TODO: إنشاء كائن TrafficLight
    
    // TODO: عرض الحالة الأولية
    
    // TODO: إجراء العدد المحدد من التغييرات
    // بعد كل تغيير، قم بعرض الحالة الحالية
    
    return 0;
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming