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

نمط الحالة

جزء من قسم Object Oriented Programming في رحلة PHP على Coddy — الدرس 79 من 91.

إنّ نمط الحالة (State Pattern) هو نمط تصميم سلوكي يسمح للكائن بتغيير سلوكه عندما تتغير حالته الداخلية. فبدلاً من استخدام الجمل الشرطية المعقدة للتعامل مع الحالات المختلفة، تقوم بتغليف كل حالة في فئة (class) خاصة بها وتفويض السلوك إلى كائن الحالة الحالي.

فكر في آلة بيع ذاتي. فهي تتصرف بشكل مختلف اعتماداً على ما إذا كانت تنتظر المال، أو استلمت الدفع، أو تقوم بصرف منتج ما.

لكل حالة قواعدها الخاصة بالإجراءات المسموح بها. يقوم نمط الحالة (State Pattern) بنمذجة ذلك من خلال إنشاء فئة (class) لكل حالة.

<?php
interface OrderState {
    public function proceed(Order $order): string;
    public function getStatus(): string;
}

class PendingState implements OrderState {
    public function proceed(Order $order): string {
        $order->setState(new ProcessingState());
        return "Order moved to processing";
    }
    
    public function getStatus(): string {
        return "Pending";
    }
}

class ProcessingState implements OrderState {
    public function proceed(Order $order): string {
        $order->setState(new ShippedState());
        return "Order has been shipped";
    }
    
    public function getStatus(): string {
        return "Processing";
    }
}

class ShippedState implements OrderState {
    public function proceed(Order $order): string {
        return "Order already shipped";
    }
    
    public function getStatus(): string {
        return "Shipped";
    }
}

تحتفظ فئة السياق (context class) بمرجع للحالة الحالية وتفوض الاستدعاءات إليها:

<?php
class Order {
    private OrderState $state;
    
    public function __construct() {
        $this->state = new PendingState();
    }
    
    public function setState(OrderState $state): void {
        $this->state = $state;
    }
    
    public function proceed(): string {
        return $this->state->proceed($this);
    }
    
    public function getStatus(): string {
        return $this->state->getStatus();
    }
}

$order = new Order();
echo $order->getStatus() . "\n";
echo $order->proceed() . "\n";
echo $order->getStatus() . "\n";
echo $order->proceed() . "\n";
echo $order->getStatus();

المخرجات:

Pending
Order moved to processing
Processing
Order has been shipped
Shipped

يقضي نمط الحالة (State Pattern) على جمل switch المتشعبة ويجعل إضافة حالات جديدة أمراً مباشراً. كل فئة حالة (state class) مسؤولة عن معرفة الحالة التي تليها، مما يحافظ على تنظيم المنطق وسهولة صيانته.

challenge icon

التحدي

سهل

لنقم ببناء نظام سير عمل للمستندات باستخدام نمط الحالة (State Pattern). تمر المستندات في نظام إدارة المحتوى بمراحل مختلفة — المسودة، المراجعة، والمنشور — حيث يكون لكل مرحلة قواعدها الخاصة حول الإجراءات المسموح بها وما يحدث عند محاولة تقديم المستند.

ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:

  • DocumentState.php — قم بتعريف واجهة DocumentState التي تحدد العقد لجميع حالات المستند. يجب أن تحتوي على طريقتين: publish(Document $document): string لمحاولة تقديم المستند، و getStatus(): string لإرجاع اسم الحالة الحالية.
  • States.php — قم بتضمين واجهة DocumentState وإنشاء ثلاث فئات حالة (state classes) تقوم بتنفيذها:
    • DraftState — عند استدعاء publish()، فإنها تنقل المستند إلى ReviewState وتعيد "Document sent for review". حالتها هي "Draft".
    • ReviewState — عند استدعاء publish()، فإنها تنقل المستند إلى PublishedState وتعيد "Document published". حالتها هي "Under Review".
    • PublishedState — عند استدعاء publish()، فإنها تعيد "Document is already published" دون تغيير الحالة. حالتها هي "Published".
  • Document.php — قم بتضمين ملف States وإنشاء فئة Document التي تعمل كـ context. يجب أن يقوم المستند الخاص بك بـ:
    • قبول عنوان في المنشئ (constructor) والبدء في الحالة DraftState
    • امتلاك طريقة setState(DocumentState $state): void لتغيير الحالة الحالية
    • امتلاك طريقة publish(): string تفوض العمل لطريقة publish() الخاصة بالحالة الحالية
    • امتلاك طريقة getStatus(): string تعيد حالة الحالة الحالية
  • main.php — قم بتضمين ملف Document. ستتلقى مدخلاً واحداً: عدد المرات التي سيتم فيها استدعاء publish() على المستند.

    قم بإنشاء Document بالعنوان "My Article". أولاً، اطبع الحالة الأولية. ثم، استدعِ publish() لعدد المرات المحدد، مع طباعة نتيجة كل استدعاء في سطر جديد. أخيراً، اطبع الحالة النهائية.

    تنسيق المخرجات:

    Status: [initial status]
    [publish result 1]
    [publish result 2]
    ...
    Status: [final status]

تعرف كل فئة حالة الحالة التي تليها في سير العمل، مما يحافظ على تنظيم منطق الانتقال وسهولة تعديله. إضافة حالة جديدة مثل "Archived" سيعني ببساطة إنشاء فئة جديدة وتحديث الانتقالات ذات الصلة.

ورقة مرجعية

نمط الحالة (State Pattern) هو نمط تصميم سلوكي يسمح للكائن بتغيير سلوكه عندما تتغير حالته الداخلية عن طريق تغليف كل حالة في فئة (class) خاصة بها.

قم بتعريف واجهة (interface) للحالة ستقوم جميع فئات الحالة بتنفيذها:

<?php
interface OrderState {
    public function proceed(Order $order): string;
    public function getStatus(): string;
}

قم بإنشاء فئات حالة ملموسة (concrete state classes) تقوم بتنفيذ الواجهة:

<?php
class PendingState implements OrderState {
    public function proceed(Order $order): string {
        $order->setState(new ProcessingState());
        return "Order moved to processing";
    }
    
    public function getStatus(): string {
        return "Pending";
    }
}

class ProcessingState implements OrderState {
    public function proceed(Order $order): string {
        $order->setState(new ShippedState());
        return "Order has been shipped";
    }
    
    public function getStatus(): string {
        return "Processing";
    }
}

class ShippedState implements OrderState {
    public function proceed(Order $order): string {
        return "Order already shipped";
    }
    
    public function getStatus(): string {
        return "Shipped";
    }
}

قم بإنشاء فئة سياق (context class) تحتفظ بمرجع للحالة الحالية وتفوض الاستدعاءات إليها:

<?php
class Order {
    private OrderState $state;
    
    public function __construct() {
        $this->state = new PendingState();
    }
    
    public function setState(OrderState $state): void {
        $this->state = $state;
    }
    
    public function proceed(): string {
        return $this->state->proceed($this);
    }
    
    public function getStatus(): string {
        return $this->state->getStatus();
    }
}

مثال على الاستخدام:

$order = new Order();
echo $order->getStatus() . "\n";  // Pending
echo $order->proceed() . "\n";     // Order moved to processing
echo $order->getStatus() . "\n";   // Processing
echo $order->proceed() . "\n";     // Order has been shipped
echo $order->getStatus();          // Shipped

يقضي نمط الحالة على جمل switch المعقدة ويجعل إضافة حالات جديدة أمراً سهلاً. كل فئة حالة مسؤولة عن معرفة الحالة التي تليها.

جرّب بنفسك

<?php

require_once 'Document.php';

// قراءة المدخلات - عدد مرات استدعاء publish()
$n = intval(trim(fgets(STDIN)));

// TODO: إنشاء مستند (Document) بعنوان "My Article"

// TODO: طباعة الحالة الأولية بالتنسيق: "Status: [status]"

// TODO: استدعاء publish() لعدد n من المرات، مع طباعة كل نتيجة في سطر جديد

// TODO: طباعة الحالة النهائية بالتنسيق: "Status: [status]"

?>
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming