【问题标题】:Is MVVM pointless? [closed]MVVM 没有意义吗? [关闭]
【发布时间】:2011-02-17 09:54:48
【问题描述】:

正统的 MVVM 实现毫无意义吗?我正在创建一个新的应用程序,我考虑过 Windows 窗体和 WPF。我选择 WPF 是因为它面向未来并且提供了很大的灵活性。使用 XAML 对 UI 进行重大更改的代码更少,更容易。

由于 WPF 的选择是显而易见的,我想我不妨一直使用 MVVM 作为我的应用程序架构,因为它提供了可混合性、分离问题和单元可测试性。从理论上讲,它看起来就像 UI 编程的圣杯一样美丽。这短暂的冒险;然而,已经变成了一个真正的头痛。 正如在实践中所预期的那样,我发现我已经将一个问题换成了另一个问题。我倾向于成为一个痴迷的程序员,因为我想以正确的方式做事,这样我才能得到正确的结果,并可能成为一个更好的程序员。 MVVM 模式刚刚通过了我的生产力测试,并且刚刚变成了一个令人讨厌的大黑客!

明显的例子是添加对模态对话框的支持。正确的方法是建立一个对话框并将其绑定到一个视图模型。让它发挥作用是困难的。为了从 MVVM 模式中受益,您必须在应用程序层的多个位置分布代码。您还必须使用深奥的编程结构,如模板和 Lamba 表达式。让你盯着屏幕挠头的东西。正如我最近发现的那样,这使得维护和调试成为等待发生的噩梦。我有一个关于框工作正常,直到我第二次调用它时遇到异常,说一旦关闭它就无法再次显示对话框。我必须为对话框窗口的关闭功能添加一个事件处理程序,在它的 IDialogView 实现中添加另一个,最后在 IDialogViewModel 中添加另一个。我以为 MVVM 会让我们免于如此奢侈的黑客攻击!

对于这个问题,有几个人提出了相互竞争的解决方案,但他们都是 hack,并没有提供一个干净、易于重用、优雅的解决方案。大多数 MVVM 工具包都掩盖了对话框,当它们处理它们时,它们只是不需要自定义界面或视图模型的警告框。

我打算放弃 MVVM 视图模式,至少放弃它的正统实现。你怎么看?如果你有任何麻烦,对你来说值得吗?我只是一个不称职的程序员还是 MVVM 不是它所宣传的那样?

【问题讨论】:

  • 我一直质疑 MVVM 是否过度设计。有趣的问题。
  • 像 MVVM 和 MVC 这样的模式看起来像是过度工程,直到您必须执行一些修改或更改组件。第一次你必须这样做,所有的仪式自己支付。
  • Lambda 很深奥?给我的消息。
  • @Ray - 哈哈,+1 评论! :D
  • 正如 Alan Cooper 十多年前在 About Face 中指出的那样,如果您正在设计 UI 并且模式对话框不是边缘情况,那么您可能正在做一些事情错了。

标签: .net wpf mvvm


【解决方案1】:

对不起,如果我的回答有点冗长,但不要怪我!你的问题也很长。

综上所述,MVVM 并非毫无意义。

明显的例子是添加 支持模态对话框。这 正确的方法是放一个对话框 并将其绑定到视图模型。得到 这很难工作。

是的,确实如此。
但是,MVVM 为您提供了一种将 UI 外观与其逻辑分离的方法。没有人强迫你在任何地方都使用它,也没有人拿着枪抵着你的额头让你为所有东西创建一个单独的 ViewModel。

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

为了从 MVVM 模式中受益,您必须在应用程序层的多个位置分布代码。您还必须使用深奥的编程结构,例如模板和 Lamba 表达式。

好吧,您不必在多个地方使用它。这就是我将如何解决它:

  • 将 XAML 添加到视图中,而 .xaml.cs 中没有任何内容
  • 在 ViewModel 中编写每个应用逻辑(除了直接与 UI 元素一起操作的东西)
  • 应由 UI 完成但与业务逻辑无关的所有代码都放入 .xaml.cs 文件中

我认为 MVVM 的目的主要是将应用程序的逻辑和具体的 UI 分离,从而可以轻松修改(或完全替换) UI。
我使用以下原则:View 可以从 ViewModel 知道并假设它想要的任何东西,但 ViewModel 对 View 一无所知。
WPF 提供了一个很好的绑定模型,您可以使用它来实现这一点。

(顺便说一句,如果使用得当,模板和 lambda 表达式并不深奥。但如果您不想使用,请不要使用它们。)

让你盯着屏幕挠头的东西。

是的,我知道这种感觉。正是我第一次看到 MVVM 时的感受。但是一旦掌握了窍门,就不会再难受了。

我有一个关于框工作正常...

为什么要将 ViewModel 放在 about 框后面?没有意义。

大多数 MVVM 工具包都掩盖了对话框,当它们处理它们时,它们只是不需要自定义界面或视图模型的警告框。

是的,因为 UI 元素在同一个窗口或另一个窗口中,或者目前正在绕火星运行,这与 ViewModel 无关。
Separation of Concerns

编辑:

这是一个非常棒的视频,其标题是Build your own MVVM framework。值得一看。

【讨论】:

  • +1 表示最后三个单词。但其余的答案也很好。 :)
  • +1 获取有关使用代码隐藏的建议。一个常见的误解是,在 MVVM 中使用代码隐藏是“不好的”……但对于纯粹与 UI 相关的东西,这就是要走的路。
  • @Thomas:是的,我完全同意。我见过几个实现,人们将所有代码(甚至与 UI 相关的)都放入 ViewModel,因为(根据他们的说法)“那是代码所在的位置”。这很hacky。
  • @Venemo,我确实认为您可以使用自定义行为等技术封装很多您想要放入代码隐藏中的内容,如果您发现自己反复编写胶水代码,这将非常方便.不过总的来说,我确实认为将代码隐藏用于粘合比将笨拙的 XAML 组合在一起更好。在我看来,主要关心的是确保代码隐藏中没有任何东西足够复杂以保证单元测试。任何足够复杂的东西最好封装在 ViewModel 或扩展类中,例如 Behavior 或 MarkupExtension。
  • @Thomas:你说得对,关于 MVVM 的最大神话是 MVVM 的目的是摆脱代码隐藏。目的是从后面的代码中获取非 UI ocde。将纯 UI 代码放入 ViewModel 与将问题域代码放入代码隐藏中一样糟糕。
【解决方案2】:

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

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

但缺少的是:

  • 谁创建了 ViewModel?
  • 谁负责应用程序工作流程?
  • 当 ViewModel 需要相互通信时,谁在它们之间进行调解?

我的方法是引入一个(用例)控制器,它负责缺失的点。您可以在 WPF Application Framework (WAF) 示例应用程序中了解其工作原理。

【讨论】:

  • 由 Josh Smith 实现的中介者模式解决了我所有的视图模型通信问题。 Messenger.NotifyColleagues 提供了一种拥有完全独立的视图模型的方法,该模型知道如何响应全局事件(如果他们关心的话),而无需任何两个视图模型相互了解。它已经救了我们几次培根了。
【解决方案3】:

让这个工作很困难。在 为了从 MVVM 中受益 模式,你必须分发代码 在全国多个地方 应用程序的层。你也是 必须使用深奥的编程 像模板和兰巴这样的结构 表达式。

对于一个普通的模式对话框?你肯定在那儿做错了什么——MVVM 实现不必那么复杂。

考虑到您是 MVVM 和 WPF 的新手,您很可能到处都在使用次优解决方案并且不必要地使事情复杂化 - 至少我在第一次使用 WPF 时是这样做的。在放弃之前确保问题真的是 MVVM 而不是你的实现。

MVVM、MVC、文档视图等是一个古老的模式家族。有缺点,但没有你描述的那种致命缺陷。

【讨论】:

    【解决方案4】:

    我通过作弊来处理对话问题。我的 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();
    }
    

    这将所有 Dialog 执行放在一个地方,并且可以轻松地将其存根以进行单元测试。我遵循对话框的客户端必须创建适当的 ViewModel 的模式,然后他们可以根据需要对其进行配置。 Execute 调用阻塞,之后客户端可以查看 ViewModel 的内容以查看 Dialog 结果。

    对于大型应用程序来说,更“纯粹”的 MVVM 设计可能很重要,在这种应用程序中,您需要更干净的绝缘和更复杂的组合,但对于中小型应用程序,我认为是一种实用的方法,使用适当的服务来公开所需的钩子,已经足够了。

    【讨论】:

    • 但是你怎么称呼它呢?在 IWindowServices 类的函数中构造对话框而不是传入它不是更好吗?这样调用模型视图就不必知道有关特定对话框实现的任何信息。
    • 接口被注入到我需要访问应用程序对话框的任何 ViewModel 实例中。在大多数情况下,我为对话框传递 ViewModel,但我确实有点懒惰,并使用 WPF OpenFileDialog 和 SaveFileDialog 进行文件对话框调用。我的主要目标是为了单元测试的目的而进行隔离,因此这足以实现该目标。如果您想要更好的隔离,您可能想要创建一个 OpenFileViewModel 和 SaveFileViewModel,这将复制对话框的必要属性。
    • 请注意,这绝对不是一种纯粹的方法,因为使用对话框的 ViewModel 知道它要打开的每个对话框的特定 ViewModel。我觉得这是相当干净的,但您总是可以添加一个额外的绝缘层,该类纯粹公开对话框使用所需的参数,隐藏绑定期间使用的 ViewModel 属性的任何不必要的可见性。对于较小的应用,我觉得这种额外的绝缘是多余的。
    【解决方案5】:

    不,这并非毫无意义,但即使模式本身非常简单,也很难绕开你的脑袋。那里有大量的错误信息,各种团体都在为正确的方式而战。我认为对于 WPF 和 Silverlight,您应该使用 MVVM,否则您将过度编码并尝试在新模型中解决问题,“旧”赢形式方法只会给您带来麻烦。这在 Silverlight 中更是如此,因为一切都需要是异步的(围绕这一点的黑客是可能的,但您应该选择另一个平台)。

    我建议阅读这篇文章Simplifying the WPF TreeView by Using the ViewModel Pattern 仔细看看如何很好地实现 MVVM,并让您将您的获胜形式心态转变为 MVVM 中的新思维方式。简而言之,当您想完成某事时,首先将逻辑应用于 ViewModel 而不是 View。您想选择一个项目吗?换个图标?不要迭代 UI 元素,只需更新模型属性并让数据绑定完成细节。

    【讨论】:

      【解决方案6】:

      作为模式本身,MVVM 很棒。但是附带 NET 4.0 数据绑定支持的 WPF 控件库非常有限,它比 WinForm 好很多,但对于可绑定 MVVM 来说仍然不够,我想说它的功率大约是可绑定 MVVM 所需功率的 30%。 可绑定的 MVVM:它是 ViewModel 仅使用数据绑定与 View 连接的 UI。
      MVVM 模式是关于 ViewState 的对象表示,它没有描述如何保持 View 和 ViewModel 之间的同步,在 WPF 中它是数据绑定,但它可以是任何东西。实际上,您可以在任何支持事件\回调的 UI 工具包中使用 MVVM 模式,您可以在 WinForms 中的纯 WinAPI 中使用它(我做过,它对事件\回调的工作并不多),您甚至可以在文本中使用它控制台,比如使用 MVVM 模式重写 DoS 的 Norton Commander。

      简而言之:MVVM 并非毫无意义,它很棒。 NET 4.0 WPF 的控件库是垃圾。

      这是一个简单的概念证明 ViewModel,您不能使用 WPF 以纯 MVVM 方式进行数据绑定。

      public class PersonsViewModel
      {
          public IList<Person> PersonList;
          public IList<ColumnDescription> TableColumns;
          public IList<Person> SelectedPersons;
          public Person ActivePerson;
          public ColumnDescription SortedColumn;
      }
      

      你不能数据绑定 WPF 的 DataGrid 列标题,你不能数据绑定选定的行等等,你要么用代码简单的方式来做,要么为这 5 行代码编写 200 行 XAML hack 代码最简单的视图模型。您只能想象复杂的 ViewModel 会使事情变得更糟。
      所以答案很简单,除非您正在编写 Hello World 应用程序,否则在 WPF 中使用可绑定的 MVVM 是没有意义的。您将花费大部分时间思考如何绑定 ViewModel。数据绑定很好,但要准备好回退到事件的 70% 时间。

      【讨论】:

      • 您可以使用转换器将此绑定到 DataGrid。
      • @CameronMacFarland:不是全部,有些属性是只读且不可绑定的,有些根本不存在,只有报告状态变化的事件。
      • 我承认我没有太多使用 WPF DataGrid 的经验。我倾向于避免它,因为它很丑陋并且不再适合 WPF。话虽如此,转换器和 AttachedProperties 的组合来处理事件应该可以满足您的需求。
      • Alex,您遇到的问题在于 DataGrid 的设计,而不是 MVVM。说“数据绑定很好,但准备好回退到事件的 70% 时间”是完全不正确的。我已经编写了一些客观上巨大的 WPF 应用程序,其中 UI 中没有任何事件处理程序 - 除了 (Telerik) 数据网格初始化所需的事件处理程序。
      • 我认为你可能会取得更大的成功?”你可能会发现事情很难做的原因是你还不知道如何去做。
      【解决方案7】:

      我正在使用 PRISM 进行相当复杂的 MVVM 开发,所以我已经不得不处理这种问题。

      我个人的结论:

      MVVM 与 MVC /PopUps & co

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

      【讨论】:

        【解决方案8】:

        设计模式可以帮助您,而不是阻碍。成为一名优秀开发人员的一小部分是知道何时“打破规则”。如果 MVVM 对于一项任务来说很麻烦,并且您确定未来的价值不值得付出努力,那么不要使用该模式。例如,正如其他发帖人所评论的那样,您为什么要花费所有的开销来实现一个简单的关于框?

        设计模式从来不打算被教条地遵循。

        【讨论】:

        • 正确。一个简单的关于框应该很简单,但是如果您必须显示版本、许可、运行过程、公司名称等信息怎么办。这些信息都存在于某个地方。在标准表单中,您可以绑定所有这些信息并完成它。 MVVM 说你应该为它创建一个视图模型,这是多余的。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-30
        • 2016-07-05
        • 2021-06-20
        • 1970-01-01
        • 2021-12-15
        • 2012-09-25
        相关资源
        最近更新 更多