نمط الحالة
جزء من قسم 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) مثالية للكائنات ذات أوضاع التشغيل المتميزة، مثل سير عمل معالجة الطلبات، أو مكونات واجهة المستخدم، أو معالجات الاتصال حيث يعتمد السلوك تمامًا على الحالة الحالية.
التحدي
سهللنقم ببناء نظام دعم التذاكر باستخدام نمط الحالة (State pattern)! ستقوم بإنشاء تذكرة دعم تنتقل عبر مراحل مختلفة—من كونها مفتوحة، إلى العمل عليها، وصولاً إلى حلها—حيث تحدد كل حالة الإجراءات الممكنة وما سيحدث بعد ذلك.
ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:
state.go: حدد واجهة الحالة (state interface) والحالات الملموسة (concrete states) التي تمثل كل مرحلة من دورة حياة التذكرة.قم بإنشاء واجهة
TicketStateمع طريقةHandle(t *Ticket) stringالتي تعالج التذكرة وتنقلها محتملاً إلى الحالة التالية.قم بتنفيذ ثلاث حالات:
OpenState— عند معالجتها، تنقل التذكرة إلىInProgressStateوتعيدTicket opened, assigning to support teamInProgressState— عند معالجتها، تنقل التذكرة إلىResolvedStateوتعيدWorking on ticket, issue resolvedResolvedState— عند معالجتها، تبقى في نفس الحالة وتعيد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: معالجة التذكرة لعدد المرات المحدد
// وطباعة كل نتيجة في سطر منفصل
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP في Go
الملفات الخارجيةمساحة العمل والـ Modules في Goالـ Packages والـ Importsالأسماء المصدرة مقابل غير المصدرةمقدمة إلى الـ OOP في Goالـ Structs كـ Classesتعريف الـ Methods في الـ Structsالـ Pointer Receivers مقابل الـ Value Receiversتهيئة الـ Structدوال الـ Constructorمراجعة - آلة حاسبة بسيطة4الواجهات
مقدمة في الواجهاتالتنفيذ الضمنيالواجهة كعقدالواجهة الفارغة (any)تأكيد النوع (Type Assertion)تبديل النوع (Type Switch)تركيب الواجهاتواجهات Stringer و Errorمراجعة - حاسبة الأشكال7التغليف
الحقول المصدرة وغير المصدرةالتغليف على مستوى الحزمةدوال الـ Getter والـ Setterإخفاء المعلومات في Goملخص - سجلات الطلاب10الأنواع العامة (Go 1.18+)
مقدمة في الأنواع العامةمعاملات الأنواعقيود الأنواعالـ Structs العامةحل بديل للـ Methods العامةملخص - المجموعات العامة13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Abstract Factoryنمط Observerنمط Strategy2تعمق في الأنواع و Structs
الأنواع الأساسية والمركبةتعريفات الأنواع المخصصةStruct TagsStructs مجهولةStructs متداخلةالقيم الصفرية والافتراضيةمراجعة - دفتر العناوين5التركيب بدلاً من الوراثة
لماذا لا تدعم Go الوراثةأساسيات تضمين الـ Structترقية الـ Methodتضمين عدة Structsالتضمين مقابل التجميعحجب الـ Methods المضمنةملخص - الهيكل الهرمي للموظفين8معالجة الأخطاء و OOP
واجهة errorأنواع الأخطاء المخصصةتغليف الأخطاء (fmt.Errorf)أخطاء Sentinelerrors.Is() و errors.As()Panic و Defer و Recoverملخص - File Parser11المكتبة القياسية والبرمجة كائنية التوجه (OOP)
io.Reader و io.Writerواجهة sort.Interfaceواجهة fmt.Stringerencoding/json مع Structsواجهة http.Handlerمراجعة - نماذج REST API14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبMiddleware كنمط مزين3المؤشرات والذاكرة
أساسيات المؤشرات في Goالمؤشرات إلى الـ Structsالتمرير بالقيمة مقابل التمرير بالمرجعدالة ()newGarbage Collection في Goملخص - بناء Linked List6تعدد الأشكال في Go
تعدد الأشكال عبر InterfacesDuck Typing في Goقواعد استيفاء الـ Interfaceمجموعات متعددة الأشكالحقن التبعيةمراجعة - معالج الدفع9التزامن والبرمجة كائنية التوجه (OOP)
أساسيات الـ Goroutinesالقنوات (Channels) والاتصالالقنوات المخزنة (Buffered) مقابل غير المخزنةجملة Selectsync.Mutex و sync.RWMutexsync.WaitGroupتصميم الـ Structs الآمنة للخيوط (Thread-Safe)مراجعة - Worker Pool