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() — تفوض المهمة لدالة التغيير الخاصة بالحالة الحالية.
    • 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) لتحديد ما سيحدث — فهي ببساطة تفوض الأمر لأي حالة هي عليها حالياً.

جرّب بنفسك

#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