نمط الحالة
جزء من قسم Object Oriented Programming في رحلة Dart على Coddy — الدرس 98 من 110.
يسمح State pattern للكائن بتغيير سلوكه عندما تتغير حالته الداخلية، مما يجعله يبدو كما لو أن الكائن قد غير الـ class الخاص به. بدلاً من استخدام الشروط المعقدة للتعامل مع الحالات المختلفة، تقوم بتغليف كل حالة في class خاصة بها وتفويض السلوك إلى كائن الحالة الحالي.
يتكون النمط من Context يحافظ على مرجع للحالة الحالية، وفئات State التي تحدد السلوك لكل حالة ممكنة. عندما تتغير حالة السياق، يتغير سلوكه تلقائيًا:
abstract class DocumentState {
void publish(Document doc);
String getStatus();
}
class DraftState implements DocumentState {
@override
void publish(Document doc) {
print('Moving to review...');
doc.setState(ReviewState());
}
@override
String getStatus() => 'Draft';
}
class ReviewState implements DocumentState {
@override
void publish(Document doc) {
print('Document published!');
doc.setState(PublishedState());
}
@override
String getStatus() => 'Under Review';
}
class PublishedState implements DocumentState {
@override
void publish(Document doc) {
print('Already published.');
}
@override
String getStatus() => 'Published';
}
class Document {
DocumentState _state = DraftState();
void setState(DocumentState state) => _state = state;
void publish() => _state.publish(this);
String getStatus() => _state.getStatus();
}
void main() {
var doc = Document();
print(doc.getStatus()); // مسودة
doc.publish(); // الانتقال إلى المراجعة...
print(doc.getStatus()); // قيد المراجعة
doc.publish(); // تم نشر المستند!
print(doc.getStatus()); // منشور
}تتعامل كل فئة حالة مع الإجراء publish() بشكل مختلف ويمكنها نقل المستند إلى الحالة التالية. لا تحتاج فئة Document إلى جمل شرطية للتحقق من حالتها الحالية - فهي ببساطة تفوض الأمر إلى أي كائن حالة نشط حالياً. هذا النمط مثالي للكائنات ذات الأنماط السلوكية المتميزة، مثل معالجة الطلبات، أو مشغلات الوسائط، أو أنظمة سير العمل.
التحدي
سهللنقم ببناء نظام إشارات مرور باستخدام نمط الحالة (State pattern)! ستقوم بإنشاء إشارة مرور تتنقل عبر حالات مختلفة، حيث تحدد كل حالة لون الإشارة وكيفية انتقالها إلى الحالة التالية.
ستقوم بتنظيم الكود الخاص بك في ملفين:
traffic_light.dart: يحتوي هذا الملف على فئات الحالة (state classes) وسياق إشارة المرور (traffic light context). قم بإنشاء فئة مجردة (abstract class) باسمTrafficLightStateمع طريقتين (methods):String getColor()التي تعيد لون الإشارة الحالي، وvoid next(TrafficLight light)التي تنقل الإشارة إلى حالتها التالية. قم بتنفيذ ثلاث فئات حالة ملموسة (concrete state classes):RedState- تعيدREDكلون لها وتطبعStop! Light is red.عند استدعاءnext()، ثم تنتقل إلىGreenStateGreenState- تعيدGREENكلون لها وتطبعGo! Light is green.عند استدعاءnext()، ثم تنتقل إلىYellowStateYellowState- تعيدYELLOWكلون لها وتطبعCaution! Light is yellow.عند استدعاءnext()، ثم تنتقل إلىRedState
TrafficLight(السياق) التي تبدأ في الحالةRedState. يجب أن تحتوي على طريقةsetState()لتغيير الحالة الحالية، وطريقةgetColor()التي تفوض المهمة إلى الحالة الحالية، وطريقةchange()التي تستدعيnext()في الحالة الحالية.main.dart: قم باستيراد ملف إشارة المرور الخاص بك واستعرض نمط الحالة أثناء العمل. قم بإنشاءTrafficLightواطبع لونها الأولي. ثم استدعِchange()ثلاث مرات، مع طباعة اللون بعد كل تغيير. سيظهر هذا تنقل الإشارة عبر جميع الحالات والعودة إلى البداية.
لاحظ كيف أن فئة TrafficLight لا تحتاج إلى أي جمل شرطية لتحديد السلوك - فكل حالة تعرف لونها الخاص والحالة التي تليها. تقوم إشارة المرور ببساطة بالتفويض إلى أي حالة نشطة حالياً!
المخرجات المتوقعة:
RED
Stop! Light is red.
GREEN
Go! Light is green.
YELLOW
Caution! Light is yellow.
REDورقة مرجعية
يسمح نمط الحالة (State pattern) للكائن بتغيير سلوكه عندما تتغير حالته الداخلية. بدلاً من استخدام الشروط المعقدة، يتم تغليف كل حالة في فئة (class) خاصة بها.
يتكون النمط من:
- السياق (Context): يحتفظ بمرجع للحالة الحالية
- فئات الحالة (State classes): تحدد السلوك لكل حالة ممكنة
الهيكل الأساسي:
abstract class DocumentState {
void publish(Document doc);
String getStatus();
}
class DraftState implements DocumentState {
@override
void publish(Document doc) {
print('Moving to review...');
doc.setState(ReviewState());
}
@override
String getStatus() => 'Draft';
}
class Document {
DocumentState _state = DraftState();
void setState(DocumentState state) => _state = state;
void publish() => _state.publish(this);
String getStatus() => _state.getStatus();
}يقوم السياق بتفويض السلوك إلى كائن الحالة الحالي. تتعامل كل فئة حالة مع الإجراءات بشكل مختلف ويمكنها الانتقال إلى الحالة التالية. هذا يلغي الحاجة إلى الشروط للتحقق من الحالة الحالية.
مثالي للكائنات ذات أوضاع سلوكية متميزة مثل معالجة الطلبات، أو مشغلات الوسائط، أو أنظمة سير العمل.
جرّب بنفسك
import 'traffic_light.dart';
void main() {
// TODO: إنشاء مثيل من TrafficLight
// TODO: طباعة اللون الأولي
// TODO: استدعاء change() وطباعة اللون (3 مرات للمرور عبر جميع الحالات)
// التغيير الأول: RED -> GREEN
// التغيير الثاني: GREEN -> YELLOW
// التغيير الثالث: YELLOW -> RED
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةالمكتبات والـ Importsمقدمة في الـ OOPالـ Classes مقابل الـ Objectsالكلمة المفتاحية thisالـ Methodsمتغيرات الـ Instanceأساسيات الـ Constructorمراجعة - آلة حاسبة بسيطة4أمان الـ Null
مقدمة في أمان الـ NullNullable مقابل Non-Nullableعاملا الـ ? و !الكلمة المفتاحية Late وأمان الـ Nullالعوامل المدركة للـ Nullأمان الـ Null في الـ Classesمراجعة - نظام ملف تعريف المستخدم7الفئات المجردة والواجهات
الفئات المجردةالدوال المجردةالواجهات في Dartالواجهات الضمنيةالتنفيذ مقابل التوسيعالواجهات المتعددةمراجعة - حاسبة الأشكال10المجموعات والأنماط العامة
نظرة عامة على List و Set و Mapالمجموعات آمنة النوعالفئات العامةالدوال العامةقيود الأنماط العامةIterable و Iteratorملخص - التخزين العام13مفاهيم OOP المتقدمة
التركيب مقابل الوراثة (Composition vs Inheritance)دوال التوسعة (Extension Methods)الأصناف القابلة للاستدعاء (Callable Classes)الأصناف المختومة (Sealed Classes - Dart 3)السجلات (Records - Dart 3)الأنماط والمطابقة (Patterns & Matching - 3.0)التعدادات مع الدوال (Enums with Methods)2المنشئات في Dart
المنشئ الافتراضيالمنشئات المسماةقوائم التهيئةالمنشئات الثابتةالمنشئات المصنعيةالمنشئات الموجهةملخص - بناء الأشكال5التغليف (Encapsulation)
الأعضاء العامة مقابل الخاصةاتفاقية البادئة _الخصوصية على مستوى المكتبةتعمق في Getters و Settersإخفاء المعلوماتمراجعة - سجلات الطلاب8Mixins
مقدمة إلى Mixinsإنشاء Mixinsاستخدام Mixins متعددةالكلمة المفتاحية on في MixinsMixin مقابل الوراثة (Inheritance)Mixin مقابل الواجهة (Interface)مراجعة - نظام الحيوانات (Animal System)3خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticحقول Final و Constمتغيرات Lateالـ Static Methods والحقولدوال الـ Getters والـ Settersمراجعة - مدير الحساب البنكي6الوراثة
أساسيات الوراثةالكلمة المفتاحية superإعادة تعريف الدوال (Method Overriding)الوسم @overrideالكلمة المفتاحية final Classالدوال البانية والوراثةمراجعة - هيكلية الموظفين9تعدد الأشكال
أساسيات تعدد الأشكالتعدد الأشكال عبر الـ Interfacesالتحقق من النوع أثناء التشغيلالمعاملات is و asالكلمة المفتاحية Covariantملخص - معالج الدفع12البرمجة الكائنية غير المتزامنة (Async OOP)
Futures و async/awaitأساسيات الـ Streamsمتحكمات الـ Streamsالـ Constructors غير المتزامنةالعمليات غير المتزامنة في دوال الكلاسمراجعة - جالب البيانات (Data Fetcher)15أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المستودع