نمط الحالة
جزء من قسم 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) مسؤولة عن معرفة الحالة التي تليها، مما يحافظ على تنظيم المنطق وسهولة صيانته.
التحدي
سهللنقم ببناء نظام سير عمل للمستندات باستخدام نمط الحالة (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تعيد حالة الحالة الحالية
- قبول عنوان في المنشئ (constructor) والبدء في الحالة
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]"
?>يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات OOP
الملفات الخارجيةمقدمة في OOPClasses مقابل Objectsالكلمة المفتاحية $thisالـ Methodsالـ Propertiesالمنشئ (__construct)الهادم (__destruct)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسيةالكلمة المفتاحية parent::إعادة تعريف الدوال (Method Overriding)الكلمة المفتاحية finalالأصناف المجردة (Abstract Classes)مراجعة - هيكلية الموظفين7التغليف (Encapsulation)
العام، المحمي، الخاص (Public, Protected, Private)نظرة متعمقة على معدلات الوصولدوال Getters و Settersإخفاء المعلوماتميزة Constructor Promotion (8.0)مراجعة - نظام سجلات الطلاب10مفاهيم البرمجة كائنية التوجه المتقدمة
التركيب مقابل الوراثةحقن التبعيةالأصناف المجهولةالتعدادات (PHP 8.1)الألياف (PHP 8.1)تعمق في استنساخ الكائناتالمولدات والمكررات13أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المستودع2مساحات الأسماء والتحميل التلقائي
مقدمة إلى مساحات الأسماءالكلمة المفتاحية useمعيار التحميل التلقائي PSR-4أداة التحميل التلقائي Composerملخص - مشروع منظم5الواجهات والعقود
مقدمة إلى الواجهاتتنفيذ الواجهاتتنفيذ واجهات متعددةالواجهة مقابل الـ Abstract Classالـ Type Hinting مع الواجهاتملخص - حاسبة الأشكال8الميثودات السحرية (Magic Methods)
مقدمة في الميثودات السحرية__toString و __debugInfo__get, __set, __isset, __unset__call و __callStatic__clone واستنساخ الكائنات__serialize و __unserializeمراجعة - Custom Collection3خصائص الكلاس
خصائص الـ Instance مقابل الخصائص الساكنة (Static)الثوابت في الكلاساتالدوال والخصائص الساكنة (Static)الخصائص الخاصة (Private) والمحمية (Protected)خصائص للقراءة فقط (Readonly) في PHP 8.1مراجعة - مدير الحساب البنكي6تعدد الأشكال (Polymorphism)
مراجعة إعادة تعريف الدوال (Method Overriding)تعدد الأشكال عبر الـ Interfacesتلميح النوع (Type Hinting) وأنواع الاتحاد (Union Types)الربط الاستاتيكي المتأخر (Late Static Binding)ملخص - معالج الدفع (Payment Processor)