1
votes

I know that in MVVM pattern (or possibly in any design pattern of this kind) we should keep our layers decoupled. From my understanding it also means, that I should keep my ViewModels separate. I'm having a bit trouble following this rule.

Say - I have a ConversationViewModel and a MessageViewModel - the former needs to create instances of the later. When ConversationViewModel gets notification about incoming message it spawns a new MessageViewModel instance and fills it with data.

The question is - if I create new MessageViewModel instances explicitly in the ConversationViewModel won't it make my app a bit harder to test? I mean - one unit of code is the ConversationViewModel and other is the MessageViewModel - I'd like to test both separate, so when somebody breaks something in the later, test for the former won't be affected. How do I achieve it?

I'm using MVVMLight, so I thought I would register MessageViewModel as an implementation of some interface, and then create a class like MockMessageViewModel implementing the same interface, but used only in tests. Then in the ConversationViewModel I'd ask the IOC container to just give me the registered implementation. Is it a good approach, or am I overreacting? Example code:

public class ViewModelLocator {
    public ViewModelLocator() {
        ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

        if (//in test) {
            SimpleIoc.Default.Register<IMessageViewModel, MockMessageViewModel>();
        }
        else {
            SimpleIoc.Default.Register<IMessageViewModel, MessageViewModel>();
        }
}

public class ConversationViewModel : ViewModelBase {
    public void MessageReceived(string data) {
        //I'm thinking about doing this:
        var vm = SimpleIoc.Default.GetInstance<IMessageViewModel>();
        // instead of doing this
        var vm = new MessageViewModel();

        //do stuff with vm
    }
}
4

4 Answers

1
votes

Whether to use interface bases approach to separate the view models from each other is the design decision based on complexity of your application. If you want to dynamically create instance of IMessageViewModel inside IConvesationViewModel; I would recommend instead of referring to IoC container in your ViewModel class inject a factory for creating IMessageViewModel in the ConversationViewModel constructor. Later you can use this factory to create instances of IMessageViewModel. A simple implementation of factory could be Func delegate.

public class ConversationViewModel 
{
    private Func<IMessageViewModel> _messageViewModelFactory;
    public ConversationViewModel(Func<IMessageViewModel> messageViewModelFactory)
    {
        _messageViewModelFactory = messageViewModelFactory;
    }

    public void MessageReceived(string data) {
        var messageViewModel = _messageViewModelFactory();
    }
}

This way you are exposing dependencies of your ConversationViewModel class through the constrctor instead of hiding them inside the class implementation. The IoC containers like Autofac provide way to inject Func in the constructor when you create object of ConversationViewModel using it.

0
votes

I believe a better way to do that is by using interfaces. You can have both your real and mock ViewModels implement the same interface and use that interface everywhere where you would use a ViewModel class.

0
votes

If it was me and I may not have all the information about your application but I would have a single ViewModel IConversationViewModel. And in the IConversationViewModel I would have a collection of IMessageModel instances. I would not go nesting ViewModels.

0
votes

What you can do is create the MessageViewModel immediately in ViewModelLocator and register for receiving messages in MessageViewModel using the MVVMLight MessengerInstance in its constructor. Something like this:

public class ViewModelLocator
{
    public class ViewModelLocator()
    {
       //creates instance immediately
       SimpleIoc.Default.Register<MessageViewModel>(true);
    }
}

public class MessageViewModel:ViewModelBase
{
     public MessageViewModel()
     {
        MessengerInstance.Register<string>(this,DoSomething);
     }

     public void DoSomething(string data)
     {
             //do stuff
     }
}

public class ConversationViewModel:ViewModelBase
{
     public void MessageReceived(string data)
     {
       MessengerInstance.Send<string>(data);//this will trigger DoSomething in MessageViewModel
     }
}