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

نمط الحالة

جزء من قسم Object Oriented Programming في رحلة C# على Coddy — الدرس 60 من 70.

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

يتكون النمط من ثلاثة مكونات: واجهة حالة (state interface) تحدد السلوك الخاص بكل حالة، وفئات حالة ملموسة (concrete state classes) تنفذ ذلك السلوك، وفئة سياق (context class) تحتفظ بمرجع للحالة الحالية:

public interface IDocumentState
{
    void Publish(Document doc);
}

public class DraftState : IDocumentState
{
    public void Publish(Document doc)
    {
        Console.WriteLine("Moving to moderation");
        doc.SetState(new ModerationState());
    }
}

public class ModerationState : IDocumentState
{
    public void Publish(Document doc)
    {
        Console.WriteLine("Publishing document");
        doc.SetState(new PublishedState());
    }
}

public class PublishedState : IDocumentState
{
    public void Publish(Document doc)
    {
        Console.WriteLine("Already published");
    }
}

يحتفظ السياق (context) بالحالة الحالية ويفوض الإجراءات إليها. يمكن لكائنات الحالة (State objects) إطلاق الانتقالات عن طريق استدعاء SetState() في السياق:

public class Document
{
    private IDocumentState _state = new DraftState();
    
    public void SetState(IDocumentState state) => _state = state;
    
    public void Publish() => _state.Publish(this);
}

var doc = new Document();
doc.Publish();  // الانتقال إلى المراجعة
doc.Publish();  // نشر المستند
doc.Publish();  // تم النشر بالفعل

يقضي نمط الحالة (State pattern) على جمل if-else أو switch المتشعبة التي تتحقق من الحالة الحالية. تتعامل كل فئة حالة (state class) مع سلوكها الخاص فقط، مما يجعل الكود أسهل في الصيانة والتوسيع بحالات جديدة.

challenge icon

التحدي

سهل

لنقم ببناء نظام إشارات مرور باستخدام نمط الحالة (State pattern). تنتقل إشارات المرور عبر حالات متميزة (الأحمر، الأصفر، الأخضر)، وتحدد كل حالة ما يحدث عندما تتغير الإشارة. بدلاً من استخدام الشروط المعقدة، ستقوم بتغليف كل حالة إشارة في فئة (class) خاصة بها.

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

  • TrafficLightState.cs: قم بتعريف واجهة ITrafficLightState داخل مساحة الأسماء Traffic مع طريقة Change(TrafficLight light). ثم أنشئ ثلاث فئات حالات ملموسة:
    • RedState - عند استدعاء Change، تقوم بطباعة Red - Stop! Changing to Green... وتنتقل الإشارة إلى GreenState
    • GreenState - تطبع Green - Go! Changing to Yellow... وتنتقل إلى YellowState
    • YellowState - تطبع Yellow - Caution! Changing to Red... وتنتقل إلى RedState
  • TrafficLight.cs: أنشئ فئة TrafficLight في نفس مساحة الأسماء. هذه هي فئة السياق (context class) التي تحتفظ بالحالة الحالية. يجب أن تبدأ في RedState افتراضياً وتحتوي على:
    • طريقة SetState(ITrafficLightState state) التي تستخدمها كائنات الحالة لتنشيط الانتقالات
    • طريقة Change() التي تفوض العمل إلى طريقة Change الخاصة بالحالة الحالية
  • Program.cs: اجمع كل شيء معاً عن طريق إنشاء TrafficLight والتبديل بين الحالات بناءً على المدخلات.

ستتلقى مدخلاً واحداً:

  • عدد مرات تغيير إشارة المرور (عدد صحيح)

أنشئ TrafficLight (التي تبدأ باللون الأحمر) واستدعِ Change() بالعدد المحدد من المرات.

على سبيل المثال، إذا كان المدخل 3، يجب أن يكون المخرج:

Red - Stop! Changing to Green...
Green - Go! Changing to Yellow...
Yellow - Caution! Changing to Red...

إذا كان المدخل 5، فستعود الإشارة للدوران مرة أخرى:

Red - Stop! Changing to Green...
Green - Go! Changing to Yellow...
Yellow - Caution! Changing to Red...
Red - Stop! Changing to Green...
Green - Go! Changing to Yellow...

لاحظ كيف أن فئة TrafficLight لا تحتوي على أي منطق حول معنى كل لون أو ما سيأتي بعده - كل هذه المعرفة تعيش في فئات الحالات الفردية. كل حالة مسؤولة عن سلوكها الخاص وعن تفعيل الانتقال إلى الحالة التالية!

جرّب بنفسك

using System;
using Traffic;

class Program
{
    public static void Main(String[] args)
    {
        // اقرأ عدد مرات تغيير إشارة المرور
        int n = Convert.ToInt32(Console.ReadLine());

        // TODO: أنشئ instance من `TrafficLight` (يبدأ بـ `RedState` افتراضياً)

        // TODO: استدعِ الدالة `Change()` لـ `n` من المرات باستخدام loop
    }
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming