نمط الحالة
جزء من قسم Object Oriented Programming في رحلة Java على Coddy — الدرس 74 من 87.
إن State Pattern هو نمط تصميم سلوكي يسمح للكائن بتغيير سلوكه عندما تتغير حالته الداخلية. يبدو الكائن وكأنه يغير الـ class الخاص به، ولكنه في الواقع يفوض السلوك إلى كائنات حالة (state objects) مختلفة. فكر في آلة البيع الذاتي: تعتمد استجابتها للضغط على زر ما على ما إذا كانت في حالة خمول، أو تم إدخال مبالغ مالية، أو أنها تقوم بصرف منتج.
بدلاً من استخدام الجمل الشرطية المعقدة للتعامل مع الحالات المختلفة، تقوم بتغليف كل حالة في فئة (class) خاصة بها. يتضمن هذا النمط سياقاً (Context) يحتفظ بمرجع للحالة الحالية، وفئات حالة (State) تحدد السلوك لكل حالة:
interface State {
void handle(Context context);
}
class Context {
private State state;
public void setState(State state) {
this.state = state;
}
public void request() {
state.handle(this);
}
}تقوم كل حالة ملموسة بتنفيذ السلوك ويمكنها إطلاق الانتقالات إلى حالات أخرى:
class IdleState implements State {
public void handle(Context context) {
System.out.println("Idle: Insert coin to start");
context.setState(new ActiveState());
}
}
class ActiveState implements State {
public void handle(Context context) {
System.out.println("Active: Processing...");
context.setState(new IdleState());
}
}يقوم السياق (context) بتفويض الطلبات إلى حالته الحالية، ويتغير السلوك تلقائيًا مع تغير الحالة:
Context context = new Context();
context.setState(new IdleState());
context.request(); // خامل: أدخل عملة للبدء
context.request(); // نشط: جارٍ المعالجة...يُعد نمط الحالة (State Pattern) مثالياً عندما يعتمد سلوك الكائن بشكل كبير على حالته ويجب أن يتغير أثناء وقت التشغيل. فهو يلغي الكتل الشرطية الكبيرة ويجعل إضافة حالات جديدة أمراً مباشراً—ما عليك سوى إنشاء فئة (class) جديدة تُنفذ واجهة State.
التحدي
سهللنقم ببناء نظام سير عمل المستندات باستخدام نمط الحالة (State Pattern)! ستقوم بإنشاء نظام يمثل كيفية انتقال المستند عبر مراحل مختلفة—Draft، و Review، و Published—حيث تحدد كل حالة الإجراءات المتاحة وكيفية تصرف المستند عندما تحاول تعديله أو الموافقة عليه.
ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:
DocumentState.java: قم بتعريف واجهةDocumentStateالتي ستنفذها جميع الحالات. يجب أن تعلن عن طريقتين:edit(Document doc)لمحاولة تعديل المستند، وapprove(Document doc)لمحاولة الموافقة على المستند أو نقله إلى المرحلة التالية.States.java: قم بإنشاء ثلاث فئات حالات ملموسة (concrete state classes) تنفذDocumentState:DraftState- عند استدعاءedit، اطبعEditing draft.... وعند استدعاءapprove، اطبعDraft approved. Moving to review.وقم بنقل المستند إلىReviewState.ReviewState- عند استدعاءedit، اطبعCannot edit during review.(لا يوجد تغيير في الحالة). وعند استدعاءapprove، اطبعReview complete. Publishing document.وقم بالانتقال إلىPublishedState.PublishedState- عند استدعاءedit، اطبعCannot edit published document.. وعند استدعاءapprove، اطبعDocument already published.. لا يغير أي من الإجراءين الحالة.Document.java: قم بإنشاء فئة السياق (context class) التي تحتفظ بالحالة الحالية. يجب أن يحتويDocumentالخاص بك على:حقل خاص (private field) يحمل
DocumentStateالحالي، ويتم تهيئته إلىDraftStateفي المنشئ (constructor).طريقة
setState(DocumentState state)تقوم بتغيير الحالة الحالية.طريقة
edit()تفوض العمل إلى طريقةeditالخاصة بالحالة الحالية.طريقة
approve()تفوض العمل إلى طريقةapproveالخاصة بالحالة الحالية.طريقة
getStatus()تعيد اسم الفئة البسيط للحالة الحالية (استخدمgetClass().getSimpleName()).Main.java: استعرض نظام الحالة الخاص بك! ستتلقى مدخلاً واحداً: سلسلة من الإجراءات كسلسلة مفصولة بفاصلة حيث يكون كل إجراء إماeditأوapprove(على سبيل المثال:edit,approve,edit,approve).قم بإنشاء
Documentواطبع حالته الأولية بالتنسيقStatus: [state]. ثم قم بمعالجة كل إجراء بالترتيب، مع استدعاء الطريقة المناسبة على المستند. بعد كل إجراء، اطبع الحالة الحالية.
ستتلقى مدخلاً واحداً: سلسلة من الإجراءات مفصولة بفاصلة.
على سبيل المثال، مع المدخل edit,approve,approve، سيكون مخرجك:
Status: DraftState
Editing draft...
Status: DraftState
Draft approved. Moving to review.
Status: ReviewState
Review complete. Publishing document.
Status: PublishedStateلاحظ كيف أن نفس استدعاء approve() ينتج نتائج مختلفة اعتماداً على الحالة الحالية للمستند—هذا هو جوهر نمط الحالة! لا يستخدم المستند الجمل الشرطية للتحقق من حالته؛ بدلاً من ذلك، فإنه يفوض السلوك إلى كائن الحالة الحالي، الذي يتعامل مع الإجراء بشكل مناسب ويطلق الانتقالات عند الحاجة.
ورقة مرجعية
يعد نمط الحالة (State Pattern) نمط تصميم سلوكي يسمح للكائن بتغيير سلوكه عندما تتغير حالته الداخلية عن طريق تفويض السلوك إلى كائنات حالة مختلفة بدلاً من استخدام الجمل الشرطية المعقدة.
يتكون النمط من سياق (Context) يحتفظ بمرجع للحالة الحالية، وفئات حالة (State) تحدد السلوك لكل حالة:
interface State {
void handle(Context context);
}
class Context {
private State state;
public void setState(State state) {
this.state = state;
}
public void request() {
state.handle(this);
}
}تقوم كل حالة ملموسة بتنفيذ السلوك ويمكنها إطلاق عمليات الانتقال إلى حالات أخرى:
class IdleState implements State {
public void handle(Context context) {
System.out.println("Idle: Insert coin to start");
context.setState(new ActiveState());
}
}
class ActiveState implements State {
public void handle(Context context) {
System.out.println("Active: Processing...");
context.setState(new IdleState());
}
}يقوم السياق بتفويض الطلبات إلى حالته الحالية، ويتغير السلوك تلقائياً مع تغير الحالة:
Context context = new Context();
context.setState(new IdleState());
context.request(); // Idle: Insert coin to start
context.request(); // Active: Processing...يعد نمط الحالة مثالياً عندما يعتمد سلوك الكائن بشكل كبير على حالته ويجب أن يتغير في وقت التشغيل. فهو يزيل الكتل الشرطية الكبيرة ويجعل إضافة حالات جديدة أمراً سهلاً.
جرّب بنفسك
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
// TODO: إنشاء مستند جديد
// TODO: طباعة الحالة الأولية بتنسيق "Status: [state]"
// TODO: تقسيم المدخلات بواسطة الفاصلة للحصول على الإجراءات الفردية
// TODO: حلقة تكرارية لكل إجراء
// - إذا كان الإجراء "edit"، استدعِ ()edit على المستند
// - إذا كان الإجراء "approve"، استدعِ ()approve على المستند
// - بعد كل إجراء، اطبع الحالة الحالية
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةمقدمة في الـ OOPالفئات (Classes) مقابل الكائنات (Objects)الكلمة المفتاحية thisالدوال (Methods)الحقول (Attributes)دالة البناء (Constructor)تعدد أشكال دالة البناء (Constructor Overloading)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسية (extends)الكلمة المفتاحية superإعادة تعريف الدوال (@Override)تسلسل المنشئات (Constructor Chaining)كلاس Objectالوراثة الأحادية ومتعددة المستوياتلماذا لا توجد وراثة متعددة للكلاساتمراجعة - هيكلية الموظفين7الدوال الخاصة وفئة Object
دالة toString()equals() و hashCode()دالة clone()compareTo() و Comparableواجهة Comparatorمراجعة - الترتيب المخصص2محددات الوصول والتغليف
نظرة عامة على مستويات الوصولدوال Getter و Setterإخفاء البياناتالكلمة المفتاحية finalمراجعة - مدير الحساب البنكي5تعدد الأشكال (Polymorphism)
أساسيات الـ Method Overloadingالـ Method Overriding (في وقت التشغيل)الـ Upcasting والـ Downcastingمعامل الـ instanceofالفئات والدوال المجردة (Abstract)مراجعة - حاسبة الأشكال8مفاهيم OOP المتقدمة
الـ Composition مقابل الـ Inheritanceالـ Aggregation مقابل الـ Compositionالفئات الداخلية والمتداخلة والمجهولة (Inner, Nested & Anonymous)الـ Enums ودوالهاالـ Records (Java 16+)الـ Sealed Classes (Java 17+)11أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Builderنمط Observerنمط Strategy3خصائص الفئة والعضو الساكن
المتغيرات الخاصة بالكائن مقابل المتغيرات الساكنةالدوال الساكنةالكتل البرمجية الساكنةالثوابت (static final)ملخص - العداد والأدوات المساعدة6الواجهات (Interfaces) والأصناف المجردة (Abstract Classes)
مقدمة في الواجهات (Interfaces)تنفيذ الواجهات (Interfaces)تنفيذ واجهات متعددةالـ Default و Static في الواجهاتالأصناف المجردة مقابل الواجهاتالواجهات الوظيفية (Functional Interfaces)مراجعة - نظام الدفع9الأنواع العامة (Generics)
مقدمة في الأنواع العامةالأصناف العامة (Generic Classes)الدوال العامة (Generic Methods)معاملات الأنواع المقيدةالرموز البديلة (?, extends, super)مراجعة - الحاويات العامة12أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكرر