为什么使用 MVVM?

好吧,我一直在研究 MVVM 模式,每次我尝试研究它,我放弃了很多原因:

  1. 不必要的超长缠绕编码
  2. 对程序员来说没有明显的优势(我的办公室里没有设计师。目前只有我自己很快会成为另一个程序员)
  3. 没有很多关于良好实践的资源/文档! (或者至少很难找到)
  4. 无法想到任何一种情况下这是有利的。

我打算再次放弃这个想法,我想看看有没有人能回答我上面的问题。

说实话,我看不出在单个/合作伙伴编码中使用这种方法有什么优势。即使在有10个窗口的复杂项目中。对我来说,DataSet 是一个很好的视图和绑定,就像 有个问题后面的 布伦特的答案一样

有人能举个例子说明使用 MVVM 模式与 XAML DataBinding 相比在哪些方面可以节省时间吗。

目前我的绑定100% 是用 XAML 完成的。因此,我不认为 VM 的意义在于它只是我需要编写和依赖的后面的额外代码。

编辑:
在花了一个下午的时间研究 MVVM 之后,我终于发现了一些东西,让我意识到这个 回答的真正好处。

39890 次浏览

There are a lot of good things about MVVM, but maybe the most important thing is the ability to test your code (Unit testing the ViewModels).

The lack of connection between the view and viewmodel really helps the loose coupling as well. It becomes really easy to reuse the components you code.

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.

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 is easier to work with designers (not programmers, just people using Blend)
  • Code is testable (unit tests)
  • It is much easier to change view without messing with the rest of the code
  • While you are developing UI you can mock model and develop your interface without running real service (just using mock data from model). Then you just flip flag and connect to the service.

You'll be happy in the long run if you use a pattern like MVVM for all the reasons the others have posted. Remember, you don't need to follow the pattern requirements word-for-word, just make sure you have good separation between your window (View) and your logic (code-behind).

From Josh Smith's article on MVVM:

In addition to the WPF (and Silverlight 2) features that make MVVM a natural way to structure an application, the pattern is also popular because ViewModel classes are easy to unit test. When an application's interaction logic lives in a set of ViewModel classes, you can easily write code that tests it. In a sense, Views and unit tests are just two different types of ViewModel consumers. Having a suite of tests for an application's ViewModels provides free and fast regression testing, which helps reduce the cost of maintaining an application over time.

For me, this is the most important reason to use MVVM.

Before, I would have controls which mashed the view and viewmodel together. But a view essentially has mouse and keyboard events as input, and drawn pixels as output. How do you unit test something like that? MVVM makes this problem go away as it separates the untestable view from the testable viewmodel, and keeps the view layer as thin as possible.

I'm still coming to grips with the pattern myself, but I do think it's valuable. The biggest challenge right now is that the approach is still quite new and therefore there is a lot of confusion and certain key components of the pattern are still awkward to implement. I've discovered a few things that have helped me a lot to make cleaner implementations of the pattern:

  1. I make heavy use of the RelayCommand from Josh Smith's MVVM Foundation. This makes the binding from View to ViewModel via Commands much cleaner.

  2. I use AOP to ease the pain of implementing INotifyPropertyChanged. I'm currently using Postsharp, though I believe there are other tools that can do this. If I hadn't discovered this, I probably would've given up by now, as the boilerplate code to implement it manually was really bugging me.

  3. I've had to invert my approach to how the software is implemented. Instead of having a dictator class that tells all of its minions what to do, which in turn use their minions, my software becomes more a matter of loosely coupled services that say:

    • This is what I know how to do

    • These are the things I need to have done

When you begin to structure your code in this way and use tools that make it easy to wire up the dependencies (there are a wide range of IoC frameworks to choose from), I've found it eases some of the awkwardness of MVVM, as you can reduce the boilerplate code associated with injecting the Models into the ViewModels and locating various View Services (such as displaying file dialogs) for your ViewModels to consume.

It's a huge investment to learn this different approach and, as with any major shift in implementation, productivity is much lower when you first start using it. However, I'm beginning to see some light at the end of the tunnel and I believe that, once I've mastered the messy details, my applications will be cleaner and much more maintainable.


To address the question about INotifyPropertyChanged via Postsharp, I use an Aspect based on the example here. I've customized it a bit for my use, but that gives you the gist of it. With this, I just tag the class [NotifyPropertyChanged] and all of the public properties will have the pattern implemented in their setters (even if they are auto-property setters). It feels much cleaner to me, as I no longer have to worry about whether I want to take the time to make the class implement INotifyPropertyChanged. I can just add the attribute and be done with it.

From here:

Why should you, as a developer, even care about the Model-View-ViewModel pattern? There are a number of benefits this pattern brings to both WPF and Silverlight development. Before you go on, ask yourself:

  • Do you need to share a project with a designer, and have the flexibility for design work and development work to happen near-simultaneously?
  • Do you require thorough unit testing for your solutions?
  • Is it important for you to have reusable components, both within and across projects in your organization?
  • Would you like more flexibility to change your user interface without having to refactor other logic in the code base?

If you answered "yes" to any of these questions, these are just a few of the benefits that using the MVVM model can bring for your project.

Read the introduction into MVVM in this article

In 2005, John Gossman, currently one of the WPF and Silverlight Architects at Microsoft, unveiled the Model-View-ViewModel (MVVM) pattern on his blog. MVVM is identical to Fowler's Presentation Model, in that both patterns feature an abstraction of a View, which contains a View's state and behavior. Fowler introduced Presentation Model as a means of creating a UI platform-independent abstraction of a View, whereas Gossman introduced MVVM as a standardized way to leverage core features of WPF to simplify the creation of user interfaces. In that sense, I consider MVVM to be a specialization of the more general PM pattern, tailor-made for the WPF and Silverlight platforms.

..

Unlike the Presenter in MVP, a ViewModel does not need a reference to a view. The view binds to properties on a ViewModel, which, in turn, exposes data contained in model objects and other state specific to the view. The bindings between view and ViewModel are simple to construct because a ViewModel object is set as the DataContext of a view. If property values in the ViewModel change, those new values automatically propagate to the view via data binding. When the user clicks a button in the View, a command on the ViewModel executes to perform the requested action. The ViewModel, never the View, performs all modifications made to the model data. The view classes have no idea that the model classes exist, while the ViewModel and model are unaware of the view. In fact, the model is completely oblivious to the fact that the ViewModel and view exist. This is a very loosely coupled design, which pays dividends in many ways, as you will soon see.

Also the article explains why to use these gui patterns:

It is unnecessary and counterproductive to use design patterns in a simple "Hello, World!" program. Any competent developer can understand a few lines of code at a glance. However, as the number of features in a program increases, the number of lines of code and moving parts increase accordingly. Eventually, the complexity of a system, and the recurring problems it contains, encourages developers to organize their code in such a way that it is easier to comprehend, discuss, extend, and troubleshoot. We diminish the cognitive chaos of a complex system by applying well-known names to certain entities in the source code. We determine the name to apply to a piece of code by considering its functional role in the system.

Developers often intentionally structure their code according to a design pattern, as opposed to letting the patterns emerge organically. There is nothing wrong with either approach, but in this article, I examine the benefits of explicitly using MVVM as the architecture of a WPF application. The names of certain classes include well-known terms from the MVVM pattern, such as ending with "ViewModel" if the class is an abstraction of a view. This approach helps avoid the cognitive chaos mentioned earlier. Instead, you can happily exist in a state of controlled chaos, which is the natural state of affairs in most professional software development projects!

I agree that using MVVM put more weight on our shoulders by writing ore code, but look at the bright side where everything is isolated then if you are a designer so you can design your program and other can code it for you and other does the Database layer for you,look how maintainable enviroment you will be in especially in large enterprise applications if you would not use MVVM ,then the maintainance is almost killing.... I myself used it when developing ERP solution now the maintainance is pretty straight forward because of that isolation level

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. View1 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.

Benefits of MVVM

  1. Reduced complexity.
  2. Isolation of Designing and Development.
  3. Dependency injection.
  4. Major advantage is when you have a Well MVVM structured Windows Phone application and want to develop same for Windows Metro Desktop, Only thing u want to concentarte on design as the same view model can be used as it is.

Hope it helps.

MVVM is really excessive code.

So what benefits does the MVVM provide?

It's just Seperation of concerns not more. You could also write the ViewModel logic into the Controller. The ViewModel is just repsonsible for doing a conversion (for example and object into a string). By using MVVM you use more object-oriented programming style. By writing the conversion logic into the Controller you use more functional programming style.

So it comes down to having more code with better readability or less code with big Controller files. In conclusion you cannot say you have to use MVVM, because it is better then MVC or so, it is just a personal preference.

Just to be clear why I mention a Controller: MVVM also has Controller-code somewhere. I have no idea why there's a wide consensus to leave the C.