Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نمط الحالة

جزء من قسم 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.

challenge icon

التحدي

سهل

لنقم ببناء نظام سير عمل المستندات باستخدام نمط الحالة (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 على المستند
        // - بعد كل إجراء، اطبع الحالة الحالية
    }
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming