Posted by: Zeeshan Amjad | July 31, 2013

Using Mediator to communicate between user controls: Part 8


The mediator pattern we created here works quite well for most of my scenario where i am suppose to use int message not string messages. Changing the class is not a big deal, i can easily change the source of Mediator class to use int and create Dictionary of int instead of string. But this raise a question, what if in future i want to use a different type? Do I have to do it again. Why not make it such a way that it can be used with more than one type without changing the code, or at best minimal changes of code.

I decided to make message generic just like i did with Action parameter. Its quite simple solution and can be done in few minutes. Here is a new version of Mediator class that uses generic messages.

Code Snippet
namespace MVVMBase
{
    using System;
    using System.Collections.Generic;

    public sealed class Mediator<TMessage>
    {
        private Dictionary<TMessage, List<WeakReferenceWrapper>> actions =
             new Dictionary<TMessage, List<WeakReferenceWrapper>>();

        public Mediator()
        {
        }

        public void Register<TParameter>(TMessage message, object receiver, Action<TParameter> action)
        {
            if (message == null)
            {
                throw new ArgumentNullException("message");
            }

            if (action == null)
            {
                throw new ArgumentNullException("action");
            }

            lock (this.actions)
            {
                if (!this.actions.ContainsKey(message))
                {
                    this.actions[message] = new List<WeakReferenceWrapper>();
                }

                this.actions[message].Add(new WeakReferenceWrapper(receiver, action));
            }
        }

        public void Unregister<TParameter>(TMessage message, object receiver, Action<TParameter> action)
        {
            if (message == null)
            {
                throw new ArgumentNullException("message");
            }

            if (action == null)
            {
                throw new ArgumentNullException("action");
            }

            lock (this.actions)
            {
                if (this.actions.ContainsKey(message))
                {
                    List<WeakReferenceWrapper> actionlist = this.actions[message];

                    actionlist.Remove(new WeakReferenceWrapper(receiver, action));

                    if (actionlist.Count == 0)
                    {
                        this.actions.Remove(message);
                    }
                }
            }
        }

        public void Send<TParameter>(TMessage message, TParameter parameter)
        {
            if (message == null)
            {
                throw new ArgumentNullException("message");
            }

            lock (this.actions)
            {
                if (this.actions.ContainsKey(message))
                {
                    List<WeakReferenceWrapper> actionslist = this.actions[message];

                    foreach (var action in actionslist)
                    {
                        action.Action.DynamicInvoke(parameter);
                    }
                }
            }
        }
    }
}

So far so good, but now I have to change the Mediator property too in all of my ViewModel and UserControl classes to use the type. Let’s first create a Mediator object with additional information.

Code Snippet
Mediator = new Mediator<int>();

Mediator.Register<object>(1, myvm, myvm.Notify);

 

And here is a change we are suppose to do in ViewModel or UserControl code behing class.

Code Snippet
public Mediator<int> Mediator
{
    get;
    set;
}

 

Remember the type should be the same, otherwise you will get a compilation error. Let’s force it by making a contract that every class that need to send message or be notified used a proper signature by introducing interface. We can make an interface that does this. Here is a code of our interface

Code Snippet
namespace MVVMBase
{
    interface IMediator<TMessage, TParameter>
    {
        Mediator<TMessage> Mediator
        {
            get;
            set;
        }

        void Send(TMessage message, TParameter parameter);

        void Notify(TParameter parameter);
    }
}

 

In this way we also document that which classes are taking part of this approach. Here is an example of one class that implement this interface.

Code Snippet
public class SettingsViewModel : ViewModelBase, IMediator<int, object>
{

    // other stuff

    public Mediator<int> Mediator
    {
        get;
        set;
    }

    public ICommand GetSettingsCommand
    {
        get;
        set;
    }

    public void Send(int message, object o)
    {
        Mediator.Send<object>(message, o);
    }

    public void Notify(object o)
    {

    }

    private void GetSettings()
    {
        Send(1, "testmessage");
    }
}

 

But this will create one interesting problem. Now we can select the message type, but our message is restricted. If we want to pass different types of messages, then we have to implement IMediator interface with different second generic parameter.

Let’s remove the second parameter from IMediator interface and make it generic only for message type. In this case we have to modify the Send and Nofity methods and and make those generic. Here is updated version of our IMediator interface.

Code Snippet
namespace MVVMBase
{
    public interface IMediator<TMessage>
    {
        Mediator<TMessage> Mediator
        {
            get;
            set;
        }

        void Send<TParameter>(TMessage message, TParameter parameter);

        void Notify<TParameter>(TParameter parameter);
    }
}

 

Here is a usage of our new IMediator interface

Code Snippet
namespace ADFDesktopTool
{
    using System.Collections.ObjectModel;
    using System.Windows.Input;
    using MVVMBase;

    public class SettingsViewModel : ViewModelBase, IMediator<int>
    {
        // other stuff

        public Mediator<int> Mediator
        {
            get;
            set;
        }

        public void Send<TParameter>(int message, TParameter arg)
        {
            Mediator.Send<TParameter>(message, arg);
        }

        public void Notify<TParameter>(TParameter args)
        {

        }

    }
}

 

Let’s make one small change in our Mediator class. Currently our Mediator class can register any class. Let’s make it more specific to register classes which implement IMediator interface only. Here is a new version of our Mediator class.

Code Snippet
namespace MVVMBase
{
    using System;
    using System.Collections.Generic;

    public sealed class Mediator<TMessage>
    {
        private Dictionary<TMessage, List<WeakReferenceWrapper>> actions =
             new Dictionary<TMessage, List<WeakReferenceWrapper>>();

        public Mediator()
        {
        }

        public void Register<TParameter>(TMessage message, IMediator<TMessage> receiver, Action<TParameter> action)
        {
            if (message == null)
            {
                throw new ArgumentNullException("message");
            }

            if (action == null)
            {
                throw new ArgumentNullException("action");
            }

            lock (this.actions)
            {
                if (!this.actions.ContainsKey(message))
                {
                    this.actions[message] = new List<WeakReferenceWrapper>();
                }

                this.actions[message].Add(new WeakReferenceWrapper(receiver, action));
            }
        }

        public void Unregister<TParameter>(TMessage message, IMediator<TMessage> receiver, Action<TParameter> action)
        {
            if (message == null)
            {
                throw new ArgumentNullException("message");
            }

            if (action == null)
            {
                throw new ArgumentNullException("action");
            }

            lock (this.actions)
            {
                if (this.actions.ContainsKey(message))
                {
                    List<WeakReferenceWrapper> actionlist = this.actions[message];

                    actionlist.Remove(new WeakReferenceWrapper(receiver, action));

                    if (actionlist.Count == 0)
                    {
                        this.actions.Remove(message);
                    }
                }
            }
        }

        public void Send<TParameter>(TMessage message, TParameter parameter)
        {
            if (message == null)
            {
                throw new ArgumentNullException("message");
            }

            lock (this.actions)
            {
                if (this.actions.ContainsKey(message))
                {
                    List<WeakReferenceWrapper> actionslist = this.actions[message];

                    foreach (var action in actionslist)
                    {
                        action.Action.DynamicInvoke(parameter);
                    }
                }
            }
        }
    }
}

 

This is more powerful version of mediator than previous version can accept any type of message type and define contract to use with mediator.

The output of the program is same as previous program.

mediator_02_output

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

%d bloggers like this: