I'm developing a mediator for an MVVM application. This application lets employees file tickets with IT support to ask for help. The mediator communicates messages and triggers callbacks between ViewModels. Here's a description of it's architecture :
The messages between the ViewModels
The messages are stored in an enumerator :
public enum ViewModelMessages
{
UserCreated,
ManagerChanged,
TicketCreated,
TicketDeleted,
TicketRefusedByIT,
CommentAppendedToTicket,
TicketCompleted,
StatisticsFileOutputted
};
This list of messages could get a lot longer than that as I continue development.
The Mediator
The mediator is a singleton that can register multiple callbacks for each message and triggers them when the message is emitted. These callbacks can take no parameters or a single object parameter of any given type :
public sealed class Mediator {
#region Singleton definition
private static readonly Lazy<Mediator> lazy = new Lazy<Mediator>(() => new Mediator());
public static Mediator Instance {
get {
return lazy.Value;
}
}
private Mediator() {
}
#endregion
private MultiDictionary<ViewModelMessages, Action> registeredCallbacksWithoutParameter = new MultiDictionary<ViewModelMessages, Action>();
private MultiDictionary<ViewModelMessages, Action<object>> registeredCallbacksWithParameter = new MultiDictionary<ViewModelMessages, Action<object>>();
public void Register(ViewModelMessages message, Action callback) {
registeredCallbacksWithoutParameter[message] += callback;
}
public void Register<ParameterType>(ViewModelMessages message, Action<ParameterType> callback) {
registeredCallbacksWithParameter[message] +=
(parameter) => callback((ParameterType)parameter);
}
public void Unregister(ViewModelMessages message, Action callback) {
registeredCallbacksWithoutParameter[message] -= callback;
}
public void Unregister(ViewModelMessages message, Action<object> callback) {
registeredCallbacksWithParameter[message] -= callback;
}
public void Notify(ViewModelMessages message) {
foreach(Action callback in registeredCallbacksWithoutParameter[message]) {
callback();
}
}
public void Notify(ViewModelMessages message, object parameter) {
foreach(Action callback in registeredCallbacksWithoutParameter[message]) {
callback();
}
foreach(Action<object> callback in registeredCallbacksWithParameter[message]) {
callback(parameter);
}
}
}
The MultiDictionary
The callbacks are stored in a MultiDictionary
: a regular dictionary where each entry can store a hashset :
public class MultiDictionary<keyType, contentType> : IDictionary<keyType, AddableHashSet<contentType>> {
private Dictionary<keyType, AddableHashSet<contentType>> dictionary = new Dictionary<keyType, AddableHashSet<contentType>>();
public AddableHashSet<contentType> this[keyType key] {
get {
EnsureKey(key);
return dictionary[key];
}
set {
EnsureKey(key);
dictionary[key] = value;
if(dictionary[key].Count == 0) {
dictionary.Remove(key);
}
}
}
private void EnsureKey(keyType key) {
if(!ContainsKey(key)) {
dictionary.Add(key, new AddableHashSet<contentType>());
}
else {
if(dictionary[key] == null)
dictionary[key] = new AddableHashSet<contentType>();
}
}
#region IDictionary interface implementation via the dictionnary member variable
public ICollection<keyType> Keys => dictionary.Keys;
public ICollection<AddableHashSet<contentType>> Values => dictionary.Values;
public int Count => dictionary.Count;
public bool IsReadOnly => ((IDictionary<keyType, AddableHashSet<contentType>>)dictionary).IsReadOnly;
public void Add(keyType key, AddableHashSet<contentType> value) => dictionary.Add(key, value);
public void Add(KeyValuePair<keyType, AddableHashSet<contentType>> item) => throw new NotImplementedException();
public void Clear() => dictionary.Clear();
public bool Contains(KeyValuePair<keyType, AddableHashSet<contentType>> item) => throw new NotImplementedException();
public bool ContainsKey(keyType key) => dictionary.ContainsKey(key);
public void CopyTo(KeyValuePair<keyType, AddableHashSet<contentType>>[] array, int arrayIndex) => throw new NotImplementedException();
public IEnumerator<KeyValuePair<keyType, AddableHashSet<contentType>>> GetEnumerator() => dictionary.GetEnumerator();
public bool Remove(keyType key) => dictionary.Remove(key);
public bool Remove(KeyValuePair<keyType, AddableHashSet<contentType>> item) => throw new NotImplementedException();
public bool TryGetValue(keyType key, out AddableHashSet<contentType> value) => dictionary.TryGetValue(key, out value);
IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException();
#endregion
}
The number of callbacks per message will potentially be between 0 and 10, but it could potentially go a lot higher than that. At this stage I still don't know how high so I preferred to use a hashet instead of a list to avoid it being a bottleneck later on.
The hashset
The AddableHashSet
is a hashset with the +
and -
operators overloaded to simplify the syntax in the Mediator: using +=
and -=
follows the event += eventhandler()
pattern.
public class AddableHashSet<Type> : HashSet<Type> {
public static AddableHashSet<Type> operator +(AddableHashSet<Type> hashSet, Type value) {
hashSet.Add(value);
return hashSet;
}
public static AddableHashSet<Type> operator -(AddableHashSet<Type> list, Type value) {
if(list.Contains(value)) {
list.Remove(value);
}
return list;
}
public AddableHashSet() {
}
}
What do you think?
This is my first implementation of the mediator pattern ever. Any suggestions would be greatly appreciated!