MVVM 没有意义吗?

96

传统的MVVM实现是否没有意义?我正在创建一个新的应用程序,考虑了Windows Forms和WPF。我选择了WPF,因为它是未来的趋势,提供了很多灵活性。使用XAML可以减少代码量并更轻松地对UI进行重大更改。

既然选择WPF很明显,那么我认为我也应该使用MVVM作为我的应用程序架构,因为它提供了混合能力、分离关注点和单元测试能力。在理论上,这似乎就像UI编程的圣杯一样美好。然而,这段简短的冒险却变成了一个真正的头痛。

实践中,我发现自己交换了一个问题以换取另一个问题,这与我的预期相同。我倾向于成为一个有强迫症的程序员,因为我想以正确的方式做事情,以便达到正确的结果并可能成为一个更好的程序员。MVVM模式只是在我的生产力测试中不及格,变成了一个大杂烩!

明显的例子是添加对模态对话框的支持。正确的方法是弹出一个对话框并将其绑定到视图模型。使其工作很困难。为了从MVVM模式中受益,您必须将代码分布在应用程序的几个层中。您还必须使用像模板和lambda表达式这样的深奥编程构造。这些东西让你盯着屏幕挠头。如我最近所发现的那样,这使得维护和调试成为一场等待发生的噩梦。我的一个关于框已经能够正常工作了,直到我第二次调用它时出现异常,说它不能再次显示对话框一旦关闭。我不得不在对话窗口中添加一个关闭功能的事件处理器,在其IDialogView实现中添加另一个事件处理程序,最后在IDialogViewModel中再次添加一个事件处理程序。我想MVVM会使我们摆脱这种过度复杂的方式!

有几位竞争解决此问题的人,它们都是hack,不提供清洁、易重用、优雅的解决方案。大多数MVVM工具包忽略了对话框,即使他们解决了它们,它们也只是不需要自定义接口或视图模型的警报框。

我计划放弃MVVM视图模式,至少是其正统实现。你认为呢?如果你曾经使用过它,这对你来说是否值得麻烦?我是一个无能的程序员吗,还是MVVM并没有像宣传的那样好用?


10
我一直质疑MVVM是否过于工程化。有趣的问题。 - Taylor Leese
12
MVVM 和 MVC 这样的设计模式似乎有些过度设计,直到你需要进行某些修改或更换组件。第一次这样做时,所有的繁琐程序都是值得的。 - Robert Harvey
42
"Lambda函数很深奥?这可是新鲜事。" - Ray Booysen
6
@Ray - 哈哈,给你的评论点赞! :D - Venemo
8
正如Alan Cooper在十多年前《关于面孔》一书中指出的那样,如果你正在设计用户界面(UI),而模态对话框不是边缘情况,那么你可能做错了什么。 - Robert Rossney
显示剩余4条评论
8个回答

62

抱歉我的回答有点冗长,但别怪我!你的问题也很冗长。

总之,MVVM并不是毫无意义。

明显的例子是添加模态对话框支持。正确的方式是提供一个对话框并将其绑定到视图模型。让这个工作起来很困难。

是的,确实如此。
然而,MVVM为您提供一种将UI外观与逻辑分离的方法。没有人强迫您在所有地方都使用它,也没有人用枪指着您的头要求您为每个东西创建单独的视图模型。

以下是我针对此特定示例的解决方案:
UI如何处理某个输入与ViewModel无关。 我会将代码添加到View的.xaml.cs文件中,该文件实例化对话框并将相同的ViewModel实例(或者如果需要其他内容)设置为其DataContext。

为了从MVVM模式中受益,您必须在应用程序的各个层中分发代码。您还必须使用像模板和lambda表达式这样的深奥编程结构。

好吧,您不必在几个地方都使用它。以下是我如何解决它:

  • 将XAML添加到View中,.xaml.cs中不添加任何内容
  • 在ViewModel中编写除了直接操作UI元素的内容之外的所有应用程序逻辑
  • 所有应该由UI完成但与业务逻辑无关的代码都放入.xaml.cs文件中
