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); }