The Fat ViewModel

If someone asked you, what are the building blocks of MVVM, what would you say? Models, Views, and ViewModels, of course. It’s in the name. But really, there are two others:

  • Models
  • Views
  • ViewModels
  • Commands
  • Events

If Commands and Events aren’t strongly represented in your application, I’d say there is a strong chance it isn’t factored very well.

One problem I see quite often is the Fat ViewModel: a long, bloated class that violates SRP by presenting complex application Commands to the view, and implementing them too.

Symptoms

You can tell you have a Fat ViewModel if any of the following are true:

  • Instead of having a separate class for each Command, your ViewModel uses DelegateCommands (aka RelayCommands) to invoke methods on itself.
  • Your ViewModel has three properties, two commands, and is 300 lines long.
  • Your ViewModel has a large number of services injected into it.
  • Your ViewModel tests include assertions for both presentation and application behaviour.
  • Testing simple UI behaviour — e.g. a button should be disabled after it has been clicked — requires excessive mocking of dependencies.
  • Your ViewModel tests frequently call xyzCommand.Execute().
  • Commands cannot easily be re-used by other ViewModels. Your application is not modular.
  • ViewModels update their state directly, instead of listening for global application Events.

Instead, if you extract Commands into separate classes, that publish their results via Events, you will benefit by having:

  • Skinny and light ViewModels.
  • Commands that can be developed and tested in isolation.
  • Commands that encapsulate and mask complex logic.
  • Separation of presentation logic (ViewModels) from application logic (Commands).
  • A catalog of clearly defined and reusable Commands that be composed into new ViewModels.

Whither DelegateCommand?

From the cases I have seen, I have no doubt that DelegateCommands are the primary cause of Fat ViewModels — they encourage developers to implement Commands using local ViewModel methods, which results in poorly-factored code. For this reason, I consider DelegateCommands to be in the same bad code smell category as ServiceLocator: as an anti-pattern, with few legitimate uses. (One is when the Command is entirely self-contained within the ViewModel (i.e. does not collaborate with any other object), the other when genuinely delegating to another object. But in that case, the other object should probably be a Command anyway.)

See also: Fat Controller, God Object.

December 8, 2010

12 Comments

Pingback: DotNetShoutout

Geert van Horrik on December 9, 2010 at 12:11 pm.

Not sure whether having a fat view model is actually a problem. Of course, UI shouldn’t be handled in the view model. But a method is interaction that you want to do with the data of that specific view model.

According to your blog post, it looks like it’s a bad thing to define commands inside a view model, but I don’t think that way. I think it’s worser to “split up” your functionality that should actually be located at one place into places, just because your mind tells you a class is getting too big (of course 300 lines of code for 3 properties and 2 commands is wrong, but you get the point).

Cameron on December 10, 2010 at 4:54 am.

I think I agree with Geert – It’s bad to split common behaviour over multiple classes by breaking out the commands.

In the case of the 300 line VM with 3 properties and 2 commands I’d ask “Can some of those 300 lines be refactored into shared services?”

Jim on December 13, 2010 at 9:25 am.

It’s easy to criticise something. Why not show some examples of what you consider bad and sample code that fulfills the same purpose but in what you would consider good style?

Henke on April 1, 2011 at 9:57 am.

I think mayby its just a definition of what a command is? I like to inject a IDeleteService and then use DelegateCommand like:
..
ICommand DeleteCommand {get;private set;}


ctor
{

DeleteCommand = new DelegateCommand(()=>deleteService.Delete(param1,param2));
}

That doesnt really make my viewmodel fat and I can reuse the deleteservice. In my case I think my service is your command. Why is it a better approach to reuse the command? I think it makes the vm-coding more difficult to inject a command instead of a service.

Richard on April 1, 2011 at 10:14 am.

@ Henke: you can do this, but remember services are stateless, so it’s harder to do things like a command that can only be clicked once (and the button is greyed out). This is much easier to achieve with Command objects which can have their own internal state.

Henke on April 1, 2011 at 1:26 pm.

Thanks for the feedback.

Hey and thanks for the feedback.
Normally for me the service would answer to CanDelete(someParameter) that doesnt mean that it holds state. I often end up in a combo with the service and the vm:
DelegateCommand……()=>CanDelete(param,param) && MyProp != someThing

My point was “Your ViewModel has a large number of services injected into it” is not different from having “a large number of commands injecting into it”.
I think its equal to inject a service thats used by a command or injecting a command, in fact, most of the time I think that makes the vm easier to understand and the service easier to reuse than a command.

In the special case of a command that only should execute once (and not that the service cannot execute because of some reason that occured the first time) I would defenitly consider making some sort of delegatecommand that only execute once and held its own state, but I would still inject the service.
/H

Mark on May 30, 2011 at 5:44 pm.

I really like this article. I’m relatively new to Silverlight and MVVM, and this is just what I was looking for! Thanks for sharing!

Rob Lyndon on December 5, 2011 at 5:26 pm.

Interesting article. I think some of the MVVM frameworks out there tend to push developers in the direction of a fat viewmodel. My viewmodels have certainly been on the pies, and I’ve got a feeling that’s causing me to suck up more memory than is absolutely necessary.

Leave a Reply