问题
I know there are already questions on that topic, but the issues there are somewhat specific to other problems and do not offer conclusive answers.
Especially those here: Question1, Question2 and of course Question3 so please do not close this question too fast. They answer there just say "Do this, do that" and not why!
There are people out there that deny the need for a ViewModel
and say that
the "standard" way is to bind to the Model directly. This is what I deny and try to prove with technical arguments.
From my background in MVC
, MVP
, Presentation Model
, it just is natural
to me to use a ViewModel
. Perhaps I miss an important point?
So for me the default is to bind to a ViewModel
, no matter what the Model
is (and no matter if it implements INotifyPropertyChanged
or not).
There are several reasons that I see for binding to ViewModel
s, including
(As mentioned here CodeProject and here on another article)
1. Removing logic from the View
- Make logic unit testable
- reduce code redundance (duplication where needed)
2. Security
- The model contains properties that the user shall not change
- Automatic, but unwanted updates can happen if Binding to model
3. Loose coupling
- If binding directly to the model, there will be a coupling between lower layers and View
- Changing the Model causes changes in all the Views
- The view is not depending on any given model
- Model can be easily generated with EF, some DSL, batch files, on so on
4. Speed of development
- You can start with a
Prototype ViewModel
hierarchy and bind to that - If the model is still under development, you can start with a
Prototype Model
Model
andViewModel
can be developed testdriven, no matter of the View- The
View
can entirely be built by a designer, or developer with strong design background
5. "Tricky synchronizion" is solved
- There are plenty of solutions to any given "tricky synchronization" problem, e.g.
- AutoMapper
- Event system from the model (model fires event, ViewModel subscribes)
6. Equal structure throughout the project
- There are points where have to a ViewModel, like SelectedItem
- Mixing Binding to Model and ViewModel is errorprone
- It is harder for fresh developers to figure out structure of the project
- starting to bring ViewModel later when there is no way around it is messy
7. Scalability
- Lets define: If you do not use a ViewModel, it is not MVVM
- MVVM can be easily adopted to lots of datasources, lots of views
- If you find any performance issues: Lazy loading and caching goes in the ViewModel
8. Separation of concerns
- Getting a grip on complexity is the main problem in software
- The ViewModels sole responsibility is pushing changes
- It is as easy to send notifications to a view as it is to push it to a different process or machine
- The ViewModel, not the View register for change notifications on the model / data source
- the datasource can ignore sending events to the ViewModel that caused the change
On contrary, the guy from the other thread dumps some points, including
If the model is updated directly, the view-model won't know to fire a property changed event. This causes the UI to go out of sync. This severely limits your options for sending messages between parent and child view-models.
If the model has its own property changed notification, #1 and 2 aren't a problem. Instead, you have to worry about memory leaks if the wrapper VM goes out of scope but the model doesn't.
If your models are complex, with lots of children objects, then you have to walk the entire tree and create a second object graph that shadows the first one. This can be quite tedious and error prone.
Wrapped collections are particularly difficult to work with. Any time something (UI or backend) inserts or removes an item from a collection, the shadow collection needs to be updated to match. This kind of code is really hard to get right.
So, the question is: what is the default way to bind and why?
Do I miss points that make it necessary to have a ViewModel?
Are there any real reasons one would like to bind to a model?
Most important is the why, not the how to.
回答1:
View models usually contains members, which are intended to be used with view (e.g., properties like IsSomethingSelected
, IsSomethingExpanded
, IsSomethingVisible
, any implementation of ICommand
).
Do you see any reason to bring all those stuff into models? Of course, no. That's why view models exists.
回答2:
So, the question is: what is the default way to bind and why?
In general, I would argue that having a ViewModel, and binding to it, is the default. There's a reason that "ViewModel" exists and is part of the MVVM pattern.
There are other reasons a ViewModel is necessary other than purely the data. You also typically implement application-specific logic (ie: not part of the model, but required in the application). Any ICommand
implementation, for example, really should be on a ViewModel, since it's completely unrelated to the Model.
Are there any real reasons one would like to bind to a model?
It can be simpler in some cases, especially if your model already implements INotifyPropertyChanged
. Reducing code complexity is a valuable goal with its own merit.
回答3:
Counter-arguments:
- Removing logic from the View
Removing logic from the view-model is likewise useful. By pushing logic like validation, calculated fields, etc. into the model you are left with a lighter, cleaner view-model.
•Make logic unit testable
The model itself is really easy to unit test. You don't have to worry about mocking libraries and whatnot like you would with a view-model that deals with external services.
•reduce code redundance (duplication where needed)
Multiple view-models can share the same model, reducing redundancies for validation, calculated fields, etc.
- Security •The model contains properties that the user shall not change
Then don't expose them on the UI.
•Automatic, but unwanted updates can happen if Binding to model
None of this one makes any sense. If your VM is just a wrapper around your model, it is just going to push those updates down anyways.
- Loose coupling •If binding directly to the model, there will be a coupling between lower layers and View
That coupling doesn't magically disappear when you shove a wrapper VM between them.
•Changing the Model causes changes in all the Views
Changing the Model causes changes in all of the wrapper view-models. Changing the view-model also causes changes in all of views. Thus the Model can still cause changes in all of the views.
•The view is not depending on any given model
That is true with or without the view-model wrapping the model. It only sees properties, not actual classes.
•Model can be easily generated with EF, some DSL, batch files, on so on
Yep. And with a little bit of work those easily generated models can include useful interfaces like INotifyDataErrorInfo, IChangeTracking, and IEditableObject.
- Speed of development
Binding to models offers faster development because you don't have to map all the properties.
•You can start with a Prototype ViewModel hierarchy and bind to that
Or I can start with a prototype model. Nothing is gained by adding a wrapper.
•If the model is still under development, you can start with a Prototype Model •Model and ViewModel can be developed testdriven, no matter of the View
Again, nothing is gained by adding a wrapper around the model.
•The View can entirely be built by a designer, or developer with strong design background
Yet again, nothing is gained by adding a wrapper around the model.
- "Tricky synchronizion" is solved •There are plenty of solutions to any given "tricky synchronization" problem, e.g. •AutoMapper
If you use automapper to copy the data into the view-model then you aren’t using the MVVM pattern. You are just using Views and Models.
•Event system from the model (model fires event, ViewModel subscribes)
Hello memory leaks. That is, unless you are incredibly careful and give up the ability to share a model across multiple views.
- Equal structure throughout the project •There are points where have to a ViewModel, like SelectedItem
Irrelevant. No one is arguing against non-wrapper view-models.
•Mixing Binding to Model and ViewModel is errorprone
Not supported.
•It is harder for fresh developers to figure out structure of the project
Not supported.
•starting to bring ViewModel later when there is no way around it is messy
Irrelevant. Again, no one is arguing against also using non-wrapper view-models.
- Scalability •Lets define: If you do not use a ViewModel, it is not MVVM
Irrelevant. For the third time, no one is arguing against also using non-wrapper view-models.
•MVVM can be easily adopted to lots of datasources, lots of views
Irrelevant. We are not arguing about whether or not to use MVVM, we are arguing about how best to use it.
•If you find any performance issues: Lazy loading and caching goes in the ViewModel
Agreed, but irrelevant. No one is suggesting that you shove service calls into the model.
- Separation of concerns
This is where the wrapper view-model falls down the hardest.
The view-model already has to deal with UI-data (e.g. modes, selected items) and host the ICommands that call out to external services.
Shoving all of the model data, validation logic, calculated properties, etc. into the view-model makes is even more bloated.
回答4:
I agree with Reed-- the ViewModel is to what one ought to bind. I always imagine the Model to be a more or less static set of values which might not change as frequently or dynamically as the ViewModel. Generally, I try to put anything with a value which can be assumed at compile-time in the Model, and anything which would be determined at runtime in the ViewModel.
The View itself shouldn't have anything more than the barest of bare-bones logic. The rest should be references to the ViewModel. Security is sometimes the issue, but I like to do it simply for code readability and conciseness. It's a whole lot easier to deal with code when all the aesthetic things are done in the view, all the more mathematical, logical things are hidden away in the ViewModel, and all the hard data is in a separate Model.
MVVM is also very closely related to MVC, with the guiding principle that the Model and the View should never see each other directly. Once again, for me it's a clarity thing. The logic that determines how the Model's values ought to change should also be in the ViewModel/Controller. The View shouldn't think for itself.
Think of the View like a receptionist: it's a friendly face that interfaces ("talks to") the user. The ViewModel is the accountant in the office behind the door next to the front desk, and the Model is his/her set of reference books and notes. If the receptionist starts writing in the margins of the accountant's books, erasing the accountant's notes, and changing things in the records, things start to get confusing.
回答5:
Here is a simple object graph. Just some really simple models with normal property change and validation events.
Those of you who think models need to be wrapped in view-models show your code.
public class ModelBase : INotifyPropertyChanged, INotifyDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
OnErrorChanged(propertyName);
}
protected void OnErrorChanged(string propertyName)
{
if (ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public virtual IEnumerable GetErrors(string propertyName)
{
return Enumerable.Empty<string>();
}
public virtual bool HasErrors
{
get { return false; }
}
}
public class Customer : ModelBase
{
public Customer()
{
Orders.CollectionChanged += Orders_CollectionChanged;
}
void Orders_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.OldItems.Count > 0)
foreach (INotifyPropertyChanged item in e.OldItems)
item.PropertyChanged -= Customer_PropertyChanged;
if (e.NewItems.Count > 0)
foreach (INotifyPropertyChanged item in e.NewItems)
item.PropertyChanged += Customer_PropertyChanged;
OnPropertyChanged("TotalSales");
}
void Customer_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Total")
OnPropertyChanged("TotalSales");
}
public decimal TotalSales
{
get { return Orders.Sum(o => o.Total); }
}
private string _FirstName;
public string FirstName
{
get { return _FirstName; }
set
{
if (_FirstName == value)
return;
_FirstName = value;
OnPropertyChanged();
}
}
private string _LastName;
public string LastName
{
get { return _LastName; }
set
{
if (_LastName == value)
return;
_LastName = value;
OnPropertyChanged();
}
}
private readonly ObservableCollection<Order> _Orders = new ObservableCollection<Order>();
public ObservableCollection<Order> Orders
{
get { return _Orders; }
}
}
public class Order : ModelBase
{
public Order()
{
OrderLines.CollectionChanged += OrderLines_CollectionChanged;
}
void OrderLines_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.OldItems.Count > 0)
foreach (INotifyPropertyChanged item in e.OldItems)
item.PropertyChanged -= OrderLine_PropertyChanged;
if (e.NewItems.Count > 0)
foreach (INotifyPropertyChanged item in e.NewItems)
item.PropertyChanged += OrderLine_PropertyChanged;
OnPropertyChanged("Total");
OnErrorChanged("");
}
public override bool HasErrors
{
get { return GetErrors("").OfType<string>().Any() || OrderLines.Any(ol => ol.HasErrors); }
}
void OrderLine_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Extension")
OnPropertyChanged("Total");
}
public decimal Total
{
get { return OrderLines.Sum(o => o.Extension); }
}
private int _OrderNumber;
private DateTime _OrderDate;
public DateTime OrderDate
{
get { return _OrderDate; }
set
{
if (_OrderDate == value)
return;
_OrderDate = value;
OnPropertyChanged();
}
}
public int OrderNumber
{
get { return _OrderNumber; }
set
{
if (_OrderNumber == value)
return;
_OrderNumber = value;
OnPropertyChanged();
}
}
private readonly ObservableCollection<OrderLine> _OrderLines = new ObservableCollection<OrderLine>();
public ObservableCollection<OrderLine> OrderLines
{
get { return _OrderLines; }
}
}
public class OrderLine : ModelBase
{
private string _ProductName;
private decimal _Quantity;
private decimal _Price;
public decimal Price
{
get { return _Price; }
set
{
if (_Price == value)
return;
_Price = value;
OnPropertyChanged();
}
}
public string ProductName
{
get { return _ProductName; }
set
{
if (_ProductName == value)
return;
_ProductName = value;
OnPropertyChanged();
OnPropertyChanged("Extension");
}
}
public decimal Quantity
{
get { return _Quantity; }
set
{
if (_Quantity == value)
return;
_Quantity = value;
OnPropertyChanged();
OnPropertyChanged("Extension");
}
}
public decimal Extension
{
get { return Quantity * Price; }
}
public override IEnumerable GetErrors(string propertyName)
{
var result = new List<string>();
if ((propertyName == "" || propertyName == "Price") && Price < 0)
result.Add("Price is less than 0.");
if ((propertyName == "" || propertyName == "Quantity") && Quantity < 0)
result.Add("Quantity is less than 0.");
return result;
}
public override bool HasErrors
{
get { return GetErrors("").OfType<string>().Any(); }
}
}
And here is a typical ViewModel that would go with it:
public class CustomerViewModel : ModelBase
{
public CustomerViewMode()
{
LoadCustomer = null; //load customer from service, database, repositry, etc.
SaveCustomer = null; //save customer to service, database, repositry, etc.
}
private Customer _CurrentCustomer;
public Customer CurrentCustomer
{
get { return _CurrentCustomer; }
set
{
if (_CurrentCustomer == value)
return;
_CurrentCustomer = value;
OnPropertyChanged();
}
}
public ICommand LoadCustomer { get; private set; }
public ICommand SaveCustomer { get; private set; }
}
回答6:
There is no "correct" answer to your question. WPF will, of course, happily allow you to bind to whatever you have declared to yourself to be a "Model" object; the framework simply doesn't care. You don't always have to follow the MVVM pattern just because you're doing an application in WPF. Context is always key to any software you write. If you are hard-pressed for time and need a quick-out for your prototype, by all means bind to the Model and refactor if/when you need to.
So I think what you're really asking is "when should I use the MVVM pattern?"
The answer is, of course, "when your problem fits the pattern".
So what does the MVVM pattern give you? You've already listed several reasons to use MVVM, but the most important one for the pattern is loose coupling -- all the rest sorta come with that.
The entire point of the MVVM pattern is to ensure that your model is a state machine that knows nothing about how data is presented to or obtained from a user. In a way, your model is pure data structured in the format that makes sense for the model rather than structured in a format that makes sense to a human being. Your ViewModel is responsible for translating between that pure data platform (the Model) and a user input platform (the View). The ViewModel is tightly coupled to both the View and the Model, but the important thing is that the Model knows nothing of the View.
来源:https://stackoverflow.com/questions/16947122/binding-to-model-or-viewmodel