我认为MVVM的主要目的是将应用程序的逻辑和具体的UI分离,从而实现对UI的轻松修改(或完全替换)。
我使用以下原则:视图可以从ViewModel中了解和假定任何内容,但ViewModel不能了解视图的任何信息。
WPF提供了一个很好的绑定模型,您可以使用它来实现这一点。
(顺便说一句,如果使用得当,模板和lambda表达式并不是神秘的。但如果您不想使用它们,请不要使用。)
“让你盯着屏幕挠头的东西。”
是啊,我知道那种感觉。当我第一次看到MVVM时,就有这种感觉。但是一旦你掌握了它,就不会觉得难了。
“我的关于框正常工作...”
您为什么要在关于框后面放置ViewModel?没有意义。
大多数MVVM工具包都忽略了对话框,即使它们处理它们,它们也只是不需要自定义接口或视图模型的警报框。
是的,因为UI元素是否在同一窗口、另一个窗口或者此时正在绕火星运行,都与ViewModels无关。
关注点分离
编辑:
这是一个非常好的视频,标题为构建自己的MVVM框架。值得一看。

3
+1 只翻译文本内容,不进行解释。 - Robert Harvey
13
关于使用代码后台的建议,我赞成。人们普遍误解在MVVM中使用代码后台是“不好”的...但对于纯粹与UI相关的东西,这是正确的方法。 - Thomas Levesque
4
@Venemo,我认为你可以使用自定义行为等技术来封装大量你想要放在代码后面的东西,这很方便,如果你发现自己反复编写粘合代码。然而,总的来说,我认为使用代码后台进行粘合比拼凑笨拙的XAML更好。我关心的主要问题是确保代码后台中没有足够复杂需要进行单元测试的内容。任何足够复杂的内容最好封装在ViewModel或扩展类中,例如Behavior或MarkupExtension。 - Dan Bryant
1
@Venemo "为什么要在关于框后面放置一个ViewModel?没有意义。"我们有两个共享同一个关于框的应用程序。因此,我们注入给定应用程序的正确ViewModel。好坏难说,但这就是我们的做法。 - ihatemash
8
@ Thomas: 你说得对,关于MVVM最大的误解是它的目的是要消除code-behind。其实目的是让非UI代码脱离code-behind。把仅涉及UI的代码放在ViewModel中与把问题域代码放在code-behind中一样不好。 - Jim Reineri
显示剩余2条评论

8
让这个工作起来很困难。为了从MVVM模式中受益,您必须在应用程序的各个层中分发代码。您还必须使用像模板和lambda表达式这样的晦涩编程构造。
对于一个普通的模态对话框?你肯定在做错事情了——MVVM实现不必那么复杂。
考虑到您对MVVM和WPF都是新手,您可能在任何地方都使用了次优解决方案并且不必要地使事情变得复杂——至少我在第一次使用WPF时是这样做的。在放弃之前,请确保问题确实是MVVM而不是您的实现。
MVVM、MVC、Document-View等是一系列旧的模式。它们有缺点,但没有您描述的致命缺陷。

6

我正在进行一个非常复杂的MVVM开发,使用PRISM,因此我已经不得不处理这种类型的问题。

我的个人结论:

MVVM vs MVC /PopUps & co

  • MVVM是一个非常好的模式,在大多数情况下,由于WPF的强大数据绑定,它完全取代了MVC
  • 在大多数情况下,直接从Presenter调用服务层是一种合法的实现方式
  • 即使是相当复杂的List/Detail场景,也可以通过纯粹的MVVM实现,感谢{Binding Path=/}语法
  • 然而,当需要实现多个视图之间的复杂协调时,控制器是必需的
  • 事件可以被使用;存储IView(或AbstractObserver)实例在控制器中的旧模式已经过时
  • 控制器可以通过IOC容器注入到每个Presenter中
  • Prism的IEventAggregator服务是另一个可能的解决方案,如果控制器的唯一用途是事件分派(在这种情况下,它可以完全替换控制器)
  • 如果要动态创建视图,则控制器非常适合这项工作(在prism中,控制器将获得注入(IOC)的IRegionManager)
  • 模态对话框在现代复合应用程序中大多已经过时,除了像强制确认这样的真正阻塞操作。在这些情况下,模态激活可以抽象为控制器内调用的服务,并由专门的类实现,该类还允许进行高级的演示级单元测试。例如,控制器将调用IConfirmationService.RequestConfirmation(“are you sure”),这将在运行时触发模态对话框显示,并且可以在单元测试期间轻松模拟。

5

我通过欺骗的方式解决了对话框问题。我的MainWindow实现了一个IWindowServices接口,该接口公开了所有应用程序特定的对话框。我的其他ViewModel可以导入服务接口(我使用MEF,但是您也可以手动通过构造函数传递接口),并使用它来完成必要的操作。例如,这是我一个小型实用程序的接口:

//Wrapper interface for dialog functionality to allow for mocking during tests
public interface IWindowServices
{
    bool ExecuteNewProject(NewProjectViewModel model);

    bool ExecuteImportSymbols(ImportSymbolsViewModel model);

    bool ExecuteOpenDialog(OpenFileDialog dialog);

    bool ExecuteSaveDialog(SaveFileDialog dialog);

    bool ExecuteWarningConfirmation(string text, string caption);

    void ExitApplication();
}

这将所有对话执行放在一个地方,可以轻松地为单元测试进行存根。我遵循的模式是对话框的客户端必须创建适当的ViewModel,然后按需要进行配置。Execute调用会阻塞,之后客户端可以查看ViewModel的内容以查看对话框结果。
对于大型应用程序,更“纯粹”的MVVM设计可能很重要,其中您需要更清洁的隔离和更复杂的组合,但对于中小型应用程序,我认为实用的方法,配合适当的服务来公开所需的钩子,已经足够。

但是你在哪里调用它呢?直接在 IWindowServices 类的函数中构建对话框不是更好吗?这样,调用模型视图就无需了解特定对话框的实现。 - ATL_DEV
接口被注入到需要访问应用程序对话框的任何ViewModel实例中。在大多数情况下,我会传递对话框的ViewModel,但有时也会有些懒惰,使用WPF OpenFileDialog和SaveFileDialog进行文件对话框调用。我的主要目标是为了单元测试的隔离性,所以这对于该目标来说已经足够了。如果你想要更好的隔离性,你可能需要创建一个OpenFileViewModel和SaveFileViewModel,它们将重复对话框所需的属性。 - Dan Bryant
请注意,这绝对不是一种纯粹的方法,因为使用对话框的ViewModel知道要打开的每个对话框的特定ViewModel。我认为这相当清晰,但您始终可以添加一个额外的隔离层,使用仅公开对话框使用所需参数的类,隐藏在绑定期间使用的ViewModel属性的任何不必要的可见性。对于较小的应用程序,我认为这种额外的隔离是过度的。 - Dan Bryant

5

设计模式的存在是为了帮助你,而不是阻碍你。作为一个好的开发者,掌握何时“打破规则”也是很重要的一点。如果MVVM模式在某项任务中过于繁琐,并且你已经确定未来价值不值得这样做,那么就不要使用这个模式。例如,正如其他人所评论的,为什么要费尽力气去实现一个简单的关于对话框呢?

设计模式从来没有被预期成为教条性的遵循。


2
正确。一个简单的关于框应该是简单的,但如果您必须显示诸如版本、许可、运行进程、公司名称等信息呢?这些都是存在某个地方的信息。在标准表单中,您可以绑定所有这些信息并完成它。MVVM则表示您应该为其创建一个视图模型,这是多余的。 - ATL_DEV

2
作为一种模式,MVVM本身非常优秀。但是随着NET 4.0数据绑定支持的发布,WPF控件库的数据绑定支持非常有限,虽然比WinForm好很多,但对于可绑定的MVVM来说还远远不够,我认为它的能力只有可绑定的MVVM所需能力的30%左右。
可绑定的MVVM:它的UI仅使用数据绑定将ViewModel与View连接起来。
MVVM模式关注的是ViewState的对象表示,它并没有描述如何在View和ViewModel之间保持同步,在WPF中,它是数据绑定,但它可以是任何东西。实际上,您可以在任何支持事件\回调的UI工具包中使用MVVM模式,甚至可以在文本控制台中使用,例如使用MVVM模式重写DoS的Norton Commander。
简而言之:MVVM并不是无意义的,它很棒。NET 4.0 WPF控件库则不行。
这是一个简单的概念ViewModel,您无法在纯MVVM方式下使用WPF进行数据绑定的简单证明。
public class PersonsViewModel
{
    public IList<Person> PersonList;
    public IList<ColumnDescription> TableColumns;
    public IList<Person> SelectedPersons;
    public Person ActivePerson;
    public ColumnDescription SortedColumn;
}

您无法在WPF的DataGrid列标题中进行数据绑定,也无法对选定的行进行数据绑定等等。您只能通过编写简单的代码方式或编写200行XAML hack代码来完成这5行最简单的ViewModel。当涉及到复杂的ViewModel时,情况会变得更加糟糕。
因此,除非您正在编写Hello World应用程序,否则在WPF中使用可绑定的MVVM是没有意义的。您将花费大部分时间思考如何hack绑定您的ViewModel。数据绑定很好,但请准备好在70%的时间内回退到事件。

你可以使用转换器将其绑定到DataGrid。 - Cameron MacFarland
@CameronMacFarland:不是所有的属性都是只读且不能解除绑定的,有一些根本不存在,只有报告状态更改的事件。 - Alex Burtsev
我承认我没有太多使用WPF DataGrid的经验。我倾向于避免它,因为它很丑,并且不再适用于WPF。话虽如此,使用转换器和附加属性来处理事件的组合应该可以满足您的需求。 - Cameron MacFarland
1
Alex,你遇到的问题与MVVM无关,而是与DataGrid的设计有关。说“数据绑定很好,但要准备好70%的时间回退到事件”是不正确的。我编写了一些非常庞大的WPF应用程序,在其中UI中没有任何事件处理程序——除了(Telerik)数据网格需要进行初始化的事件处理程序。 - Robert Rossney
3
如果你改变一下态度,不再认为“这设计很烂,根本用不了”,而是想一想“别人怎么就能用,我为什么不行?”或许会更成功。你可能会发现事情难做的原因在于你还不知道如何去做。 - Robert Rossney
显示剩余3条评论

0

在许多MVVM实现中,当涉及到(模态)对话框时,我看到了同样的问题。当我查看MVVM模式的参与者时,我感觉缺少了一些东西来构建一个连贯的应用程序。

  • View 包含特定的GUI控件,并定义用户界面的外观。
  • ViewModel 表示演示文稿的状态和行为。
  • Model 可以是来自域层的业务对象或提供必要数据的服务。

但缺失的是:

  • 谁创建ViewModels?
  • 谁负责应用程序工作流程?
  • 当ViewModels需要彼此通信时,谁会调解?

我的方法是引入一个(用例)Controller,负责缺失的点。如何工作可以在WPF Application Framework (WAF)示例应用程序中看到。


由Josh Smith实现的Mediator模式解决了我所有的视图模型通信问题。 Messenger.NotifyColleagues提供了一种完全独立的视图模型,它们知道如何响应全局事件(如果他们关心)而不需要任何两个视图模型互相了解。 它已经多次拯救了我们的困境。 - JasonD

0

不,这并不是毫无意义的,但即使模式本身非常简单,理解起来也很困难。有大量的错误信息和各种争论正确的方法。我认为在使用WPF和Silverlight时,你应该使用MVVM,否则你会过度编码并尝试用“旧”的win forms方法解决问题,这只会让你陷入麻烦。在Silverlight中更是如此,因为所有内容都需要异步处理(虽然可以绕过这个问题,但最好选择另一个平台)。

我建议仔细阅读这篇文章通过使用ViewModel模式简化WPF TreeView,以了解如何很好地实现MVVM,并允许您将win forms思维方式改变为MVVM的新思维方式。简而言之,当您想要完成某些事情时,首先将逻辑应用于ViewModel而不是View。您想选择一个项目?更改图标?不要迭代UI元素,只需更新模型属性并让数据绑定完成琐碎的工作。


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