Why use MVVM?
Okay, I have been looking into MVVM pattern, and each time I have previously tried looking into it, I gave up for a number of reasons:
- Unnecessary Extra Long Winded Coding
- No apparent advantages for coders (no designers in my office. Currently only myself soon to be another coder)
- Not a lot of resources/documentation of good practices! (Or at least hard to find)
- Cannot think of a single scenario where this is advantageous.
I'm about to give up on it yet again, and thought I'd ask to see if someone answer the reasons above.
I honestly can't see an advantage of using this for a single/partner coding. Even in complex projects with 10's of windows. To me the DataSet is a good enough view and binding like in the answer by Brent following question
Could someone show an example of where using MVVM pattern would of saved time when compared to XAML DataBinding.
100% of my binding is done in XAML at the moment. And therefore I don't see the point of the VM as its just extra code behind that I need to write and depend on.
EDIT:
After spending the afternoon researching about MVVM I have finally found something that made me realise the true benefits of it from this answer.
Summary
- The usage of all patterns is situational, and the benefit (if there is any) always lies in reduced complexity.
- MVVM guides us how to distribute responsibilities between classes in a GUI application.
- ViewModel projects the data from the Model into a format that fits the View.
- For trivial projects MVVM is unnecessary. Using only the View is sufficient.
- For simple projects, the ViewModel/Model split may be unnecessary, and just using a Model and a View is good enough.
- Model and ViewModel do not need to exist from the start and can be introduced when they are needed.
When to use patterns and when to avoid them
For a sufficiently simple application every design pattern is overkill. Assume you write a GUI application that displays a single button which when pressed shows "Hello world". In this case, design patterns like MVC, MVP, MVVM all add a lot of complexity, while not adding any value whatsoever.
In general, it is always a bad decision to introduce a design pattern just because it somewhat fits. Design patterns should be used to reduce complexity, either by directly reducing overall complexity, or by replacing unfamiliar complexity with familiar complexity. If the design pattern cannot reduce complexity in either of these 2 ways, do not use it.
To explain familiar and unfamiliar complexity, take the following 2 sequences of characters:
- "D.€|Ré%dfà?c"
- "CorrectHorseBatteryStaple"
While the second character sequence is twice the length of the first sequence, it's easier to read, faster to write, and easier to remember than the first sequence, all because it's more familiar. The same holds true for familiar patterns in code.
This problem gains another dimension when you consider that familiarity depends on the reader. Some readers will find "3.14159265358979323846264338327950" easier to remember than either of the above passwords. Some won't. So if you want to use a flavor of MVVM, try to use one that mirrors its most common form in the specific language and framework you're using.
MVVM
That said, let's dive into the topic of MVVM by means of an example. MVVM guides us how to distribute responsibilities between classes in a GUI application (or between layers - more about this later), with the goal of having a small number of classes, while keeping the number of responsibilities per class small and well defined.
'Proper' MVVM assumes at least a moderately complex application, which deals with data it gets from "somewhere". It may get the data from a database, a file, a web service, or from a myriad of other sources.
Example
In our example, we have 2 classes View
and Model
, but no ViewModel
. The Model
wraps a csv-file which it reads on startup and saves when the application shuts down, with all changes the user made to the data. The View
is a Window class that displays the data from the Model
in a table and lets the user edit the data. The csv content might look somewhat like this:
ID, Name, Price
1, Stick, 5$
2, Big Box, 10$
3, Wheel, 20$
4, Bottle, 3$
New Requirements: Show price in Euro
Now we are asked to make a change to our application. The data consists of a 2-dimensional grid which already has a "price" column, containing a price in USD. We need to add a new column which shows prices in Euro in addition to those in USD, based on a predefined exchange rate. The format of the csv-file must not change because other applications work with the same file, and these other applications are not under our control.
A possible solution is to simply add the new column to the Model
class. This isn't the best solution, because the Model
saves all the data it exposes to the csv - and we do not want a new Euro price column in the csv. So the change to the Model
would be non-trivial, and it would also be harder to describe what the Model class does, which is a code smell.
We could also make the change in the View
, but our current application uses data binding to display the data directly as provided by our Model
class. Because our GUI framework doesn't allow us to introduce an additional calculated column in a table when the table is data bound to a data source, we would need to make a significant change to the View
to make this work, making the View
a lot more complex.
Introducing the ViewModel
There is no ViewModel
in the application because until now the Model
presents the data in exactly the way the Csv needs it, which is also the way the View
needed it. Having a ViewModel
between would have been added complexity without purpose. But now that the Model
no longer presents the data in the way the View
needs it, we write a ViewModel
. The ViewModel
projects the data of the Model
in such a way that the View
can be simple. Previously the View
class subscribed to the Model
class. Now the new ViewModel
class subscribes to the Model
class, and exposes the Model
's data to the View
- with an extra column displaying the price in Euros. The View
no longer knows the Model
, it now only knows the ViewModel
, which from the point of the View
looks the same as the Model
did before - except that the exposed data contains a new read only column.
New requirements: different way to format the data
The next customer request is that we should not display the data as rows in a table, but instead display the information of each item (a.k.a. row) as a card/box, and display 20 boxes on the screen in a 4x5 grid, showing 20 boxes at a time. Because we kept the logic of the View
simple, we simply replace the View
entirely with a new class that does as the customer desires. Of course there is another customer who preferred the old View
, so we now need to support both. Because all of the common business logic already happens to be in the ViewModel
that is not much of an issue. So we can solve this by renaming the View class into TableView
, and writing a new CardView
class that shows the data in a card format. We will also have to write some glue code, which might be a oneliner in the startup function.
New requirements: dynamic exchange rate
The next customer request is that we pull the exchange rate from the internet, rather than using a predefined exchange rate. This is the point where we revisit my earlier statement about "layers". We don't change our Model
class to provide an exchange rate. Instead we write (or find) a completely independent additional class that provides the exchange rate. That new class becomes part of the model layer, and our ViewModel
consolidates the information of the csv-Model and the exchange-rate-Model, which it then presents to the View
. For this change the old Model class and the View class do not even have to be touched. Well, we do need to rename the Model class to CsvModel
and we call the new class ExchangeRateModel
.
If we hadn't introduced the ViewModel when we did but had instead waited until now to do so, the amount of work to introduce the ViewModel now would be higher because we need to remove significant amounts of functionality from both of the View
and the Model
and move the functionality into the ViewModel
.
Afterword on Unit Tests
The primary purpose of MVVM is not that the code in the Model and the ViewModel can be put under Unit Test. The primary purpose of MVVM is that the code is broken up into classes with a small number of well defined responsibilities. One of several benefits of having code consisting of classes with a small number of well defined responsibilities is that it is easier to put the code under Unit Test. A much larger benefit is that the code is easier to understand, maintain, and modify.
Implementing patterns and following best practices often feel like pointless pursuits but you will become a convert when months down the road your boss asks you to add or tweak a feature. Using MVVM (and patterns in general) you will actually be able to follow your own code and fulfill the requirement in a few hours or days at worst instead of weeks or months. (This change is likely to be just a few lines of code rather than spending weeks trying to figure out how you did what you did in the first place before even trying to add new features.)
Follow up: Patterns and best practices will actually slow down initial development and that's often a hard sell to management and engineering alike. The payback (ROI in biz terms) comes from having well-structured code that is actually maintainable, scalable and extensible.
As an example, if you follow MVVM properly, you should be able to make very drastic changes to the display logic, such as swapping out an entire view, with no impact on the data and biz logic.
A thought about using datasets for your model: (I have actually fallen for this too.) Datasets seem like a perfectly valid way to move around model data in an application. The problem comes in with how you identify the data items. Because your data is stored in rows and columns you have to perform look-ups by column name or index as well as having to filter for a particular row. These bits of logic mean having to use magic strings and numbers in wiring logic in your application. Using a typed dataset would alleviate some of this issue but not completely. Using typed datasets you'd be moving away from MVVM and into tighter coupling between the UI and the data source.
It helps you seperating GUI and program logic; mixing them can result in very hard to maintain applications, especially when your project grows with time.