Event Bus ونمط Observer في Go (Golang)
في عالم التطوير الحديث بلغة Go (Golang)، غالبًا ما تنشأ الحاجة إلى تنظيم تفاعل ضعيف الاقتران بين مكونات التطبيق. هناك طريقتان تقليديتان — نمط Observer وتنفيذه الأكثر تقدمًا — Event Bus (ناقل الأحداث). تسمح هذه الآليات لأجزاء من البرنامج بالاستجابة لإجراءات الأجزاء الأخرى دون إنشاء تبعيات صلبة.
في هذه المقالة، سنشرح كيفية تنفيذ Event Bus بلغة Go النقية دون تبعيات خارجية، وسننظر في نمط Observer، وسنقدم أمثلة على الكود للمعالجة المتزامنة وغير المتزامنة للأحداث.
ما هو نمط Observer؟
نمط Observer (المراقب) هو نمط تصميم سلوكي ينشئ آلية اشتراك تسمح لكائنات بمراقبة الأحداث التي تحدث في كائنات أخرى والاستجابة لها.
العناصر الأساسية:
- Subject (الموضوع) — الكائن الذي يولد الأحداث.
- Observer (المراقب) — الكائن الذي يشترك في الأحداث.
- Event (الحدث) — البيانات المنقولة من الموضوع إلى المراقبين.
ما هو Event Bus؟
Event Bus هو ناقل مركزي يدير نقل الأحداث بين الناشرين (publishers) والمشتركين (subscribers). على عكس Observer التقليدي، حيث يخزن الموضوع مباشرة قائمة المراقبين، يعمل Event Bus كوسيط. وهذا يجعل النظام أكثر مرونة وقابلية للتوسع.
تنفيذ Event Bus بلغة Go
لا تحتوي Go على تنفيذ مدمج لـ Event Bus، ولكن يمكن كتابته بسهولة باستخدام goroutines والقنوات والواجهات. دعنا نلقي نظرة على تنفيذ بسيط ولكنه كامل.
1. تعريف الأنواع الأساسية
// Event يمثل بنية الحدثtype Event struct { Name string Data interface{}}
// EventHandler — دالة معالجة الحدثtype EventHandler func(Event)2. إنشاء Event Bus
type EventBus struct { handlers map[string][]EventHandler mu sync.RWMutex}
// NewEventBus ينشئ ناقل أحداث جديدfunc NewEventBus() *EventBus { return &EventBus{ handlers: make(map[string][]EventHandler), }}
// Subscribe يشترك بمعالج في حدثfunc (eb *EventBus) Subscribe(name string, handler EventHandler) { eb.mu.Lock() defer eb.mu.Unlock() eb.handlers[name] = append(eb.handlers[name], handler)}
// Publish ينشر حدثًا بشكل متزامنfunc (eb *EventBus) Publish(name string, data interface{}) { eb.mu.RLock() defer eb.mu.RUnlock() if handlers, ok := eb.handlers[name]; ok { event := Event{Name: name, Data: data} for _, handler := range handlers { handler(event) } }}3. النشر غير المتزامن
للمعالجة غير المتزامنة، يمكن تشغيل كل معالج في goroutine منفصلة:
// PublishAsync ينشر حدثًا بشكل غير متزامنfunc (eb *EventBus) PublishAsync(name string, data interface{}) { eb.mu.RLock() defer eb.mu.RUnlock() if handlers, ok := eb.handlers[name]; ok { event := Event{Name: name, Data: data} for _, handler := range handlers { go handler(event) } }}مثال على الاستخدام
دعنا نرى كيفية استخدام Event Bus الخاص بنا في تطبيق حقيقي. لنفترض أن لدينا نظامًا يسجل إجراءات المستخدم ويرسل الإشعارات.
func main() { bus := NewEventBus()
// الاشتراك في حدث "user.login" bus.Subscribe("user.login", func(e Event) { userData := e.Data.(map[string]string) fmt.Printf("سجل: المستخدم %s قام بتسجيل الدخول", userData["username"]) })
// الاشتراك في نفس الحدث للإشعارات bus.Subscribe("user.login", func(e Event) { userData := e.Data.(map[string]string) fmt.Printf("إشعار: مرحبًا %s!", userData["username"]) })
// نشر الحدث bus.Publish("user.login", map[string]string{ "username": "john_doe", })
// النشر غير المتزامن bus.PublishAsync("user.login", map[string]string{ "username": "jane_doe", })
// انتظار اكتمال المعالجة غير المتزامنة time.Sleep(100 * time.Millisecond)}مزايا استخدام Event Bus
- فصل الاهتمامات: لا يحتاج الناشرون إلى معرفة المشتركين والعكس صحيح.
- قابلية التوسع: يمكن إضافة مشتركين جدد بسهولة دون تغيير الكود الموجود.
- المرونة: يمكن تنفيذ المعالجة المتزامنة وغير المتزامنة بسهولة.
- سهولة الاختبار: يمكن اختبار كل مكون بشكل مستقل.
الخلاصة
Event Bus ونمط Observer هما أداتان قويتان لبناء تطبيقات قابلة للتوسع والصيانة بلغة Go. التنفيذ المقدم بسيط ولكنه يغطي الاحتياجات الأساسية. يمكن توسيعه بإضافة ميزات مثل التصفية، والأولوية، والمعالجة المضمونة، وغيرها.
استخدم Event Bus عندما تحتاج إلى فصل قوي بين المكونات، واختر Observer عندما تريد تحكمًا أكثر دقة في التفاعلات بين الكائنات.