نمط الحالة
جزء من قسم البرمجة كائنية التوجه في رحلة Java على Coddy. الدرس 74 من 87.
إن نمط الحالة هو نمط تصميم سلوكي يسمح لكائن بتغيير سلوكه عندما تتغير حالته الداخلية. يبدو أن الكائن يغيّر class الخاص به، لكنه في الواقع delegates السلوك إلى كائنات حالة مختلفة. فكّر في آلة بيع: تعتمد استجابتها للضغط على زر على ما إذا كانت خاملة، أو أُدخلت فيها نقود، أو كانت توزّع منتجًا.
بدلاً من استخدام عبارات شرطية معقدة للتعامل مع الحالات المختلفة، تقوم بتغليف كل حالة في فئتها الخاصة. يتضمن هذا النمط Context يحافظ على مرجع إلى الحالة current، وفئات 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);
}
}تطبّق كل حالة concrete السلوك ويمكنها تشغيل انتقالات إلى حالات أخرى:
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(); // Idle: أدخل عملة معدنية للبدء
context.request(); // Active: جاري المعالجة...يُعد نمط State مناسبًا للغاية عندما يعتمد سلوك كائن بدرجة كبيرة على حالته ويجب أن يتغير أثناء وقت التشغيل. فهو يلغي الكتل الشرطية الكبيرة ويجعل إضافة حالات جديدة أمرًا مباشرًا؛ ما عليك سوى إنشاء فئة جديدة تطبّق الواجهة State.
التحدي
سهللنَبْنِ نظامًا لسير عمل المستندات باستخدام نمط الحالة! ستنشئ نظامًا يحاكي كيفية انتقال المستند عبر مراحل مختلفة، Draft وReview وPublished، حيث تحدد كل حالة الإجراءات المتاحة وكيف يتصرف المستند عند محاولة edit أو approve له.
ستنظّم التعليمات البرمجية عبر أربعة ملفات:
DocumentState.java: عرّف الواجهةDocumentStateالتي ستنفذها جميع الحالات. يجب أن تصرّح عن أسلوبين:edit(Document doc)لمحاولة تعديل المستند، وapprove(Document doc)لمحاولة الموافقة على المستند أو نقله إلى المرحلة التالية.States.java: أنشئ ثلاث فئات حالات concrete تنفذ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: أنشئ فئة السياق التي تحافظ على الحالة الحالية. يجب أن يحتويDocumentعلى:حقل private يحتفظ بـ
DocumentStateالحالية، ويُهيَّأ إلىDraftStateفي المُنشئ.أسلوب
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() نفسه نتائج مختلفة اعتمادًا على الحالة الحالية للمستند. هذا هو جوهر نمط الحالة! لا يستخدم المستند عبارات شرطية للتحقق من حالته؛ بل يفوّض السلوك إلى كائن الحالة الحالية، الذي يعالج الإجراء بشكل مناسب ويُفعّل الانتقالات عند الحاجة.
جرّب بنفسك
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
// TODO: إنشاء Document جديد
// TODO: طباعة الحالة الأولية بالتنسيق "Status: [state]"
// TODO: تقسيم الإدخال بواسطة الفاصلة للحصول على الإجراءات الفردية
// TODO: التكرار خلال كل إجراء
// - إذا كان الإجراء "edit"، قم باستدعاء edit() على document
// - إذا كان الإجراء "approve"، قم باستدعاء approve() على document
// - بعد كل إجراء، اطبع الحالة الحالية
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكررتدرّب بنفسك: مترجم Java عبر الإنترنت