Traditional Event Handling
- An event in software application indicates occurrence of an important changes.
- For example change in objects state, components state, application state, etc.
- All software applications from small to large uses events to indicate changes happening to it and can become the backbone for its internal and external communication.
- An event allows loose coupling between two or more objects that wants to communicate.
- It allows sender object (Publisher) to specify what event occurs when it's undergoing change.
- Then, multiple receiver objects (Subscribers) can subscribe to it.
- The publisher object does not directly refer to the subscriber object and will not be aware of their existence and can undergo changes without effecting the subscribers as long as event related interfaces are not changed.
- In traditional event handling, publisher advertises its event(s) and specifies when they will be invoked.
- A Subscriber will then register itself to Publisher event(s).
- As Publisher object undergoes changes in its lifetime it will invoke the event informing the Subscriber(s).
public class EventAggregatorMainApp
{
private readonly IPublisher<int> IntPublisher;
private readonly Subscriber<int> IntSublisher1;
private readonly Subscriber<int> IntSublisher2;
public EventAggregatorMainApp()
{
IntPublisher = new Publisher<int>();
IntSublisher1 = new Subscriber<int>(IntPublisher);
IntSublisher1.Publisher.DataPublisher += Publisher_DataPublisher1;
IntSublisher2 = new Subscriber<int>(IntPublisher);
IntSublisher2.Publisher.DataPublisher += Publisher_DataPublisher2;
IntPublisher.PublishData(10);
}
private void Publisher_DataPublisher1(object sender, MessageArgument<int> e) =>
Console.WriteLine("Subscriber 1 : " + e.Message);
private void Publisher_DataPublisher2(object sender, MessageArgument<int> e) =>
Console.WriteLine("Subscriber 2 : " + e.Message);
}
public class MessageArgument<T> : EventArgs
{
public T Message { get; set; }
public MessageArgument(T message)
{
Message = message;
}
}
public interface IPublisher<T>
{
event EventHandler<MessageArgument<T>> DataPublisher;
void OnDataPublisher(MessageArgument<T> args);
void PublishData(T data);
}
public class Publisher<T> : IPublisher<T>
{
public event EventHandler<MessageArgument<T>> DataPublisher;
public void OnDataPublisher(MessageArgument<T> args) => DataPublisher?.Invoke(this, args);
public void PublishData(T data)
{
MessageArgument<T> message = (MessageArgument<T>)Activator.CreateInstance(typeof(MessageArgument<T>), new object[] { data });
OnDataPublisher(message);
}
}
public class Subscriber<T>
{
public IPublisher<T> Publisher { get; private set; }
public Subscriber(IPublisher<T> publisher)
{
Publisher = publisher;
}
}
Disadvantages of Traditional Event Handling
- If there are multiple Subscribers and Publishers then code become hard to read and debug.
- The Subscriber of the event needs to know the Publisher of an event and refer it directly by event names causing a tight coupling between them.
- The tight coupling will not allow Subscribers and Publishers to change independently of each other.
- Its Subscriber responsibility to register and unregister from an event, its seen many practical scenarios the subscriber typically forgets to unregister causing both subscriber and publisher to be in memory causing memory leaks.
- The Event Aggregator takes care for registering, unregistering and invoking of events loosely coupling Publishers and Subscribers.
- All Publishers and Subscribers of event will know only about the Event Aggregator.
- Intent of the pattern:
- Simplify subscribing and unsubscribing to events
- Decouple publishers from subscribers allowing both to change without effecting the other.
- Reduce wastage of system resources, but may depend on support from development framework or environment.
- Makes it easy to add new events.
- Centralized handling of events.
- Event Aggregator is a good choice when you have lots of objects that are potential event sources.
- Rather than have the observer deal with registering with them all, you can centralize the registration logic to the Event Aggregator.
- As well as simplifying registration, a Event Aggregator also simplifies the memory management issues in using observers.
public static class EventAggregatorMainApp
{
public static void Main()
{
var eventAggregator = new EventAggregator();
SubscriberA subscriber1 = new SubscriberA(eventAggregator);
subscriber1.Subscribe();
SubscriberB subscriber2 = new SubscriberB(eventAggregator);
subscriber2.Subscribe();
Publisher publisher1 = new Publisher(eventAggregator);
publisher1.PublishMessage(new SampleEvent("Sample Message 1"));
Publisher publisher2 = new Publisher(eventAggregator);
publisher2.PublishMessage(new SampleEvent("Sample Message 2"));
subscriber2.Unsubscribe();
publisher1.PublishMessage(new SampleEvent("Sample Message 3"));
publisher2.PublishMessage(new SampleEvent("Sample Message 4"));
}
}
public interface IEventAggregator
{
void Subscribe<TEvent>(Action<TEvent> subscriber);
void Publish<TEvent>(TEvent publishedEvent);
void Unsubscribe<TEvent>(Action<TEvent> subscriber);
}
public class EventAggregator : IEventAggregator
{
private Dictionary<Type, List<object>> _Subscribers = new Dictionary<Type, List<object>>();
readonly static object _Sync = new object();
public void Subscribe<TEvent>(Action<TEvent> subscriber)
{
var eventType = typeof(TEvent);
lock (_Sync)
{
if (_Subscribers.TryGetValue(eventType, out List<object> handlers))
handlers.Add(subscriber);
else _Subscribers.Add(eventType, new List<object> { subscriber });
}
}
public void Publish<TEvent>(TEvent publishedEvent)
{
var eventType = typeof(TEvent);
lock (_Sync)
{
if (_Subscribers.TryGetValue(eventType, out List<object> handlers))
foreach (var handler in handlers.Cast<Action<TEvent>>())
handler?.Invoke(publishedEvent);
}
}
public void Unsubscribe<TEvent>(Action<TEvent> subscriber)
{
var eventType = typeof(TEvent);
lock (_Sync)
{
if (_Subscribers.TryGetValue(eventType, out List<object> handlers) && handlers.Contains(subscriber))
handlers.Remove(subscriber);
}
}
}
public class SampleEvent
{
public string Message { get; set; }
public SampleEvent(string message) => Message = message;
}
public interface IPublisher<TEvent>
{
void PublishMessage(TEvent eventToPublish);
}
public class Publisher : IPublisher<SampleEvent>
{
private IEventAggregator _EventAggregator;
public Publisher(IEventAggregator eventAggregator) => _EventAggregator = eventAggregator;
public void PublishMessage(SampleEvent eventToPublish) => _EventAggregator.Publish(eventToPublish);
}
public interface ISubscriber<TEvent>
{
void Subscribe();
void Unsubscribe();
}
public class SubscriberA : ISubscriber<SampleEvent>
{
private IEventAggregator _EventAggregator;
public SubscriberA(IEventAggregator eve) => _EventAggregator = eve;
public void Subscribe() => _EventAggregator.Subscribe<SampleEvent>(Subscriber);
public void Unsubscribe() => _EventAggregator.Unsubscribe<SampleEvent>(Subscriber);
private static void Subscriber(SampleEvent publishedEvent) => Console.WriteLine("S1: {0}", publishedEvent.Message);
}
public class SubscriberB : ISubscriber<SampleEvent>
{
private IEventAggregator _EventAggregator;
public SubscriberB(IEventAggregator eve) => _EventAggregator = eve;
public void Subscribe() => _EventAggregator.Subscribe<SampleEvent>(Subscriber);
public void Unsubscribe() => _EventAggregator.Unsubscribe<SampleEvent>(Subscriber);
private static void Subscriber(SampleEvent publishedEvent) => Console.WriteLine("S2: {0}", publishedEvent.Message);
}