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

نمط الحالة

جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 95 من 107.

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

في لغة Go، نقوم بتعريف واجهة للحالة (state interface) وإنشاء حالات ملموسة (concrete states) تنفذ سلوكيات مختلفة:

type State interface {
    Handle(d *Document) string
}

type Document struct {
    state State
}

func (d *Document) SetState(s State) {
    d.state = s
}

func (d *Document) Publish() string {
    return d.state.Handle(d)
}

تحدد كل حالة ما يحدث وما هي الحالة التي تليها:

type DraftState struct{}

func (s DraftState) Handle(d *Document) string {
    d.SetState(ModerationState{})
    return "Draft submitted for moderation"
}

type ModerationState struct{}

func (s ModerationState) Handle(d *Document) string {
    d.SetState(PublishedState{})
    return "Moderation approved, now published"
}

type PublishedState struct{}

func (s PublishedState) Handle(d *Document) string {
    return "Already published"
}

ينتج عن استدعاء الطريقة نفسها نتائج مختلفة بناءً على الحالة الحالية:

doc := &Document{state: DraftState{}}

fmt.Println(doc.Publish())  // تم تقديم المسودة للمراجعة
fmt.Println(doc.Publish())  // تمت الموافقة على المراجعة، تم النشر الآن
fmt.Println(doc.Publish())  // منشور بالفعل

تعد الحالة (State) مثالية للكائنات ذات أوضاع التشغيل المتميزة، مثل سير عمل معالجة الطلبات، أو مكونات واجهة المستخدم، أو معالجات الاتصال حيث يعتمد السلوك تمامًا على الحالة الحالية.

challenge icon

التحدي

سهل

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

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

  • state.go: حدد واجهة الحالة (state interface) والحالات الملموسة (concrete states) التي تمثل كل مرحلة من دورة حياة التذكرة.

    قم بإنشاء واجهة TicketState مع طريقة Handle(t *Ticket) string التي تعالج التذكرة وتنقلها محتملاً إلى الحالة التالية.

    قم بتنفيذ ثلاث حالات:

    • OpenState — عند معالجتها، تنقل التذكرة إلى InProgressState وتعيد Ticket opened, assigning to support team
    • InProgressState — عند معالجتها، تنقل التذكرة إلى ResolvedState وتعيد Working on ticket, issue resolved
    • ResolvedState — عند معالجتها، تبقى في نفس الحالة وتعيد Ticket already resolved
  • ticket.go: قم بإنشاء هيكل (struct) التذكرة الذي يحمل الحالة الحالية ويفوض السلوك إليها.

    قم ببناء هيكل Ticket مع حقل ID (string) وحقل state (TicketState). أضف هذه الطرق:

    • SetState(s TicketState) لتغيير الحالة الحالية للتذكرة
    • Process() string التي تفوض العمل لطريقة Handle الخاصة بالحالة الحالية

    قم بإنشاء منشئ NewTicket(id string) *Ticket يعيد تذكرة تبدأ في الحالة OpenState.

  • main.go: وضح كيف ينتج عن نفس الإجراء نتائج مختلفة بناءً على الحالة الحالية للتذكرة.

    اقرأ معرف التذكرة (ticket ID) وعدد مرات معالجة التذكرة. أنشئ تذكرة جديدة بهذا المعرف، ثم استدعِ Process() لعدد المرات المحدد، مع طباعة كل نتيجة في سطر منفصل.

سيتم توفير المدخلات التالية:

  • السطر 1: معرف التذكرة (Ticket ID)
  • السطر 2: عدد مرات معالجة التذكرة

على سبيل المثال، إذا كان المعطى:

TKT-001
3

يجب أن يكون مخرجك:

Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved

وإذا كان المعطى:

TKT-500
5

يجب أن يكون مخرجك:

Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved
Ticket already resolved
Ticket already resolved

وإذا كان المعطى:

ISSUE-42
1

يجب أن يكون مخرجك:

Ticket opened, assigning to support team

لاحظ كيف أن استدعاء Process() على نفس التذكرة ينتج نتائج مختلفة في كل مرة—يتغير سلوك التذكرة أثناء انتقالها عبر الحالات. بمجرد حلها، تظل محلولة بغض النظر عن عدد مرات معالجتها. يبدو أن كائن التذكرة يغير سلوكه، لكنه في الواقع يفوض المهام لكائنات حالات مختلفة!

ورقة مرجعية

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

تعريف واجهة الحالة (state interface):

type State interface {
    Handle(d *Document) string
}

إنشاء كائن سياق (context object) يحمل الحالة الحالية:

type Document struct {
    state State
}

func (d *Document) SetState(s State) {
    d.state = s
}

func (d *Document) Publish() string {
    return d.state.Handle(d)
}

تنفيذ حالات ملموسة (concrete states) تغلف السلوك الخاص بكل حالة:

type DraftState struct{}

func (s DraftState) Handle(d *Document) string {
    d.SetState(ModerationState{})
    return "Draft submitted for moderation"
}

type ModerationState struct{}

func (s ModerationState) Handle(d *Document) string {
    d.SetState(PublishedState{})
    return "Moderation approved, now published"
}

type PublishedState struct{}

func (s PublishedState) Handle(d *Document) string {
    return "Already published"
}

تحدد كل حالة ما يحدث وتنتقل إلى الحالة التالية:

doc := &Document{state: DraftState{}}

fmt.Println(doc.Publish())  // مسودة مقدمة للمراجعة
fmt.Println(doc.Publish())  // تمت الموافقة على المراجعة، تم النشر الآن
fmt.Println(doc.Publish())  // منشور بالفعل

يعد نمط الحالة مثاليًا للكائنات التي لها أوضاع تشغيل متميزة حيث يعتمد السلوك تمامًا على الحالة الحالية.

جرّب بنفسك

package main

import (
	"fmt"
)

func main() {
	// قراءة المدخلات
	var ticketID string
	var numProcesses int
	fmt.Scanln(&ticketID)
	fmt.Scanln(&numProcesses)

	// TODO: إنشاء تذكرة جديدة بالمعرف المعطى

	// TODO: معالجة التذكرة لعدد المرات المحدد
	// وطباعة كل نتيجة في سطر منفصل
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming