为什么要使用MVVM?

90

好的,我一直在研究MVVM模式,但以前每次尝试时都因以下几个原因而放弃:

  1. 不必要的冗长编码
  2. 对于程序员没有明显的优势(我的办公室里没有设计师。目前只有我自己和另一个程序员)
  3. 没有很多好的实践资源/文档!(或者至少很难找到)
  4. 想不出一个单独使用它的优势。

我即将再次放弃,想问一下是否有人能回答上面的原因。

我真的看不出在单个/合伙编码中使用这个的优势。即使在具有10个窗口的复杂项目中也是如此。对我来说,DataSet是足够好的视图,绑定就像Brent问题中所述的那样。

能否举一个使用MVVM模式节省时间的例子比较XAML数据绑定。

目前我的100%绑定都是在XAML中完成的。因此,我觉得没有必要使用VM,因为它只是我需要编写并依赖的额外代码。

编辑:
在花了一个下午研究MVVM后,我终于从这个答案中找到了一些让我认识到它真正好处的东西。


18
如果你已经对它进行了多次评估,但没有发现使用它的优势,那么就不要使用它。 - Daniel Daranas
3
为什么要使用多个问号?这似乎是一些相关问题的重复,比如:https://dev59.com/BXI-5IYBdhLWcg3wm5pH和https://dev59.com/XnI-5IYBdhLWcg3w18Z3。 - Benjamin Podszun
1
@Daniel 我知道,但我想要一些例子来帮助改变我的想法,并真正实现它! - Michal Ciechan
请查看托管可扩展性框架(MEF)。正如Ross在这里所解释的那样,它更适合小团队使用。 - Rob Tillie
3
大多数情况下,WPF 假定采用 MVVM 模式。例如,如果不使用 MVVM 就使用 WPF 树控件,可能会在一天内让你抓狂。MVVM 只是简化和可测试化的东西。 - Gishu
2
这个链接可能会回答一些问题:http://www.wintellect.com/CS/blogs/jlikness/archive/2010/04/14/model-view-viewmodel-mvvm-explained.aspx。我将其作为评论发布,因为它只是一个链接,而不是真正的答案。 - Peter
13个回答

129

摘要

  • 所有模式的使用都是情境性的,如果有的话,好处在于减少复杂性。
  • MVVM指导我们如何在GUI应用程序中分配类之间的责任。
  • ViewModel将数据从Model投射到适合View的格式中。
  • 对于琐碎的项目,MVVM是不必要的。只使用View就足够了。
  • 对于简单的项目,ViewModel/Model分离可能是不必要的,只使用Model和View就足够了。
  • Model和ViewModel不需要一开始就存在,可以在需要时引入。

何时使用模式以及何时避免它们

对于足够简单的应用程序,每个设计模式都是过度设计。假设您编写一个GUI应用程序,显示一个按钮,按下后显示“Hello world”。在这种情况下,像MVC,MVP,MVVM这样的设计模式增加了很多复杂性,而没有添加任何价值。

通常情况下,仅因某种程度上匹配就引入设计模式是一个不好的决定。设计模式应该用于减少复杂性,可以通过直接减少总体复杂性或者用熟悉的复杂性替代陌生的复杂性来实现。如果设计模式不能以这两种方式之一减少复杂性,则不要使用它。
为了解释熟悉和陌生的复杂性,请看以下两个字符序列:
  • "D.€|Ré%dfà?c"
  • "CorrectHorseBatteryStaple"
虽然第二个字符序列是第一个序列的两倍长度,但由于更加熟悉,它比第一个序列更易读、更快写和更容易记忆。同样的道理也适用于代码中的熟悉模式。
当考虑到熟悉度取决于读者时,这个问题又增加了另一个维度。有些读者会认为"3.14159265358979323846264338327950"比上述密码更容易记忆,而有些人则不会。因此,如果想使用MVVM的一种特定版本,请尝试使用与所使用的具体语言和框架中最常见形式相似的版本。
MVVM
话虽如此,让我们通过一个例子来深入探讨MVVM。 MVVM指导我们如何在GUI应用程序(或层之间 - 更多关于此后面)之间分配职责,目标是拥有少量的类,同时保持每个类的职责数量小且明确定义。
“正确”的MVVM假定至少是一个中等复杂的应用程序,该应用程序处理从“某处”获取的数据。它可以从数据库、文件、Web服务或其他无数来源获取数据。
示例
在我们的示例中,我们有两个类ViewModel,但没有ViewModelModel包装了一个csv文件,在启动时读取并在应用程序关闭时保存所有用户对数据所做的更改。 View是一个窗口类,以表格形式显示来自Model的数据,并允许用户编辑数据。 csv内容可能看起来像这样:
ID, Name, Price
1, Stick, 5$
2, Big Box, 10$
3, Wheel, 20$
4, Bottle, 3$

新需求:显示欧元价格
现在我们被要求对我们的应用程序进行更改。数据由一个二维网格组成,其中已经有一个“价格”列,包含美元价格。我们需要添加一个新的列,显示以欧元计价的价格,而不是美元,基于预定义的汇率。CSV文件的格式不能更改,因为其他应用程序使用相同的文件,并且这些其他应用程序不在我们的控制范围内。
一种可能的解决方案是简单地将新列添加到“Model”类中。这不是最好的解决方案,因为“Model”保存它公开给csv的所有数据 - 我们不想在csv中增加新的欧元价格列。因此,对“Model”的更改将是非平凡的,并且描述“Model”类所做的事情也会更加困难,这是一种code smell
我们也可以在View中进行更改,但是我们当前的应用程序使用数据绑定直接显示由我们的Model类提供的数据。由于我们的GUI框架不允许在表格数据绑定到数据源时引入额外的计算列,因此我们需要对View进行重大更改才能使其正常工作,从而使View变得更加复杂。

引入ViewModel

应用程序中没有ViewModel,因为直到现在Model以Csv需要的方式呈现数据,这也是View所需的方式。在中间添加一个ViewModel只会增加无意义的复杂性。但是现在Model不再按照View需要的方式呈现数据,因此我们编写了一个ViewModelViewModel以一种使View变得简单的方式投影Model的数据。之前,View类订阅了Model类。现在,新的ViewModel类订阅了Model类,并将Model的数据公开给View——其中包括一个显示欧元价格的额外列。View不再知道Model,它现在只知道ViewModel,从View的角度看,ViewModel与之前的Model相同——除了公开的数据包含一个新的只读列。

新要求:以不同的方式格式化数据

下一个客户请求是,我们不应该将数据显示为表格中的行,而是将每个项目的信息(即行)显示为卡片/框,并在屏幕上以4x5网格的形式显示20个框,每次显示20个框。因为我们保持了 View 的逻辑简单,所以我们只需完全用符合客户要求的新类替换 View。当然还有另一个客户喜欢旧的 View,所以我们现在需要支持两者。由于所有通用业务逻辑已经在 ViewModel 中发生,这并不是什么大问题。因此,我们可以通过将 View 类重命名为 TableView,并编写一个新的 CardView 类来显示以卡片格式显示数据。我们还需要编写一些粘合代码,这可能是启动函数中的一行代码。

新要求:动态汇率

下一位客户的要求是从互联网上获取汇率,而不是使用预定义的汇率。这就是我们重新审视“层”的早期声明的时候了。我们不会改变我们的Model类来提供汇率。相反,我们编写(或查找)一个完全独立的附加类来提供汇率。那个新类成为模型层的一部分,我们的ViewModel将csv-Model和汇率-Model的信息合并在一起,然后呈现给View。对于这个更改,旧的Model类和View类甚至不需要被修改。好吧,我们需要将Model类重命名为CsvModel,并称新类为ExchangeRateModel。
如果我们没有在适当的时候引入ViewModel,而是等到现在才这样做,那么引入ViewModel现在需要的工作量会更高,因为我们需要从View和Model中删除大量功能,并将这些功能移动到ViewModel中。
关于单元测试的后记
MVVM的主要目的并不是使Model和ViewModel中的代码可以进行单元测试。MVVM的主要目的是将代码分解为具有少量明确定义职责的类。其中一个好处是,让代码由少量明确定义职责的类组成时更容易进行单元测试。更大的好处是,代码更容易理解、维护和修改。

嗨@Peter,非常好的解释。 我有一个问题。关于上面提到的“新需求:显示欧元价格”的情况,你能否解释一下(也许是一些代码片段或其他什么东西),以便了解viewmodel如何处理它。是这样吗?创建一个新的数据模型类,将新列作为属性,并使用此模型将其绑定到视图? - Swamy
@swamy 这部分几乎完全取决于您使用的框架。通常,新的 ViewModel 类会向现有的 View 类公开某种列表/集合/表,并使用现有的 Model 类中的数据填充其中的大部分内容。根据框架的不同,一个列表项属性/表列可能会用 price * exchangeRate 填充。 - Peter

34

实现模式和遵循最佳实践通常感觉毫无意义,但在数个月后,当您的老板要求您添加或调整某个功能时,您会成为一个信徒。使用MVVM(和一般的设计模式),您将能够有效地遵循自己的代码,并在最坏的情况下在几小时或几天内满足需求,而不是用几周甚至几个月的时间。(这种修改可能仅涉及几行代码,而不需要花费数周时间尝试理解首次编写如何才能新增功能。)

后续: 实际上,遵循设计模式和最佳实践会拖慢初步开发速度,这往往是管理层和工程师都难以接受的。回报(商业术语中的投资回报率)来自于具有良好结构化的代码,这种代码实际上是可维护、可扩展和可伸缩的。

例如,如果您正确遵循了MVVM,您应该能够对显示逻辑进行非常大胆的更改,例如交换整个视图,而不会对数据和业务逻辑造成任何影响。

关于使用数据集作为模型的思考:(我也曾犯过这个错误。)数据集似乎是将模型数据在应用程序中移动的一种完全有效的方法。问题在于,您如何识别数据项。因为您的数据存储在行和列中,您必须按列名称或索引进行查找,并且还必须过滤特定行。这些逻辑意味着在应用程序中使用魔法字符串和数字连接逻辑。使用类型化的数据集可以缓解其中一些问题,但并不能完全解决问题。使用类型化的数据集,您将远离MVVM,并进入UI和数据源之间更紧密的耦合。


1
这就是我问这个问题的原因。这种情况以前发生过。我只需要一些动力来停止懒惰,并编写一些额外的(在我看来是重复的)代码。 - Michal Ciechan
你能给出一个现实生活中的例子,说明使用MVVM模式实现某个功能会很容易,而不使用则需要花费很长时间吗? - Michal Ciechan
例如,您可以通过将实际的枚举值转换为用户友好的字符串(甚至本地化)来使枚举值在用户界面中显得更加友好。 或者,您是希望您的用户阅读诸如“非常高”,“浅红”等值吗? - gehho
3
谢谢,我现在决定采用MVVM。Jason Dollinger在解释和演示MVVM方面做得非常出色,无人能及。 - Michal Ciechan
@michal Ciechan MVVM让现实生活中的一个功能变得容易,那就是当你的老板想要完全重写UI时。在这种情况下,你只需编写新的UI布局XAML代码,而不需要触及你的模型。在旧式的Winforms应用程序中,您将头痛不已地支持两个UI同时存在。 - rollsch

14

它帮助你将GUI和程序逻辑分离;混合它们可能会导致非常难以维护的应用程序,特别是当您的项目随时间增长时。


1
+1 我曾经维护过100% XAML数据绑定的项目。分离gammelgul所说的会有很大帮助。 - Onion-Knight

6

来自这里

作为一名开发者,你为什么要关心模型-视图-视图模型(MVVM)模式呢?这种模式对WPF和Silverlight开发都有很多好处。在继续之前,请问自己以下问题:

  • 你需要与设计师共享项目,并且希望设计工作和开发工作可以同时进行吗?
  • 你需要对解决方案进行彻底的单元测试吗?
  • 对于你的组织内部和跨项目使用的可重用组件很重要吗?
  • 你希望更灵活地更改用户界面,而不必重构代码库中的其他逻辑吗?

如果你对任何一个问题回答“是”,那么使用MVVM模型可以为你的项目带来诸多好处。


5
  • 与设计师合作更加容易(不是程序员,只是使用Blend的人)
  • 代码可测试(单元测试)
  • 更容易在不影响其他代码的情况下更改视图
  • 在开发用户界面时,您可以模拟模型并开发接口而无需运行真实服务(仅使用模型中的模拟数据)。然后,您只需切换标志并连接到服务。

4

来自Josh Smith的MVVM文章

除了WPF(和Silverlight 2)功能使MVVM成为应用程序结构的自然方式外,该模式也很受欢迎,因为ViewModel类易于单元测试。当应用程序的交互逻辑存在一组ViewModel类中时,您可以轻松编写测试代码。从某种意义上说,视图和单元测试只是两种不同类型的ViewModel消费者。拥有一套应用程序ViewModel的测试可提供免费和快速的回归测试,这有助于降低维护应用程序的成本。

对我而言,这是使用MVVM的最重要原因。

以前,我会将视图和视图模型混合在一起。但是,视图本质上具有鼠标和键盘事件作为输入,并具有绘制像素作为输出。如何对此进行单元测试?MVVM通过将不可测试的视图与可测试的视图模型分离,并尽可能保持视图层薄,解决了这个问题。


3

MVVM有许多好处,但可能最重要的是能够测试您的代码(单元测试ViewModels)。

视图和ViewModel之间缺乏连接确实有助于松耦合。这使得重用您编写的组件变得非常容易。


好的。测试它会更容易......或者至少更有意义。就我个人而言,我想不出我想在ModelView上进行的测试,我不能在DataSets上完成。 - Michal Ciechan
2
非常简单的例子:您可以为按钮的启用状态设置一个属性,然后将按钮的IsEnabled属性绑定到ViewModel中的此属性。现在,您可以对启用/禁用按钮的逻辑进行单元测试,以确保其正确性。告诉我你在你的DataSet中如何做到这一点。 ;) - gehho

3
我自己仍在努力理解这种模式,但我认为它很有价值。目前最大的挑战是这种方法仍然很新,因此存在很多混乱,某些关键组件的实现仍然很尴尬。我发现了一些对我帮助很大的东西,可以使模式的实现更加清晰:
  1. 我大量使用Josh Smith的MVVM Foundation中的RelayCommand。这使得通过命令从视图到ViewModel的绑定更加简洁。
  2. 我使用AOP来减轻实现INotifyPropertyChanged的痛苦。我目前正在使用Postsharp,虽然我相信还有其他工具可以做到这一点。如果我没有发现这个,我现在可能已经放弃了,因为手动实现它的样板代码真的很让我烦恼。
  3. 我不得不反转我的软件实现方式。而不是有一个独裁者类告诉它的所有手下干什么,然后使用他们的手下,我的软件更多地成为松散耦合的服务,说:
    • 这是我知道如何做的
    • 这些是我需要完成的事情
当您开始以这种方式结构化代码并使用使依赖关系易于连接的工具(有各种IoC框架可供选择)时,我发现它可以减轻MVVM的一些尴尬,因为您可以减少与将模型注入到ViewModel中和定位各种View服务(例如显示文件对话框)以供ViewModel使用相关的样板代码。
学习这种不同的方法需要巨大的投资,并且与任何重大实现转变一样,当您开始使用它时,生产力会降低。但是,我开始看到隧道尽头的一些光亮,并且我相信,一旦我掌握了混乱的细节,我的应用程序将更加清洁且易于维护。
要回答关于Postsharp通过INotifyPropertyChanged的问题,我使用了基于此处示例的Aspect。我对其进行了一些自定义,但这就是它的要点。通过这种方式,我只需标记类[NotifyPropertyChanged],所有公共属性的setter都将实现该模式(即使它们是自动属性setter)。对我来说,感觉更加清洁,因为我不再担心是否想花时间使类实现INotifyPropertyChanged。我只需添加属性即可完成。

请详细介绍如何使用PostSharp实现INotifyPropertyChanged。我只是在我的基本ViewModel类中放置了OnPropertyChanged,但我还没有觉得有负担。到目前为止,我只使用PostSharp来跟踪方法调用;你是如何使用它的? - Robert Rossney

3

阅读这篇文章,了解MVVM的介绍。

2005年,微软WPF和Silverlight架构师之一John Gossman在他的博客中发布了模型-视图-视图模型(MVVM)模式。MVVM与Fowler的演示模型相同,因为两种模式都具有View的状态和行为的抽象。Fowler引入Presentation Model作为创建UI平台无关的View抽象的手段,而Gossman则引入MVVM作为利用WPF核心功能简化用户界面创建的标准化方法。从这个意义上讲,我认为MVVM是更一般的PM模式的专业化,为WPF和Silverlight平台量身定制。

..

与MVP中的Presenter不同,ViewModel不需要引用视图。视图绑定到ViewModel上的属性,而ViewModel又公开包含在模型对象和其他特定于视图的状态中的数据。视图和ViewModel之间的绑定很容易构建,因为将ViewModel对象设置为视图的DataContext。如果ViewModel中的属性值更改,这些新值会通过数据绑定自动传播到视图中。当用户在视图中单击按钮时,由ViewModel执行命令来执行请求的操作。ViewModel,而不是View,执行对模型数据所做的所有修改。 视图类不知道模型类的存在,而ViewModel和模型也不知道视图的存在。实际上,模型完全不知道ViewModel和View的存在。这是一种非常松散耦合的设计,从许多方面获得回报,正如您很快就会看到的。
此外,本文还解释了为什么要使用这些GUI模式:
在一个简单的“Hello, World!”程序中使用设计模式是不必要且适得其反的。任何有能力的开发人员都可以一眼看出几行代码。然而,随着程序功能数量的增加,代码行数和移动部件数量相应增加。最终,系统的复杂性以及它所包含的重复问题促使开发人员以更易于理解、讨论、扩展和故障排除的方式组织他们的代码。我们通过在源代码中给某些实体命名已知的名称来减少复杂系统的认知混乱。我们通过考虑代码在系统中的功能角色来确定要应用于代码片段的名称。
开发人员通常会有意按照设计模式来构建他们的代码,而不是让模式自然地出现。这两种方法都没有错,但在本文中,我将探讨明确使用MVVM作为WPF应用程序的架构的好处。某些类的名称包括来自MVVM模式的知名术语,例如如果该类是视图的抽象,则以“ViewModel”结尾。这种方法有助于避免前面提到的认知混乱。相反,你可以快乐地处于受控混乱的状态,这是大多数专业软件开发项目的自然状态!

2

如果你采用像MVVM这样的设计模式,出于其他人已经介绍的所有原因,你将从长远来看受益。请记住,你不需要逐字遵循该模式的要求,只需确保在你的窗口(视图)和逻辑(代码后台)之间有良好的分离。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接