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

نمط الحالة

جزء من قسم البرمجة كائنية التوجه في رحلة 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.

challenge icon

التحدي

سهل

لنَبْنِ نظامًا لسير عمل المستندات باستخدام نمط الحالة! ستنشئ نظامًا يحاكي كيفية انتقال المستند عبر مراحل مختلفة، 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
        // - بعد كل إجراء، اطبع الحالة الحالية
    }
}
quiz iconاختبر نفسك

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

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم Java عبر الإنترنت