Monday, February 21, 2011

My take on MVVM, I like to add a "C" so we get MVVMC

There is a lot on the web about the Model-View-ViewModel (MVVM) pattern. To me, overall, it seems like a great way to go for developing testable, extensible user interfaces. However, I have run across a few areas where I'm not in total agreement, and other areas where I just think MVVM blogs just gloss over details of things that I think are really important. In my next few blog posts, I hope to outline how I see MVVM and how I have chosen to implement it. I may not be correct in everything, but I hope to present a direction to go for development.

My ultimate goals are to develop code that is consistent, easy to read and follow, and hopefully automatically testable via unit testing or other automation.

The first thing I propose doing is removing control logic from the ViewModel. In most MVVM implementations, you see the VM containing the control logic, for example, if a "Save" button is clicked, we see the VM responding to the command and writing the data to the database. I don't like this. It's not that having it in the VM is a bad thing, it is encapsulated and away from the view. But to me the VM should be light weight, responsible only for transforming model data into a data format easily digestible by the view and for gathering user input back from the view.

By moving control logic, validation, etc. into a controller class, this allows the view model to focus on it's job, makes it easier to read, and I think makes it easier to test the controller.

Here is a diagram of my structure.  Unlike most MVVM models, I take into account using MVVM on custom controls as well as Main UI windows.   Designing a custom control (that derives from User Control) and using the MVVM pattern requires departure from the traditional MVVM pattern.

In looking at the model, we see a departure from the classic MVVM model where the view only accesses only the ViewModel.  If the setup is for a top-level window, and not a control, then things become just like the classic MVVM model.

But when making a user control, their are things that belong to the view in the WPF world and we really need them to notify the controller.  Dependency properties are a prime example of this.  They are attached to the code-behind of an XAML file, which really belongs to the view.  In this case, the view needs to inform the controller module that a dependency property has been updated and let the controller respond appropriately.  And the controller needs to inform the view when a dependency properties value should change.  If we are not working on a user control, then everything goes back to normal MVVM where the view has no knowledge of the controller or view model.

However, if we look at a dependency diagram, I think the view have knowledge of the controller is OK.   After all, dependencies still only go one way and this is fine.  It still allows us to replace the view with a unit test and not have anything break.

It is this important that dependencies flow one way.  This is critical for being able to test and for a clean architecture.  The View has knowledge of the ViewModel and possible the Controller.  The Controller and possible the ViewModel has knowledge of the Model.  In this model, the View can be replaced without the knowledge of any of the underlying layers.

Messaging up the chain takes place via events.  If the ViewModel needs to notify the View of changes, this is accomplished by raising an event and allowing the View to listen for it.  The WPF way of doing this is to use the INotifyPropertyChanged interface. 

 It is critical to use interfaces whenever possible.  An interface allows us to swap out components with testable components.  An interface between the ViewModel / Controller and Model allows us to unit test the ViewModel / Controller by creating a mock model. 

In developing the ViewModel and Controller, it is critical that these classes be separated from any UI components, including opening new windows.  Every action within the view model and the controller should be completely contained.  Opening a new window would be instantiating another view, which violates the requirement that the ViewModel and Controller have no knowledge of the View.  Additionally moving things on and off the UI thread is something that belongs in the domain of the View.  However, often it is the Controller that makes this move.

In order to accomplish application tasks, such as opening new windows and putting things onto the UI thread, interfaces should be created to concrete classes responsible for operating in the View space.  This configuration allows these objects to be mocked out during unit testing.  This will be the subject of a future blog post.

I hope to put some example code up soon as well. 

No comments:

Post a Comment