نمط الحالة
جزء من قسم 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) عندما يعتمد سلوك الكائن بشكل كبير على حالته، ويكون لديك العديد من الجمل الشرطية التي تتغير بناءً على حالة الكائن.
التحدي
سهللنقم ببناء 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"، وتتغير إلى GreenYellowState— اللون هو"Yellow"، الإجراء هو"Caution"، وتتغير إلى RedGreenState— اللون هو"Green"، الإجراء هو"Go"، وتتغير إلى Yellow
ستحتاج إلى إعلان مسبق (forward declaration) للفئة
TrafficLightلأن الحالات تشير إليها.- دالة افتراضية نقية (pure virtual method) باسم
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;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةبناء وتجميع (Compilation) لغة C++ملفات الـ Header وملفات الـ Sourceالـ Namespaces والـ Scopeمقدمة في الـ OOP في C++الـ Classes مقابل الـ Objectsالمؤشر 'this'الـ Methods (الدوال الأعضاء)الـ Attributes (بيانات الأعضاء)أساسيات الـ Ctors والـ Dtorsمراجعة - آلة حاسبة بسيطة4خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticدوال الـ Getters والـ Settersدوال الأعضاء الثابتة (Const)الكلمة المفتاحية Mutableالدوال والمتغيرات الساكنة (Static)الدوال والفئات الصديقة (Friend)مراجعة - مدير الحساب البنكي7الوراثة
الوراثة الأساسيةمستويات الوصول في الوراثةترتيب استدعاء الـ Ctor والـ Dtorإعادة تعريف الدوال (Method Overriding)الدوال الافتراضية والـ VTableالوراثة المتعددةالوراثة الافتراضيةمراجعة - هيكلية الموظفين10نظرة عامة على STL
نظرة عامة وفلسفة STLحاويات STLالـ Iteratorsخوارزميات STLالـ Functors وتعبيرات Lambdaمراجعة - تكرار الكلمات13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factory و Abstract Factoryنمط Builderنمط Observerنمط Strategy2إدارة الذاكرة
ذاكرة Stack مقابل Heapالمؤشرات والمراجعالذاكرة الديناميكية (new/delete)المؤشرات الذكية في C++RAII في C++مراجعة - مدير المصفوفات الديناميكية5التغليف (Encapsulation)
محددات الوصول في C++محددات الوصول بشكل متعمقإخفاء المعلوماتStruct مقابل Classالأصناف المتداخلة والداخليةمراجعة - نظام سجلات الطلاب8تعدد الأشكال (Polymorphism)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالتحميل الزائد للدوال (Function Overloading)مراجعة الدوال الافتراضية (Virtual Functions)الدوال الافتراضية البحتة (Pure Virtual Functions)الأصناف المجردة (Abstract Classes)تصميم الواجهات (Interface) في C++التحويل الديناميكي (Dynamic Casting) و RTTIملخص - حاسبة الأشكال11مفاهيم OOP المتقدمة
التركيب مقابل الوراثةالـ Mixins عبر CRTPنمط Pimplمحو النوع (Type Erasure)أصناف Enum والأنواع القويةمعالجة الاستثناءات في OOPتسلسلات الاستثناءات المخصصة14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط3المنشئات والموادم
المنشئ الافتراضيالمنشئ ذو المعاملاتمنشئ النسخمنشئ النقلقوائم تهيئة المنشئالمنشئات المفوضةتعمق في الموادمقاعدة الثلاثة / الخمسة / الصفرمراجعة - فئة String6تحميل العوامل (Operator Overloading)
مقدمة في تحميل العواملتحميل العوامل الحسابيةتحميل عوامل المقارنةعوامل Streamتحميل عامل التعيينتحميل العوامل [] و ()عوامل تحويل النوعمراجعة - Matrix Class9القوالب
قوالب الدوالقوالب الأصنافتخصيص القوالبالقوالب المتغيرةأساسيات SFINAE و Type Traitsمراجعة - الحاويات العامة