Event Bus ونمط Observer في C# (.NET): التنفيذ والأمثلة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

Event Bus ونمط Observer في C# (.NET): من النظرية إلى التطبيق



في التطوير الحديث باستخدام C# (.NET)، أحد المبادئ الأساسية هو الاقتران الضعيف (loose coupling) بين مكونات النظام. يوفر نمط Observer (المراقب) وتطوره — Event Bus (ناقل الأحداث) — آلية أنيقة لتنظيم التفاعل بين الكائنات دون تبعيات مباشرة. في هذه المقالة، سنشرح بالتفصيل كيفية تنفيذ Event Bus في C# (.NET)، ونستعرض هندسته المعمارية، ونقدم أمثلة عملية.



ما هو نمط Observer؟

يحدد نمط Observer علاقة "واحد إلى متعدد" بين الكائنات: عندما يغير كائن واحد (الموضوع) حالته، يتم إخطار جميع الكائنات التابعة له (المراقبون) وتحديثها تلقائيًا. في C#، يتم تنفيذ هذا النمط تقليديًا من خلال الأحداث (event) والمفوضين.



مشكلة التنفيذ التقليدي

على الرغم من أن الأحداث المضمنة في C# تعمل بشكل جيد، إلا أن لها قيودًا:

  • الارتباط المباشر: يجب أن يعرف الناشر المشتركين بشكل صريح.
  • صعوبة إلغاء الاشتراك: إذا نسي المشترك إلغاء اشتراكه، يحدث تسرب للذاكرة.
  • مرونة محدودة: من الصعب تنظيم المعالجة غير المتزامنة أو تصفية الأحداث.


ما هو Event Bus؟

Event Bus هو ناقل مركزي (وسيط) ينفذ نمط الناشر-المشترك (Publisher-Subscriber). على عكس Observer التقليدي، لا يعرف الناشر والمشترك بعضهما البعض على الإطلاق. يتواصلان فقط من خلال الناقل:

  • الناشر يرسل رسالة (حدثًا) إلى الناقل.
  • الناقل يجد جميع المشتركين في هذا النوع من الأحداث ويخطرهم.
  • المشترك يسجل نفسه لنوع معين من الأحداث ويعالجها.


مزايا Event Bus

  • الاقتران الضعيف: المكونات لا تعتمد على بعضها البعض.
  • قابلية التوسع: سهولة إضافة مشتركين جدد.
  • قابلية الاختبار: يمكن استبدال الناقل بنماذج وهمية (mocks).
  • عدم التزامن: يمكن معالجة الأحداث في خيوط خلفية.


تنفيذ Event Bus في C# (.NET)

لننظر في تنفيذ بسيط ولكنه عملي. سننشئ فئة EventBus التي ستخزن قاموسًا للمشتركين.



public interface IEventBus{    void Publish<TEvent>(TEvent eventData) where TEvent : class;    IDisposable Subscribe<TEvent>(Action<TEvent> handler) where TEvent : class;}

public class EventBus : IEventBus{ private readonly Dictionary<Type, List<Delegate>> _subscribers = new(); private readonly object _lock = new();

public void Publish<TEvent>(TEvent eventData) where TEvent : class { List<Delegate> handlers; lock (_lock) { if (!_subscribers.TryGetValue(typeof(TEvent), out var list)) return; handlers = new List<Delegate>(list); }

foreach (var handler in handlers) { if (handler is Action<TEvent> action) action(eventData); } }

public IDisposable Subscribe<TEvent>(Action<TEvent> handler) where TEvent : class { lock (_lock) { if (!_subscribers.ContainsKey(typeof(TEvent))) _subscribers[typeof(TEvent)] = new List<Delegate>(); _subscribers[typeof(TEvent)].Add(handler); }

return new Unsubscriber(() => { lock (_lock) { if (_subscribers.TryGetValue(typeof(TEvent), out var list)) list.Remove(handler); }

توصيات